Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TTarFileExtractor class file
4
 *
5
 * @author Vincent Blavet <vincent@phpconcept.net>
6
 * @copyright Copyright &copy; 1997-2003 The PHP Group
7
 * @version $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $
8
 * @package System.IO
9
 */
10
 
11
/* vim: set ts=4 sw=4: */
12
// +----------------------------------------------------------------------+
13
// | PHP Version 4                                                        |
14
// +----------------------------------------------------------------------+
15
// | Copyright (c) 1997-2003 The PHP Group                                |
16
// +----------------------------------------------------------------------+
17
// | This source file is subject to version 3.0 of the PHP license,       |
18
// | that is bundled with this package in the file LICENSE, and is        |
19
// | available through the world-wide-web at the following url:           |
20
// | http://www.php.net/license/3_0.txt.                                  |
21
// | If you did not receive a copy of the PHP license and are unable to   |
22
// | obtain it through the world-wide-web, please send a note to          |
23
// | license@php.net so we can mail you a copy immediately.               |
24
// +----------------------------------------------------------------------+
25
// | Author: Vincent Blavet <vincent@phpconcept.net>                      |
26
// +----------------------------------------------------------------------+
27
//
28
// $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $
29
 
30
/**
31
 * TTarFileExtractor class
32
 *
33
 * @author Vincent Blavet <vincent@phpconcept.net>
34
 * @version $Id: TTarFileExtractor.php 1637 2007-01-17 22:26:53Z xue $
35
 * @package System.IO
36
 * @since 3.0
37
 */
38
class TTarFileExtractor
39
{
40
    /**
41
    * @var string Name of the Tar
42
    */
43
    private $_tarname='';
44
 
45
    /**
46
    * @var file descriptor
47
    */
48
    private $_file=0;
49
 
50
    /**
51
    * @var string Local Tar name of a remote Tar (http:// or ftp://)
52
    */
53
    private $_temp_tarname='';
54
 
55
    /**
56
    * Archive_Tar Class constructor. This flavour of the constructor only
57
    * declare a new Archive_Tar object, identifying it by the name of the
58
    * tar file.
59
    *
60
    * @param    string  $p_tarname  The name of the tar archive to create
61
    * @access public
62
    */
63
    public function __construct($p_tarname)
64
    {
65
        $this->_tarname = $p_tarname;
66
    }
67
 
68
    public function __destruct()
69
    {
70
        $this->_close();
71
        // ----- Look for a local copy to delete
72
        if ($this->_temp_tarname != '')
73
            @unlink($this->_temp_tarname);
74
    }
75
 
76
    public function extract($p_path='')
77
    {
78
        return $this->extractModify($p_path, '');
79
    }
80
 
81
    /**
82
    * This method extract all the content of the archive in the directory
83
    * indicated by $p_path. When relevant the memorized path of the
84
    * files/dir can be modified by removing the $p_remove_path path at the
85
    * beginning of the file/dir path.
86
    * While extracting a file, if the directory path does not exists it is
87
    * created.
88
    * While extracting a file, if the file already exists it is replaced
89
    * without looking for last modification date.
90
    * While extracting a file, if the file already exists and is write
91
    * protected, the extraction is aborted.
92
    * While extracting a file, if a directory with the same name already
93
    * exists, the extraction is aborted.
94
    * While extracting a directory, if a file with the same name already
95
    * exists, the extraction is aborted.
96
    * While extracting a file/directory if the destination directory exist
97
    * and is write protected, or does not exist but can not be created,
98
    * the extraction is aborted.
99
    * If after extraction an extracted file does not show the correct
100
    * stored file size, the extraction is aborted.
101
    * When the extraction is aborted, a PEAR error text is set and false
102
    * is returned. However the result can be a partial extraction that may
103
    * need to be manually cleaned.
104
    *
105
    * @param string $p_path         The path of the directory where the
106
	*                               files/dir need to by extracted.
107
    * @param string $p_remove_path  Part of the memorized path that can be
108
	*                               removed if present at the beginning of
109
	*                               the file/dir path.
110
    * @return boolean               true on success, false on error.
111
    * @access public
112
    */
113
    protected function extractModify($p_path, $p_remove_path)
114
    {
115
        $v_result = true;
116
        $v_list_detail = array();
117
 
118
        if ($v_result = $this->_openRead()) {
119
            $v_result = $this->_extractList($p_path, $v_list_detail,
120
			                                "complete", 0, $p_remove_path);
121
            $this->_close();
122
        }
123
 
124
        return $v_result;
125
    }
126
 
127
    protected function _error($p_message)
128
    {
129
		throw new Exception($p_message);
130
    }
131
 
132
    private function _isArchive($p_filename=null)
133
    {
134
        if ($p_filename == null) {
135
            $p_filename = $this->_tarname;
136
        }
137
        clearstatcache();
138
        return @is_file($p_filename);
139
    }
140
 
141
    private function _openRead()
142
    {
143
        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
144
 
145
          // ----- Look if a local copy need to be done
146
          if ($this->_temp_tarname == '') {
147
              $this->_temp_tarname = uniqid('tar').'.tmp';
148
              if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
149
                $this->_error('Unable to open in read mode \''
150
				              .$this->_tarname.'\'');
151
                $this->_temp_tarname = '';
152
                return false;
153
              }
154
              if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
155
                $this->_error('Unable to open in write mode \''
156
				              .$this->_temp_tarname.'\'');
157
                $this->_temp_tarname = '';
158
                return false;
159
              }
160
              while ($v_data = @fread($v_file_from, 1024))
161
                  @fwrite($v_file_to, $v_data);
162
              @fclose($v_file_from);
163
              @fclose($v_file_to);
164
          }
