Passed
Push — master ( ef3387...d0a4bc )
by Anthony
07:14
created

CKFinder_Connector_CommandHandler_CreateZip::buildXml()   F

Complexity

Conditions 60
Paths > 20000

Size

Total Lines 217
Code Lines 113

Duplication

Lines 7
Ratio 3.23 %

Importance

Changes 0
Metric Value
cc 60
eloc 113
nc 534528
nop 0
dl 7
loc 217
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
* CKFinder
4
* ========
5
* http://cksource.com/ckfinder
6
* Copyright (C) 2007-2013, CKSource - Frederico Knabben. All rights reserved.
7
*
8
* The software, this file and its contents are subject to the CKFinder
9
* License. Please read the license.txt file before using, installing, copying,
10
* modifying or distribute this file or part of its contents. The contents of
11
* this file is part of the Source Code of CKFinder.
12
*
13
* CKFinder extension: provides commands to add files into a zip archive, or extract contents from a zip.
14
*/
15
if (!defined('IN_CKFINDER')) exit;
16
17
/**
18
 * Include base XML command handler
19
 */
20
require_once CKFINDER_CONNECTOR_LIB_DIR . "/CommandHandler/XmlCommandHandlerBase.php";
21
22
class CKFinder_Connector_CommandHandler_Unzip extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase
23
{
24
  protected $filePath;
25
  protected $skippedFilesNode;
26
  protected $_config;
27
28
  /**
29
   * Handle request and build XML
30
   */
31
  protected function buildXml()
32
  {
33
    if (empty($_POST['CKFinderCommand']) || $_POST['CKFinderCommand'] != 'true') {
34
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
35
    }
36
37
    if (!extension_loaded('zip')) {
38
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_COMMAND);
39
    }
40
41
    $this->checkConnector();
42
    $this->checkRequest();
43
44
    if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_UPLOAD)) {
45
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
46
    }
47
48
    if (!isset($_POST["fileName"])) {
49
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_NAME);
50
    }
51
52
    $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($_POST["fileName"]);
53
    $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
54
55
    if (!$resourceTypeInfo->checkExtension($fileName)) {
56
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION);
57
    }
58
59
    if (!CKFinder_Connector_Utils_FileSystem::checkFileName($fileName) || $resourceTypeInfo->checkIsHiddenFile($fileName)) {
60
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
61
    }
62
63
    $filePath = CKFinder_Connector_Utils_FileSystem::combinePaths($this->_currentFolder->getServerPath(), $fileName);
64
65
    if (!file_exists($filePath) || !is_file($filePath)) {
66
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND);
67
    }
68
69
    if (!is_writable(dirname($filePath))) {
70
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED);
71
    }
72
73
    if ( strtolower(pathinfo($fileName, PATHINFO_EXTENSION)) !== 'zip'){
74
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION);
75
    }
76
77
    $zip = new ZipArchive();
78
    $result = $zip->open($filePath);
79
    if ($result !== TRUE) {
80
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNKNOWN);
81
    }
82
    $this->zip = $zip;
83
    $this->filePath = $filePath;
84
    $this->_config =& CKFinder_Connector_Core_Factory::getInstance("Core_Config");
85
86
    // list of unzipped nodes
87
    $this->unzippedNodes = new CKFinder_Connector_Utils_XmlNode("UnzippedFiles");
88
89
    // list of files which could not be unzipped
90
    $this->skippedFilesNode = new CKFinder_Connector_Utils_XmlNode("Errors");
91
    $this->errorCode = CKFINDER_CONNECTOR_ERROR_NONE;
92
  }
93
94
  /**
95
   * Check one file for security reasons
96
   *
97
   * @param object $filePathInfo
98
   * @param string $originalFileName
99
   * @return mixed bool(false) - if security checks fails. Otherwise string - ralative zip archive path with secured filename.
100
   */
101
  protected function checkOneFile($filePathInfo, $originalFileName )