165
 
166
          // ----- File to open if the local copy
167
          $v_filename = $this->_temp_tarname;
168
 
169
        } else
170
          // ----- File to open if the normal Tar file
171
          $v_filename = $this->_tarname;
172
 
173
		$this->_file = @fopen($v_filename, "rb");
174
 
175
        if ($this->_file == 0) {
176
            $this->_error('Unable to open in read mode \''.$v_filename.'\'');
177
            return false;
178
        }
179
 
180
        return true;
181
    }
182
 
183
    private function _close()
184
    {
185
        //if (isset($this->_file)) {
186
        if (is_resource($this->_file))
187
		{
188
               @fclose($this->_file);
189
            $this->_file = 0;
190
        }
191
 
192
        // ----- Look if a local copy need to be erase
193
        // Note that it might be interesting to keep the url for a time : ToDo
194
        if ($this->_temp_tarname != '') {
195
            @unlink($this->_temp_tarname);
196
            $this->_temp_tarname = '';
197
        }
198
 
199
        return true;
200
    }
201
 
202
    private function _cleanFile()
203
    {
204
        $this->_close();
205
 
206
        // ----- Look for a local copy
207
        if ($this->_temp_tarname != '') {
208
            // ----- Remove the local copy but not the remote tarname
209
            @unlink($this->_temp_tarname);
210
            $this->_temp_tarname = '';
211
        } else {
212
            // ----- Remove the local tarname file
213
            @unlink($this->_tarname);
214
        }
215
        $this->_tarname = '';
216
 
217
        return true;
218
    }
219
 
220
    private function _readBlock()
221
    {
222
      $v_block = null;
223
      if (is_resource($this->_file)) {
224
              $v_block = @fread($this->_file, 512);
225
      }
226
      return $v_block;
227
    }
228
 
229
    private function _jumpBlock($p_len=null)
230
    {
231
      if (is_resource($this->_file)) {
232
          if ($p_len === null)
233
              $p_len = 1;
234
 
235
              @fseek($this->_file, @ftell($this->_file)+($p_len*512));
236
      }
237
      return true;
238
    }
239
 
240
    private function _readHeader($v_binary_data, &$v_header)
241
    {
242
        if (strlen($v_binary_data)==0) {
243
            $v_header['filename'] = '';
244
            return true;
245
        }
246
 
247
        if (strlen($v_binary_data) != 512) {
248
            $v_header['filename'] = '';
249
            $this->_error('Invalid block size : '.strlen($v_binary_data));
250
            return false;
251
        }
252
 
253
        // ----- Calculate the checksum
254
        $v_checksum = 0;
255
        // ..... First part of the header
256
        for ($i=0; $i<148; $i++)
257
            $v_checksum+=ord(substr($v_binary_data,$i,1));
258
        // ..... Ignore the checksum value and replace it by ' ' (space)
259
        for ($i=148; $i<156; $i++)
260
            $v_checksum += ord(' ');
261
        // ..... Last part of the header
262
        for ($i=156; $i<512; $i++)
263
           $v_checksum+=ord(substr($v_binary_data,$i,1));
264
 
265
        $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
266
		                 ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
267
						 ."a32uname/a32gname/a8devmajor/a8devminor",
268
						 $v_binary_data);
269
 
270
        // ----- Extract the checksum
271
        $v_header['checksum'] = OctDec(trim($v_data['checksum']));
272
        if ($v_header['checksum'] != $v_checksum) {
273
            $v_header['filename'] = '';
274
 
275
            // ----- Look for last block (empty block)
276
            if (($v_checksum == 256) && ($v_header['checksum'] == 0))
277
                return true;
278
 
279
            $this->_error('Invalid checksum for file "'.$v_data['filename']
280
			              .'" : '.$v_checksum.' calculated, '
281
						  .$v_header['checksum'].' expected');
282
            return false;
283
        }
284
 
285
        // ----- Extract the properties
286
        $v_header['filename'] = trim($v_data['filename']);
287
        $v_header['mode'] = OctDec(trim($v_data['mode']));
288
        $v_header['uid'] = OctDec(trim($v_data['uid']));
289
        $v_header['gid'] = OctDec(trim($v_data['gid']));
290
        $v_header['size'] = OctDec(trim($v_data['size']));
291
        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
292
        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
293
          $v_header['size'] = 0;
294
        }
295
        return true;
296
    }
297
 
298
    private function _readLongHeader(&$v_header)
299
    {
300
      $v_filename = '';
301
      $n = floor($v_header['size']/512);
302
      for ($i=0; $i<$n; $i++) {
303
        $v_content = $this->_readBlock();
304
        $v_filename .= $v_content;
305
      }
306
      if (($v_header['size'] % 512) != 0) {
307
        $v_content = $this->_readBlock();
308
        $v_filename .= $v_content;
309
      }
310
 
311
      // ----- Read the next header
312
      $v_binary_data = $this->_readBlock();
313
 
314
      if (!$this->_readHeader($v_binary_data, $v_header))
315
        return false;
316
 
317
      $v_header['filename'] = $v_filename;
318
 
319
      return true;
320
    }
321
 
322
    protected function _extractList($p_path, &$p_list_detail, $p_mode,
323
	                      $p_file_list, $p_remove_path)