102
  {
103
    $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
104
105
    // checked if it is a folder
106
    $fileStat = $this->zip->statName($originalFileName);
107
    if ( empty($filePathInfo['extension']) && empty($fileStat['size']) ){
108
      $sNewFolderName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding(rtrim($fileStat['name'],'/'));
109
      if ($this->_config->forceAscii()) {
110
        $sNewFolderName = CKFinder_Connector_Utils_FileSystem::convertToAscii($sNewFolderName);
111
      }
112
      if (!CKFinder_Connector_Utils_FileSystem::checkFolderPath($sNewFolderName) || $resourceTypeInfo->checkIsHiddenFolder($sNewFolderName)) {
113
        $this->errorCode = CKFINDER_CONNECTOR_ERROR_INVALID_NAME;
114
        $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
115
        return false;
116
      }
117
118
      if (!is_writeable($this->_currentFolder->getServerPath())) {
119
        $this->errorCode = CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED;
120
        $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
121
        return false;
122
      }
123
124
      return $originalFileName;
125
    }
126
127
    $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($filePathInfo['basename']);
128
    $sFileName = CKFinder_Connector_Utils_FileSystem::secureFileName($fileName);
129
130
    // max file size
131
    $maxSize = $resourceTypeInfo->getMaxSize();
132
    if ( $maxSize && $fileStat['size'] > $maxSize )
133
    {
134
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_UPLOADED_TOO_BIG;
135
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
136
      return false;
137
    }
138
    // extension
139
    if ( !$resourceTypeInfo->checkExtension($sFileName) )
140
    {
141
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION;
142
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
143
      return false;
144
    }
145
    // hidden file
146
    if ( !CKFinder_Connector_Utils_FileSystem::checkFileName($sFileName) || $resourceTypeInfo->checkIsHiddenFile($sFileName) ){
147
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST;
148
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
149
      return false;
150
    }
151
152
    // unpack file to tmp dir for detecting html and valid image
153
    $dir = CKFinder_Connector_Utils_FileSystem::getTmpDir().'/';
154
    if ( file_exists($dir.$sFileName) && !CKFinder_Connector_Utils_FileSystem::unlink($dir.$sFileName) ){
155
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST;
156
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
157
      return false;
158
    }
159
    if ( copy('zip://'.$this->filePath.'#'.$originalFileName, $dir.$sFileName) )
160
    {
161
      // html extensions
162
      $htmlExtensions = $this->_config->getHtmlExtensions();
163
      $sExtension = CKFinder_Connector_Utils_FileSystem::getExtension( $dir.$sFileName );
164
      if ( $htmlExtensions
165
        && !CKFinder_Connector_Utils_Misc::inArrayCaseInsensitive( $sExtension, $htmlExtensions )
166
        && CKFinder_Connector_Utils_FileSystem::detectHtml($dir.$sFileName) === true )
167
      {
168
        $this->errorCode = CKFINDER_CONNECTOR_ERROR_UPLOADED_INVALID;
169
        $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
170
        return false;
171
      }
172
173
      // proper image
174
      $secureImageUploads = $this->_config->getSecureImageUploads();
175
      if ( $secureImageUploads
176
        && ( $isImageValid = CKFinder_Connector_Utils_FileSystem::isImageValid($dir.$sFileName, $sExtension) ) === false )
177
      {
178
        $this->errorCode = CKFINDER_CONNECTOR_ERROR_UPLOADED_INVALID;
179
        $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
180
        return false;
181
      }
182
    }
183
    $sDirName = ($filePathInfo['dirname'] != '.')? $filePathInfo['dirname'].'/' : '';
184
185
    return $sDirName.$sFileName;
186
  }
187
188
  /**
189
   * Add error node to the list
190
   * @param obj $oErrorsNode
191
   * @param int $errorCode
192
   * @param string $name
193
   * @param string $type
194
   * @param string $path
195
   */
196
  protected function appendErrorNode($oErrorsNode, $errorCode=0, $name, $type=null, $path=null)
197
  {
198
    $oErrorNode = new CKFinder_Connector_Utils_XmlNode("Error");
199
    $oErrorNode->addAttribute("code", $errorCode);
200
    $oErrorNode->addAttribute("name", CKFinder_Connector_Utils_FileSystem::convertToConnectorEncoding($name));
201
    if ( $type ){
202
      $oErrorNode->addAttribute("type", $type);
203
    }
204
    if ( $path ){
205
      $oErrorNode->addAttribute("folder", $path);
206
    }
207
    $oErrorsNode->addChild($oErrorNode);
208
  }
209
210
  /**
211
   * Add unzipped node to the list
212
   * @param obj $oUnzippedNodes
213
   * @param string $name
214
   * @param string $action
215
   */
216
  protected function appendUnzippedNode($oUnzippedNodes, $name, $action='ok')
217
  {
218
    $oUnzippedNode = new CKFinder_Connector_Utils_XmlNode("File");
219
    $oUnzippedNode->addAttribute("name", CKFinder_Connector_Utils_FileSystem::convertToConnectorEncoding($name));
220
    $oUnzippedNode->addAttribute("action", $action );
221
    $oUnzippedNodes->addChild($oUnzippedNode);
222
  }
223
224
  /**
225
   * Extract one file from zip archive
226
   *
227
   * @param string $extractPath
228
   * @param string $extractClientPath
229
   * @param array  $filePathInfo
230
   * @param string $sFileName
231
   * @param string $originalFileName
232
   */
233
  protected function extractTo($extractPath, $extractClientPath, $filePathInfo, $sFileName, $originalFileName)