324
    {
325
    $v_result=true;
326
    $v_nb = 0;
327
    $v_extract_all = true;
328
    $v_listing = false;
329
 
330
    $p_path = $this->_translateWinPath($p_path, false);
331
    if ($p_path == '' || (substr($p_path, 0, 1) != '/'
332
	    && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
333
      $p_path = "./".$p_path;
334
    }
335
    $p_remove_path = $this->_translateWinPath($p_remove_path);
336
 
337
    // ----- Look for path to remove format (should end by /)
338
    if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
339
      $p_remove_path .= '/';
340
    $p_remove_path_size = strlen($p_remove_path);
341
 
342
    switch ($p_mode) {
343
      case "complete" :
344
        $v_extract_all = true;
345
        $v_listing = false;
346
      break;
347
      case "partial" :
348
          $v_extract_all = false;
349
          $v_listing = false;
350
      break;
351
      case "list" :
352
          $v_extract_all = false;
353
          $v_listing = true;
354
      break;
355
      default :
356
        $this->_error('Invalid extract mode ('.$p_mode.')');
357
        return false;
358
    }
359
 
360
    clearstatcache();
361
 
362
    while (strlen($v_binary_data = $this->_readBlock()) != 0)
363
    {
364
      $v_extract_file = false;
365
      $v_extraction_stopped = 0;
366
 
367
      if (!$this->_readHeader($v_binary_data, $v_header))
368
        return false;
369
 
370
      if ($v_header['filename'] == '') {
371
        continue;
372
      }
373
 
374
      // ----- Look for long filename
375
      if ($v_header['typeflag'] == 'L') {
376
        if (!$this->_readLongHeader($v_header))
377
          return false;
378
      }
379
 
380
      if ((!$v_extract_all) && (is_array($p_file_list))) {
381
        // ----- By default no unzip if the file is not found
382
        $v_extract_file = false;
383
 
384
        for ($i=0; $i<sizeof($p_file_list); $i++) {
385
          // ----- Look if it is a directory
386
          if (substr($p_file_list[$i], -1) == '/') {
387
            // ----- Look if the directory is in the filename path
388
            if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
389
			    && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
390
				    == $p_file_list[$i])) {
391
              $v_extract_file = true;
392
              break;
393
            }
394
          }
395
 
396
          // ----- It is a file, so compare the file names
397
          elseif ($p_file_list[$i] == $v_header['filename']) {
398
            $v_extract_file = true;
399
            break;
400
          }
401
        }
402
      } else {
403
        $v_extract_file = true;
404
      }
405
 
406
      // ----- Look if this file need to be extracted
407
      if (($v_extract_file) && (!$v_listing))
408
      {
409
        if (($p_remove_path != '')
410
            && (substr($v_header['filename'], 0, $p_remove_path_size)
411
			    == $p_remove_path))
412
          $v_header['filename'] = substr($v_header['filename'],
413
		                                 $p_remove_path_size);
414
        if (($p_path != './') && ($p_path != '/')) {
415
          while (substr($p_path, -1) == '/')
416
            $p_path = substr($p_path, 0, strlen($p_path)-1);
417
 
418
          if (substr($v_header['filename'], 0, 1) == '/')
419
              $v_header['filename'] = $p_path.$v_header['filename'];
420
          else
421
            $v_header['filename'] = $p_path.'/'.$v_header['filename'];
422
        }
423
        if (file_exists($v_header['filename'])) {
424
          if (   (@is_dir($v_header['filename']))
425
		      && ($v_header['typeflag'] == '')) {
426
            $this->_error('File '.$v_header['filename']
427
			              .' already exists as a directory');
428
            return false;
429
          }
430
          if (   ($this->_isArchive($v_header['filename']))
431
		      && ($v_header['typeflag'] == "5")) {
432
            $this->_error('Directory '.$v_header['filename']
433
			              .' already exists as a file');
434
            return false;
435
          }
436
          if (!is_writeable($v_header['filename'])) {
437
            $this->_error('File '.$v_header['filename']
438
			              .' already exists and is write protected');
439
            return false;
440
          }
441
          if (filemtime($v_header['filename']) > $v_header['mtime']) {
442
            // To be completed : An error or silent no replace ?
443
          }
444
        }
445
 
446
        // ----- Check the directory availability and create it if necessary
447
        elseif (($v_result
448
		         = $this->_dirCheck(($v_header['typeflag'] == "5"
449
				                    ?$v_header['filename']
450
									:dirname($v_header['filename'])))) != 1) {
451
            $this->_error('Unable to create path for '.$v_header['filename']);
452
            return false;
453
        }
454
 
455
        if ($v_extract_file) {
456
          if ($v_header['typeflag'] == "5") {
457
            if (!@file_exists($v_header['filename'])) {
458
                if (!@mkdir($v_header['filename'], PRADO_CHMOD)) {
459
                    $this->_error('Unable to create directory {'
460
					              .$v_header['filename'].'}');
461
                    return false;
462
                }
463
                chmod($v_header['filename'], PRADO_CHMOD);
464
            }
465
          } else {
466
              if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
467
                  $this->_error('Error while opening {'.$v_header['filename']
468
				                .'} in write binary mode');
469
                  return false;
470
              } else {
471
                  $n = floor($v_header['size']/512);
472
                  for ($i=0; $i<$n; $i++) {
473
                      $v_content = $this->_readBlock();
474
                      fwrite($v_dest_file, $v_content, 512);
475
                  }
476
            if (($v_header['size'] % 512) != 0) {
477
              $v_content = $this->_readBlock();
478
              fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
479
            }
480
 
481
            @fclose($v_dest_file);
482
 
483
            // ----- Change the file mode, mtime
484
            @touch($v_header['filename'], $v_header['mtime']);
485
            // To be completed
486
            //chmod($v_header[filename], DecOct($v_header[mode]));
487
          }
488
 
489
          // ----- Check the file size
490
          clearstatcache();
491
          if (filesize($v_header['filename']) != $v_header['size']) {
492
              $this->_error('Extracted file '.$v_header['filename']
493
			                .' does not have the correct file size \''
494
							.filesize($v_header['filename'])
495
							.'\' ('.$v_header['size']
496
							.' expected). Archive may be corrupted.');
497
              return false;
498
          }
499
          }
500
        } else {
501
          $this->_jumpBlock(ceil(($v_header['size']/512)));
502
        }
503
      } else {
504
          $this->_jumpBlock(ceil(($v_header['size']/512)));
505
      }
506
 
507
      /* TBC : Seems to be unused ...
508
      if ($this->_compress)
509
        $v_end_of_file = @gzeof($this->_file);
510
      else
511
        $v_end_of_file = @feof($this->_file);
512
        */
513
 
514
      if ($v_listing || $v_extract_file || $v_extraction_stopped) {
515
        // ----- Log extracted files
516
        if (($v_file_dir = dirname($v_header['filename']))
517
		    == $v_header['filename'])
518
          $v_file_dir = '';
519
        if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
520
          $v_file_dir = '/';
521
 
522
        $p_list_detail[$v_nb++] = $v_header;
523
      }
524
    }
525
 
526
        return true;
527
    }
528
 
529
    /**
530
     * Check if a directory exists and create it (including parent
531
     * dirs) if not.
532
     *
533
     * @param string $p_dir directory to check
534
     *
535
     * @return bool true if the directory exists or was created
536
     */
537
    protected function _dirCheck($p_dir)
538
    {
539
        if ((@is_dir($p_dir)) || ($p_dir == ''))
540
            return true;
541
 
542
        $p_parent_dir = dirname($p_dir);
543
 
544
        if (($p_parent_dir != $p_dir) &&
545
            ($p_parent_dir != '') &&
546
            (!$this->_dirCheck($p_parent_dir)))
547
             return false;
548
 
549
        if (!@mkdir($p_dir, PRADO_CHMOD)) {
550
            $this->_error("Unable to create directory '$p_dir'");
551
            return false;
552
        }
553
        chmod($p_dir,PRADO_CHMOD);
554
 
555
        return true;
556
    }
557
 
558
    protected function _translateWinPath($p_path, $p_remove_disk_letter=true)
559
    {
560
      if (substr(PHP_OS, 0, 3) == 'WIN') {
561
          // ----- Look for potential disk letter
562
          if (   ($p_remove_disk_letter)
563
		      && (($v_position = strpos($p_path, ':')) != false)) {
564
              $p_path = substr($p_path, $v_position+1);
565
          }
566
          // ----- Change potential windows directory separator
567
          if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
568
              $p_path = strtr($p_path, '\\', '/');
569
          }
570
      }
571
      return $p_path;
572
    }
573
}
574
?>