234
  {
235
    $sfilePathInfo = pathinfo($extractPath.$sFileName);
236
    $extractClientPathDir = $filePathInfo['dirname'];
237
    if ( $filePathInfo['dirname'] == '.' ){
238
      $extractClientPathDir = '';
239
    }
240
    $folderPath = CKFinder_Connector_Utils_FileSystem::combinePaths($extractClientPath,$extractClientPathDir);
241
242
    $_aclConfig = $this->_config->getAccessControlConfig();
243
    $aclMask = $_aclConfig->getComputedMask($this->_currentFolder->getResourceTypeName(),$folderPath);
244
    $canCreateFolder = (($aclMask & CKFINDER_CONNECTOR_ACL_FOLDER_CREATE ) == CKFINDER_CONNECTOR_ACL_FOLDER_CREATE );
245
    // create sub-directory of zip archive
246
    if ( empty($sfilePathInfo['extension']) )
247
    {
248
      $fileStat = $this->zip->statName($originalFileName);
249
      $isDir = false;
250
      if ( $fileStat && empty($fileStat['size']) ){
251
        $isDir = true;
252
      }
253
      if( !empty($sfilePathInfo['dirname']) && !empty($sfilePathInfo['basename']) && !file_exists($sfilePathInfo['dirname'].'/'.$sfilePathInfo['basename']) )
254
      {
255
        if ( !$canCreateFolder ){
256
          return;
257
        }
258
        if ( $isDir ) {
259
          CKFinder_Connector_Utils_FileSystem::createDirectoryRecursively( $sfilePathInfo['dirname'].'/'.$sfilePathInfo['basename'] );
260
          return;
261
        } else {
262
          CKFinder_Connector_Utils_FileSystem::createDirectoryRecursively( $sfilePathInfo['dirname']);
263
        }
264
      } else {
265
        return;
266
      }
267
    }
268
269
    // extract file
270
    if ( !file_exists($sfilePathInfo['dirname']) ){
271
      if ( !$canCreateFolder ){
272
        $this->errorCode = CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED;
273
        $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName );
274
        return;
275
      }
276
      CKFinder_Connector_Utils_FileSystem::createDirectoryRecursively($sfilePathInfo['dirname']);
277
    }
278
    $isAuthorized = (($aclMask & CKFINDER_CONNECTOR_ACL_FILE_UPLOAD ) == CKFINDER_CONNECTOR_ACL_FILE_UPLOAD );
279
    if ( !$isAuthorized ){
280
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_COPY_FAILED;
281
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
282
      return;
283
    }
284
    if ( copy('zip://'.$this->filePath.'#'.$originalFileName, $extractPath.$sFileName) )
285
    {
286
      $this->appendUnzippedNode($this->unzippedNodes,$originalFileName);
287
      // chmod extracted file
288
      if ( is_file($extractPath.$sFileName) && ( $perms = $this->_config->getChmodFiles()) )
289
      {
290
        $oldumask = umask(0);
291
        chmod( $extractPath.$sFileName, $perms );
292
        umask( $oldumask );
293
      }
294
    }
295
    // file extraction failed, add to skipped
296
    else
297
    {
298
      $this->errorCode = CKFINDER_CONNECTOR_ERROR_COPY_FAILED;
299
      $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $originalFileName);
300
    }
301
  }
302
303
} // end of CKFinder_Connector_CommandHandler_Unzip class
304
305
class CKFinder_Connector_CommandHandler_UnzipHere extends CKFinder_Connector_CommandHandler_Unzip
306
{
307
  /**
308
   * Handle request and build XML
309
   */
310
  protected function buildXml()
311
  {
312
    parent::buildXml();
313
314
   $checkedFiles = array();
315
   if ( !empty($_POST['files']) && is_array($_POST['files']) ){
316
     foreach ( $_POST['files'] as $file){
317
       $checkedFiles[$file['name']] = $file;
318
     }
319
   }
320
321
   for ($i = 0; $i < $this->zip->numFiles; $i++)
322
    {
323
      $fileName = $this->zip->getNameIndex($i);
324
      if ( !empty($checkedFiles[$fileName]) && $checkedFiles[$fileName]['options'] == 'ok' )
325
      {
326
        // file was sucessfully unzipped before
327
        $this->appendUnzippedNode($this->unzippedNodes,$fileName);
328
        continue;
329
      }
330
331
      $filePathInfo = pathinfo($fileName);
332
      $fileType = 'File';
333
      $fileStat = $this->zip->statName($i);
334
      if ( empty($filePathInfo['extension']) && empty($fileStat['size']) ){
335
        $fileType = 'Folder';
336
        // check if we can create subfolder
337
        if ( !$this->_currentFolder->checkAcl( CKFINDER_CONNECTOR_ACL_FOLDER_CREATE ) ){
338
          $this->errorCode = CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED;
339
          $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $fileName, $fileType);
340
          continue;
341
        }
342
      }
343
      $extractPath = $this->_currentFolder->getServerPath();
344
      $extractClientPath = $this->_currentFolder->getClientPath();
345
346
      $sFileName = $this->checkOneFile( $filePathInfo, $fileName );
347
      // security test failed, add to skipped
348
      if ( false !== $sFileName )
349
      {
350
        if ( file_exists($extractPath.$sFileName) )
351
        {
352
          if ( !is_dir($extractPath.$sFileName) )
353
          {
354
            // file was checked before
355
            if ( !empty($checkedFiles[$fileName]['options']) )
356
            {
357
              if ( $checkedFiles[$fileName]['options'] == 'autorename')
358
              {
359
                $sFileName = CKFinder_Connector_Utils_FileSystem::autoRename($extractPath,$sFileName);
360
                $this->extractTo($extractPath,$extractClientPath,$filePathInfo,$sFileName,$fileName);
361
              }
362
              elseif ( $checkedFiles[$fileName]['options'] == 'overwrite')
363
              {
364
                if ( !$this->_currentFolder->checkAcl( CKFINDER_CONNECTOR_ACL_FILE_DELETE ) ){
365
                  $this->errorCode = CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED;
366
                  $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $fileName, $fileType);
367
                  continue;
368
                }
369
                if (!CKFinder_Connector_Utils_FileSystem::unlink($extractPath.$sFileName))
370
                {
371
                  $this->errorCode = CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED;
372
                  $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $fileName, $fileType);
373
                }
374
                else
375
                {
376
                  $this->extractTo($extractPath,$extractClientPath,$filePathInfo,$sFileName,$fileName);
377
                }
378
              }
379
              else
380
              {
381
                // add to skipped files
382
                $this->appendUnzippedNode($this->unzippedNodes,$fileName,'skip');
383
              }
384
            }
385
            else
386
            {
387
              $this->errorCode = CKFINDER_CONNECTOR_ERROR_ALREADY_EXIST;
388
              $this->appendErrorNode($this->skippedFilesNode, $this->errorCode, $fileName, $fileType);
389
            }
390
          }
391
        }
392
        // file doesn't exist yet
393
        else
394
        {
395
          $this->extractTo($extractPath,$extractClientPath,$filePathInfo,$sFileName,$fileName);
396
        }
397
      }
398
    }
399
    $this->zip->close();
400
401
    $this->_connectorNode->addChild($this->unzippedNodes);
402
403
    if ($this->errorCode != CKFINDER_CONNECTOR_ERROR_NONE) {
404
      $this->_connectorNode->addChild($this->skippedFilesNode);
405
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ZIP_FAILED);
406
    }
407
  }
408
409
  public function onBeforeExecuteCommand( &$command )
410
  {
411
      if ( $command == 'ExtractHere' )
412
      {
413
          $this->sendResponse();
414
          return false;
415
      }
416
      return true ;
417
  }
418
419
} // end of CKFinder_Connector_CommandHandler_UnzipHere class
420
421
class CKFinder_Connector_CommandHandler_UnzipTo extends CKFinder_Connector_CommandHandler_Unzip
422
{
423
  /**
424
   * Handle request and build XML
425
   */
426
  protected function buildXml()
427
  {
428
    parent::buildXml();
429
430
    $extractDir = ( !empty($_POST['extractDir']) ) ? ltrim($_POST['extractDir'],'/') : '';
431
    $extractDir = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($extractDir);
432
    if ( preg_match(CKFINDER_REGEX_INVALID_PATH, $extractDir) ){
433
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
434
    }
435
    $extractPath = CKFinder_Connector_Utils_FileSystem::combinePaths($this->_currentFolder->getServerPath(), $extractDir.'/');
436
    $extractClientPath = CKFinder_Connector_Utils_FileSystem::combinePaths($this->_currentFolder->getClientPath(),$extractDir);
437
    // acl for upload dir
438
    $_aclConfig = $this->_config->getAccessControlConfig();
439
    $aclMask = $_aclConfig->getComputedMask($this->_currentFolder->getResourceTypeName(),$extractDir);
440
441
    if ( !(($aclMask & CKFINDER_CONNECTOR_ACL_FOLDER_CREATE ) == CKFINDER_CONNECTOR_ACL_FOLDER_CREATE ) ){
442
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
443
    }
444
    if ( empty( $_POST['force']) && file_exists($extractPath) && is_dir($extractPath) && !CKFinder_Connector_Utils_FileSystem::isEmptyDir($extractPath) )
445
    {
446
      $dirExists = new CKFinder_Connector_Utils_XmlNode("FolderExists");
447
      $oErrorNode = new CKFinder_Connector_Utils_XmlNode("Folder");
448
      $oErrorNode->addAttribute("name", $extractDir);
449
      $dirExists->addChild($oErrorNode);
450
      $this->_connectorNode->addChild($dirExists);
451
      return;
452
    }
453
    elseif ( !empty( $_POST['force']) && $_POST['force'] =='overwrite' )
454
    {
455
      if ( !(($aclMask &  CKFINDER_CONNECTOR_ACL_FILE_UPLOAD | CKFINDER_CONNECTOR_ACL_FILE_DELETE ) ==  CKFINDER_CONNECTOR_ACL_FILE_UPLOAD | CKFINDER_CONNECTOR_ACL_FILE_DELETE ) ){
456
        $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
457
      }
458
      if ( $extractDir && file_exists($extractPath) && is_dir($extractPath) )
459
      {
460
        if ( !(($aclMask &  CKFINDER_CONNECTOR_ACL_FOLDER_CREATE | CKFINDER_CONNECTOR_ACL_FOLDER_DELETE ) ==  CKFINDER_CONNECTOR_ACL_FOLDER_CREATE | CKFINDER_CONNECTOR_ACL_FOLDER_DELETE ) ){
461
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
462
        }
463
        if (!CKFinder_Connector_Utils_FileSystem::unlink($extractPath))
464
        {
465
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED);
466
        }
467
      }
468
    }
469
    else if ( !empty( $_POST['force']) && $_POST['force'] !== 'merge' )
470
    {
471
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
472
    }
473
474
    for ($i = 0; $i < $this->zip->numFiles; $i++)
475
    {
476
      $fileName = $this->zip->getNameIndex($i);
477
      $filePathInfo = pathinfo($fileName);
478
479
      $sFileName = $this->checkOneFile( $filePathInfo, $fileName );
480
      // security test failed, add to skipped
481
      if ( $sFileName )
482
      {
483
        $this->extractTo($extractPath,$extractClientPath,$filePathInfo,$sFileName,$fileName);
484
      }
485
    }
486
    $this->zip->close();
487
488
489
    $this->_connectorNode->addChild($this->unzippedNodes);
490
491
    if ($this->errorCode != CKFINDER_CONNECTOR_ERROR_NONE) {
492
      $this->_connectorNode->addChild($this->skippedFilesNode);
493
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ZIP_FAILED);
494
    }
495
  }
496
497
  public function onBeforeExecuteCommand( &$command )
498
  {
499
    if ( $command == 'ExtractTo'){
500
      $this->sendResponse();
501
      return false;
502
    }
503
    return true ;
504
  }
505
506
} // end of CKFinder_Connector_CommandHandler_UnzipTo class
507
508
509
class CKFinder_Connector_CommandHandler_CreateZip extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase
510
{
511
  protected $_config;
512
513
  /**
514
   * Get private zip plugin config
515
   *
516
   * @access protected
517
   * @return array
518
   */
519
  protected function getConfig(){
520
    $config = array();
521
522
    $config['zipMaxSize'] = 'default';
523
    if (isset($GLOBALS['config']['ZipMaxSize']) && (string)$GLOBALS['config']['ZipMaxSize']!='default' ){
524
      $config['zipMaxSize'] = CKFinder_Connector_Utils_Misc::returnBytes((string)$GLOBALS['config']['ZipMaxSize']);
525
    }
526
527
    return $config;
528
  }
529
530
  /**
531
   * Checks given file for security
532
   *
533
   * @param  SplFileInfo $file
534
   * @access protected
535
   * @return bool
536
   */
537
  protected function checkOneFile($file)
538
  {
539
    $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
540
    $_aclConfig = $this->_config->getAccessControlConfig();
541
    $directory = str_replace('\\','/', $resourceTypeInfo->getDirectory());
542
    $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($file->getFilename());
543
544
    if ($this->_config->forceAscii()) {
545
      $fileName = CKFinder_Connector_Utils_FileSystem::convertToAscii($fileName);
546
    }
547
    $pathName = str_replace('\\','/', pathinfo($file->getPathname(), PATHINFO_DIRNAME) );
548
    $pathName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($pathName);
549
550
    // acl
551
    $aclMask = $_aclConfig->getComputedMask($this->_currentFolder->getResourceTypeName(), str_ireplace($directory,'',$pathName));
552
    $isAuthorized = (($aclMask & CKFINDER_CONNECTOR_ACL_FILE_VIEW) == CKFINDER_CONNECTOR_ACL_FILE_VIEW);
553
    if ( !$isAuthorized ){
554
      return false;
555
    }
556
557
    // if it is a folder fileName represents the dir
558
    if ( $file->isDir() && ( !CKFinder_Connector_Utils_FileSystem::checkFolderPath($fileName) || $resourceTypeInfo->checkIsHiddenPath($fileName) ) ){
559
      return false;
560
    }
561
    // folder name
562
    if ( !CKFinder_Connector_Utils_FileSystem::checkFolderPath($pathName) ){
563
      return false;
564
    }
565
566
    // is hidden
567
    if ( $resourceTypeInfo->checkIsHiddenPath($pathName) || $resourceTypeInfo->checkIsHiddenFile($fileName) ){
568
      return false;
569
    }
570
571
    // extension
572
    if ( !$resourceTypeInfo->checkExtension($fileName) || !CKFinder_Connector_Utils_FileSystem::checkFileName($fileName) ){
573
      return false;
574
    }
575
576
    return true;
577
  }
578
579
  /**
580
   * Get list of all files in given directory, including sub-directories
581
   *
582
   * @param string $directory
583
   * @param int $zipMaxSize Maximum zip file size
584
   * @return array $allFiles
585
   */
586
  protected function getFilesRecursively( $directory, $zipMaxSize )
587
  {
588
    $allFiles = array();
589
    $_zipFilesSize = 0;
590
    $serverPath = str_replace('\\','/',$directory);
591
592
    foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $file ) {
593
      if ( !$this->checkOneFile($file) ){
594
        continue;
595
      }
596
      if ( !empty($zipMaxSize) ){
597
        clearstatcache();
598
        $_zipFilesSize += $file->getSize();
599
        if ( $_zipFilesSize > $zipMaxSize ) {
600
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CREATED_FILE_TOO_BIG);
601
        }
602
      }
603
      $pathName = str_replace('\\','/',$file->getPathname());
604
      if ( $file->isDir() ){
605
        // skip dot folders on unix systems ( do not try to use isDot() as $file is not a  DirectoryIterator obj )
606
        if ( in_array($file->getFilename(),array('..','.')) ){
607
          continue;
608
        }
609
        if ($pathName != rtrim($serverPath,'/')){
610
          $allFiles[ ltrim(str_ireplace(rtrim($serverPath,'/'),'',$pathName),'/') ] = '';
611
        }
612
      } else {
613
        $allFiles[$pathName] = str_ireplace($serverPath,'',$pathName);
614
      }
615
    }
616
617
    return $allFiles;
618
  }
619
620
  /**
621
   * Handle request and build XML
622
   */
623
  public function buildXml()
624
  {
625
    if (!extension_loaded('zip')) {
626
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_COMMAND);
627
    }
628
629
    $this->checkConnector();
630
    $this->checkRequest();
631
632
    if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_UPLOAD)) {
633
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
634
    }
635
636
    $this->_config =& CKFinder_Connector_Core_Factory::getInstance("Core_Config");
637
    $currentResourceTypeConfig = $this->_currentFolder->getResourceTypeConfig();
638
    $_sServerDir = $this->_currentFolder->getServerPath();
639
640
    $files = array();
641
642
    $_zipFilesSize = 0;
643
    $config = $this->getConfig();
644
    $zipMaxSize = $config['zipMaxSize'];
645
    if ( !empty($zipMaxSize) && $zipMaxSize == 'default' ){
646
      $zipMaxSize = $currentResourceTypeConfig->getMaxSize();
647
    }
648
649
    $_isBasket = ( isset($_POST['basket']) && $_POST['basket'] == 'true' )? true : false;
650
651
    if ( !empty($_POST['files']))
652
    {
653
654
      $_aclConfig = $this->_config->getAccessControlConfig();
655
      $aclMasks = array();
656
      $_resourceTypeConfig = array();
657
658
      foreach ( $_POST['files'] as $arr ){
659
        if ( empty($arr['name']) || empty($arr['type']) || empty($arr['folder']) ) {
660
          continue;
661
        }
662
        // file name
663
        $name = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($arr['name']);
664
        // resource type
665
        $type = $arr['type'];
666
        // client path
667
        $path = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($arr['folder']);
668
669
        // check #1 (path)
670
        if (!CKFinder_Connector_Utils_FileSystem::checkFileName($name) || preg_match(CKFINDER_REGEX_INVALID_PATH, $path)) {
671
          continue;
672
        }
673
674
        // get resource type config for current file
675
        if (!isset($_resourceTypeConfig[$type])) {
676
          $_resourceTypeConfig[$type] = $this->_config->getResourceTypeConfig($type);
677
        }
678
679
        // check #2 (resource type)
680
        if (is_null($_resourceTypeConfig[$type])) {
681
          continue;
682
        }
683
684
        // check #3 (extension)
685
        if (!$_resourceTypeConfig[$type]->checkExtension($name, false)) {
686
          continue;
687
        }
688
689
        // check #4 (extension) - when moving to another resource type, double check extension
690
        if ($currentResourceTypeConfig->getName() != $type && !$currentResourceTypeConfig->checkExtension($name, false)) {
691
          continue;
692
        }
693
694
        // check #5 (hidden folders)
695
        // cache results
696
        if (empty($checkedPaths[$path])) {
697
          $checkedPaths[$path] = true;
698
699
          if ($_resourceTypeConfig[$type]->checkIsHiddenPath($path)) {
700
            continue;
701
          }
702
        }
703
704
        // check #6 (hidden file name)
705
        if ($currentResourceTypeConfig->checkIsHiddenFile($name)) {
706
          continue;
707
        }
708
709
        // check #7 (Access Control, need file view permission to source files)
710
        if (!isset($aclMasks[$type."@".$path])) {
711
          $aclMasks[$type."@".$path] = $_aclConfig->getComputedMask($type, $path);
712
        }
713
714
        $isAuthorized = (($aclMasks[$type."@".$path] & CKFINDER_CONNECTOR_ACL_FILE_VIEW) == CKFINDER_CONNECTOR_ACL_FILE_VIEW);
715
        if (!$isAuthorized) {
716
          continue;
717
        }
718
719
        $sourceFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($_resourceTypeConfig[$type]->getDirectory().$path,$name);
720
        // check #8 (invalid file name)
721
        if (!file_exists($sourceFilePath) || !is_file($sourceFilePath)) {
722
          continue;
723
        }
724
725
        // check #9 - max file size
726
        if ( !empty($zipMaxSize) ){
727
          clearstatcache();
728
          $_zipFilesSize += filesize($sourceFilePath);
729
          if ( $_zipFilesSize > $zipMaxSize ) {
730
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_CREATED_FILE_TOO_BIG);
731
          }
732
        }
733
734
        $zipPathPart = ( $_isBasket ) ? CKFinder_Connector_Utils_FileSystem::combinePaths($type,$path) : '';
735
736
        $files[$sourceFilePath] = $zipPathPart.pathinfo($sourceFilePath,PATHINFO_BASENAME);
737
      }
738
    }
739
    else
740
    {
741
      if (!is_dir($_sServerDir)) {
742
        $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FOLDER_NOT_FOUND);
743
      }
744
      $files = $this->getFilesRecursively($_sServerDir,$zipMaxSize);
745
    }
746
    if ( sizeof($files)<1) {
747
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND);
748
    }
749
    // default destination dir - temp
750
    $dest_dir = CKFinder_Connector_Utils_FileSystem::getTmpDir();
751
    $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
752
753
    // default file name - hash
754
    $zip_filename = substr(md5(serialize($files)), 0, 16).$resourceTypeInfo->getHash().'.zip';
755
756
    // compress files - do not download them
757
    // change destination and name
758
    if ( isset($_POST['download']) && $_POST['download'] == 'false'){
759
      $dest_dir = $_sServerDir;
760
      if ( isset($_POST['zipName']) && !empty($_POST['zipName'])){
761
        $zip_filename = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($_POST['zipName']);
762
        if (!$resourceTypeInfo->checkExtension($zip_filename)) {
763
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION);
764
        }
765
      }
766
    }
767
    if (!CKFinder_Connector_Utils_FileSystem::checkFileName($zip_filename) || $resourceTypeInfo->checkIsHiddenFile($zip_filename)) {
768
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_NAME);
769
    }
770
    if ($this->_config->forceAscii()) {
771
      $zip_filename = CKFinder_Connector_Utils_FileSystem::convertToAscii($zip_filename);
772
    }
773
774
    $zipFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($dest_dir, $zip_filename);
775
776
    if (!is_writable(dirname($zipFilePath))) {
777
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED);
778
    }
779
780
    // usually we would need to create zip?
781
    $createZip = true;
782
783
    // only if file already exists and we want download it
784
    // do not create new one - because hash of previously created is the same - existing archive is ok
785
    if ( file_exists($zipFilePath) && isset($_POST['download']) && $_POST['download'] == 'true' ){
786
      $createZip = false;
787
    }
788
    // if we only want to create archive
789
    else
790
    {
791
      if ( file_exists($zipFilePath) && ( !isset($_POST['fileExistsAction']) || !in_array($_POST['fileExistsAction'], array('autorename','overwrite')) ) ){
792
        $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ALREADY_EXIST);
793
      }
794
795
      if ( !$this->_currentFolder->checkAcl( CKFINDER_CONNECTOR_ACL_FILE_UPLOAD )) {
796
        $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
797
      }
798
      // check how to deal with existing file
799
      if ( isset($_POST['fileExistsAction']) && $_POST['fileExistsAction'] == 'autorename' )
800
      {
801
        if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_UPLOAD | CKFINDER_CONNECTOR_ACL_FILE_RENAME )) {
802
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
803
        }
804
        $zip_filename = CKFinder_Connector_Utils_FileSystem::autoRename($dest_dir, $zip_filename);
805
        $zipFilePath = CKFinder_Connector_Utils_FileSystem::combinePaths($dest_dir, $zip_filename);
806
      }
807
      elseif ( isset($_POST['fileExistsAction']) && $_POST['fileExistsAction'] == 'overwrite' )
808
      {
809
        if ( !$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_RENAME | CKFINDER_CONNECTOR_ACL_FILE_DELETE)) {
810
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
811
        }
812
        if (!CKFinder_Connector_Utils_FileSystem::unlink($zipFilePath)){
813
          $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED);
814
        }
815
      }
816
    }
817
818
    if ( $createZip ){
819
      $zip = new ZipArchive();
820
      $result = $zip->open( $zipFilePath, ZIPARCHIVE::CREATE);
821
      if ( $result !== TRUE ) {
822
        $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNKNOWN);
823
      }
824
      foreach ( $files as $pathname => $filename ){
825
        if ( !empty($filename) ){
826
          if ( file_exists($pathname) && is_readable($pathname) ){
827
            $zip->addFile( $pathname, $filename );
828
          }
829
        } else {
830
          $zip->addEmptyDir( $pathname );
831
        }
832
      }
833
      $zip->close();
834
    }
835
836
    $file = new CKFinder_Connector_Utils_XmlNode("ZipFile");
837
    $file->addAttribute("name", $zip_filename);
838
    $this->_connectorNode->addChild($file);
839
  }
840
841
  public function onBeforeExecuteCommand( &$command )
842
  {
843
    if ( $command == 'CreateZip'){
844
      $this->sendResponse();
845
      return false;
846
    }
847
    return true ;
848
  }
849
850
} // end of CKFinder_Connector_CommandHandler_DownloadZip class
851
852
class CKFinder_Connector_CommandHandler_DownloadZip extends CKFinder_Connector_CommandHandler_CreateZip
853
{
854
  /**
855
   * Sends generated zip file to the user
856
   */
857
  protected function sendZipFile()
858
  {
859
    if (!function_exists('ob_list_handlers') || ob_list_handlers()) {
860
      @ob_end_clean();
861
    }
862
    header("Content-Encoding: none");
863
864
    $this->checkConnector();
865
    $this->checkRequest();
866
867
    // empty wystarczy
868
    if ( empty($_GET['FileName']) ){
869
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND);
870
    }
871
872
    $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
873
    $hash = $resourceTypeInfo->getHash();
874
    if ( $hash !== $_GET['hash'] || $hash !== substr($_GET['FileName'],16,16) ){
875
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
876
    }
877
878
    if (!$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_VIEW)) {
879
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
880
    }
881
882
    $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding(trim($_GET['FileName']));
883
884
    if (!CKFinder_Connector_Utils_FileSystem::checkFileName($fileName)) {
885
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
886
    }
887
888
    if ( strtolower(pathinfo($fileName, PATHINFO_EXTENSION)) !== 'zip'){
889
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION);
890
    }
891
892
    $dest_dir = CKFinder_Connector_Utils_FileSystem::getTmpDir();
893
    $filePath = CKFinder_Connector_Utils_FileSystem::combinePaths($dest_dir,$fileName);
894
    if ( !file_exists($filePath) || !is_file($filePath)) {
895
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND);
896
    }
897
    if (!is_readable($filePath)) {
898
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_ACCESS_DENIED);
899
    }
900
901
    $zipFileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding(trim($_GET['ZipName']));
902
    if (!CKFinder_Connector_Utils_FileSystem::checkFileName($zipFileName)) {
903
      $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
904
    }
905
    $fileFilename = pathinfo($zipFileName,PATHINFO_BASENAME );
906
907
    header("Content-Encoding: none");
908
    header("Cache-Control: cache, must-revalidate");
909
    header("Pragma: public");
910
    header("Expires: 0");
911
    $user_agent = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "";
912
    $encodedName = str_replace("\"", "\\\"", $fileFilename);
913
    if (strpos($user_agent, "MSIE") !== false) {
914
      $encodedName = str_replace(array("+", "%2E"), array(" ", "."), urlencode($encodedName));
915
    }
916
    header("Content-type: application/octet-stream; name=\"" . $fileFilename . "\"");
917
    header("Content-Disposition: attachment; filename=\"" . $encodedName. "\"");
918
    header("Content-Length: " . filesize($filePath));
919
    CKFinder_Connector_Utils_FileSystem::sendFile($filePath);
920
    exit;
921
  }
922
923
  public function onBeforeExecuteCommand( &$command )
924
  {
925
    if ( $command == 'DownloadZip'){
926
      $this->sendZipFile();
927
      return false;
928
    }
929
    return true ;
930
  }
931
932
} // end of CKFinder_Connector_CommandHandler_DownloadZip
933
934
if (extension_loaded('zip'))
935
{
936
  $CommandHandler_UnzipHere = new CKFinder_Connector_CommandHandler_UnzipHere();
937
  $CommandHandler_UnzipTo = new CKFinder_Connector_CommandHandler_UnzipTo();
938
  $CommandHandler_CreateZip = new CKFinder_Connector_CommandHandler_CreateZip();
939
  $CommandHandler_DownloadZip = new CKFinder_Connector_CommandHandler_DownloadZip();
940
  $config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_UnzipHere, "onBeforeExecuteCommand");
941
  $config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_UnzipTo, "onBeforeExecuteCommand");
942
  $config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_CreateZip, "onBeforeExecuteCommand");
943
  $config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_DownloadZip, "onBeforeExecuteCommand");
944
  $config['Plugins'][] = 'zip';
945
}
946