Issues (3083)

htdocs/class/class.tar.php (1 issue)

1
<?php
2
// 
3
4
/**
5
 * package::i.tools
6
 *
7
 * php-downloader    v1.0    -    www.ipunkt.biz
8
 *
9
 * (c)    2002 - www.ipunkt.biz (rok)
10
 */
11
/**
12
 * =======================================================================
13
 * Name:
14
 * tar Class
15
 *
16
 * Author:
17
 * Josh Barger <[email protected]>
18
 *
19
 * Description:
20
 * This class reads and writes Tape-Archive (TAR) Files and Gzip
21
 * compressed TAR files, which are mainly used on UNIX systems.
22
 * This class works on both windows AND unix systems, and does
23
 * NOT rely on external applications!! Woohoo!
24
 *
25
 * Usage:
26
 * Copyright (C) 2002  Josh Barger
27
 *
28
 * This library is free software; you can redistribute it and/or
29
 * modify it under the terms of the GNU Lesser General Public
30
 * License as published by the Free Software Foundation; either
31
 * version 2.1 of the License, or (at your option) any later version.
32
 *
33
 * This library is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
36
 * Lesser General Public License for more details at:
37
 * https://www.gnu.org/copyleft/lesser.html
38
 *
39
 * If you use this script in your application/website, please
40
 * send me an e-mail letting me know about it :)
41
 *
42
 * Bugs:
43
 * Please report any bugs you might find to my e-mail address
44
 * at [email protected].  If you have already created a fix/patch
45
 * for the bug, please do send it to me so I can incorporate it into my release.
46
 *
47
 * Version History:
48
 * 1.0    04/10/2002    - InitialRelease
49
 *
50
 * 2.0    04/11/2002    - Merged both tarReader and tarWriter
51
 * classes into one
52
 * - Added support for gzipped tar files
53
 * Remember to name for .tar.gz or .tgz
54
 * if you use gzip compression!
55
 * :: THIS REQUIRES ZLIB EXTENSION ::
56
 * - Added additional comments to
57
 * functions to help users
58
 * - Added ability to remove files and
59
 * directories from archive
60
 * 2.1    04/12/2002    - Fixed serious bug in generating tar
61
 * - Created another example file
62
 * - Added check to make sure ZLIB is
63
 * installed before running GZIP
64
 * compression on TAR
65
 * 2.2    05/07/2002    - Added automatic detection of Gzipped
66
 * tar files (Thanks go to Jidgen Falch
67
 * for the idea)
68
 * - Changed "private" functions to have
69
 * special function names beginning with
70
 * two underscores
71
 * =======================================================================
72
 * XOOPS changes onokazu <[email protected]>
73
 *
74
 * 12/25/2002 - Added flag to addFile() function for binary files
75
 *
76
 * =======================================================================
77
 */
78
79
/**
80
 * tar Class
81
 *
82
 * This class reads and writes Tape-Archive (TAR) Files and Gzip
83
 * compressed TAR files, which are mainly used on UNIX systems.
84
 * This class works on both windows AND unix systems, and does
85
 * NOT rely on external applications!! Woohoo!
86
 *
87
 * @author     Josh Barger <[email protected]>
88
 * @copyright  Copyright (C) 2002  Josh Barger
89
 * @package    kernel
90
 * @subpackage core
91
 */
92
class Tar
93
{
94
    /**
95
     * *#@+
96
     * Unprocessed Archive Information
97
     */
98
    public $filename;
99
    public $isGzipped;
100
    public $tar_file;
101
    /**
102
     * *#@-
103
     */
104
105
    /**
106
     * *#@+
107
     * Processed Archive Information
108
     */
109
    public $files;
110
    public $directories;
111
    public $numFiles;
112
    public $numDirectories;
113
    /**
114
     * *#@-
115
     */
116
117
    /**
118
     * Class Constructor -- Does nothing...
119
     */
120
    public function __construct()
121
    {
122
    }
123
124
    /**
125
     * Computes the unsigned Checksum of a file's header
126
     * to try to ensure valid file
127
     *
128
     * @param string $bytestring
129
     *
130
     * @return int|string
131
     * @access private
132
     */
133
    public function __computeUnsignedChecksum($bytestring)
134
    {
135
        $unsigned_chksum = '';
136
        for ($i = 0; $i < 512; ++$i) {
137
            $unsigned_chksum += ord($bytestring[$i]);
138
        }
139
        for ($i = 0; $i < 8; ++$i) {
140
            $unsigned_chksum -= ord($bytestring[148 + $i]);
141
            $unsigned_chksum += ord(' ') * 8;
142
        }
143
144
        return $unsigned_chksum;
145
    }
146
147
    /**
148
     * Converts a NULL padded string to a non-NULL padded string
149
     *
150
     * @param  string $string
151
     * @return string
152
     * @access private
153
     */
154
    public function __parseNullPaddedString($string)
155
    {
156
        $position = strpos($string, chr(0));
157
158
        return substr($string, 0, $position);
159
    }
160
161
    /**
162
     * This function parses the current TAR file
163
     *
164
     * @return bool always TRUE
165
     * @access private
166
     */
167
    public function __parseTar()
168
    {
169
        // Read Files from archive
170
        $tar_length     = strlen($this->tar_file);
171
        $main_offset    = 0;
172
        $this->numFiles = 0;
173
        while ($main_offset < $tar_length) {
174
            // If we read a block of 512 nulls, we are at the end of the archive
175
            if (substr($this->tar_file, $main_offset, 512) == str_repeat(chr(0), 512)) {
176
                break;
177
            }
178
            // Parse file name
179
            $file_name = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset, 100));
180
            // Parse the file mode
181
            $file_mode = substr($this->tar_file, $main_offset + 100, 8);
182
            // Parse the file user ID
183
            $file_uid = octdec(substr($this->tar_file, $main_offset + 108, 8));
184
            // Parse the file group ID
185
            $file_gid = octdec(substr($this->tar_file, $main_offset + 116, 8));
186
            // Parse the file size
187
            $file_size = octdec(substr($this->tar_file, $main_offset + 124, 12));
188
            // Parse the file update time - unix timestamp format
189
            $file_time = octdec(substr($this->tar_file, $main_offset + 136, 12));
190
            // Parse Checksum
191
            $file_chksum = octdec(substr($this->tar_file, $main_offset + 148, 6));
192
            // Parse user name
193
            $file_uname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 265, 32));
194
            // Parse Group name
195
            $file_gname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 297, 32));
196
            // Make sure our file is valid
197
            if ($this->__computeUnsignedChecksum(substr($this->tar_file, $main_offset, 512)) != $file_chksum) {
198
                return false;
199
            }
200
            // Parse File Contents
201
            $file_contents = substr($this->tar_file, $main_offset + 512, $file_size);
0 ignored issues
show
It seems like $file_size can also be of type double; however, parameter $length of substr() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

201
            $file_contents = substr($this->tar_file, $main_offset + 512, /** @scrutinizer ignore-type */ $file_size);
Loading history...
202
203
            /**
204
             * ### Unused Header Information ###
205
             * $activeFile["typeflag"]        = substr($this->tar_file,$main_offset + 156,1);
206
             * $activeFile["linkname"]        = substr($this->tar_file,$main_offset + 157,100);
207
             * $activeFile["magic"]        = substr($this->tar_file,$main_offset + 257,6);
208
             * $activeFile["version"]        = substr($this->tar_file,$main_offset + 263,2);
209
             * $activeFile["devmajor"]        = substr($this->tar_file,$main_offset + 329,8);
210
             * $activeFile["devminor"]        = substr($this->tar_file,$main_offset + 337,8);
211
             * $activeFile["prefix"]        = substr($this->tar_file,$main_offset + 345,155);
212
             * $activeFile["endheader"]    = substr($this->tar_file,$main_offset + 500,12);
213
             */
214
215
            if ($file_size > 0) {
216
                // Increment number of files
217
                $this->numFiles++;
218
                // Create us a new file in our array
219
                $activeFile =& $this->files[];
220
                // Asign Values
221
                $activeFile['name']       = $file_name;
222
                $activeFile['mode']       = $file_mode;
223
                $activeFile['size']       = $file_size;
224
                $activeFile['time']       = $file_time;
225
                $activeFile['user_id']    = $file_uid;
226
                $activeFile['group_id']   = $file_gid;
227
                $activeFile['user_name']  = $file_uname;
228
                $activeFile['group_name'] = $file_gname;
229
                $activeFile['checksum']   = $file_chksum;
230
                $activeFile['file']       = $file_contents;
231
            } else {
232
                // Increment number of directories
233
                $this->numDirectories++;
234
                // Create a new directory in our array
235
                $activeDir =& $this->directories[];
236
                // Assign values
237
                $activeDir['name']       = $file_name;
238
                $activeDir['mode']       = $file_mode;
239
                $activeDir['time']       = $file_time;
240
                $activeDir['user_id']    = $file_uid;
241
                $activeDir['group_id']   = $file_gid;
242
                $activeDir['user_name']  = $file_uname;
243
                $activeDir['group_name'] = $file_gname;
244
                $activeDir['checksum']   = $file_chksum;
245
            }
246
            // Move our offset the number of blocks we have processed
247
            $main_offset += 512 + (ceil($file_size / 512) * 512);
248
        }
249
250
        return true;
251
    }
252
253
    /**
254
     * Read a non gzipped tar file in for processing.
255
     *
256
     * @param  string $filename full filename
257
     * @return bool   always TRUE
258
     * @access private
259
     */
260
    public function __readTar($filename = '')
261
    {
262
        // Set the filename to load
263
        if (!$filename) {
264
            $filename = $this->filename;
265
        }
266
        // Read in the TAR file
267
        $fp             = fopen($filename, 'rb');
268
        $this->tar_file = fread($fp, filesize($filename));
269
        fclose($fp);
270
271
        if ($this->tar_file[0] == chr(31) && $this->tar_file[1] == chr(139) && $this->tar_file[2] == chr(8)) {
272
            if (!function_exists('gzinflate')) {
273
                return false;
274
            }
275
            $this->isGzipped = true;
276
            $this->tar_file  = gzinflate(substr($this->tar_file, 10, -4));
277
        }
278
        // Parse the TAR file
279
        $this->__parseTar();
280
281
        return true;
282
    }
283
284
    /**
285
     * Generates a TAR file from the processed data
286
     *
287
     * @return bool always TRUE
288
     * @access private
289
     */
290
    public function __generateTar()
291
    {
292
        // Clear any data currently in $this->tar_file
293
        unset($this->tar_file);
294
        // Generate Records for each directory, if we have directories
295
        if ($this->numDirectories > 0) {
296
            foreach ($this->directories as $key => $information) {
297
                unset($header);
298
                // Generate tar header for this directory
299
                // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
300
                $header .= str_pad($information['name'], 100, chr(0));
301
                $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0);
302
                $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0);
303
                $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0);
304
                $header .= str_pad(decoct(0), 11, '0', STR_PAD_LEFT) . chr(0);
305
                $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0);
306
                $header .= str_repeat(' ', 8);
307
                $header .= '5';
308
                $header .= str_repeat(chr(0), 100);
309
                $header .= str_pad('ustar', 6, chr(32));
310
                $header .= chr(32) . chr(0);
311
                $header .= str_pad('', 32, chr(0));
312
                $header .= str_pad('', 32, chr(0));
313
                $header .= str_repeat(chr(0), 8);
314
                $header .= str_repeat(chr(0), 8);
315
                $header .= str_repeat(chr(0), 155);
316
                $header .= str_repeat(chr(0), 12);
317
                // Compute header checksum
318
                $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT);
319
                for ($i = 0; $i < 6; ++$i) {
320
                    $header[148 + $i] = substr($checksum, $i, 1);
321
                }
322
                $header[154] = chr(0);
323
                $header[155] = chr(32);
324
                // Add new tar formatted data to tar file contents
325
                $this->tar_file .= $header;
326
            }
327
        }
328
        // Generate Records for each file, if we have files (We should...)
329
        if ($this->numFiles > 0) {
330
            $this->tar_file = '';
331
            foreach ($this->files as $key => $information) {
332
                unset($header);
333
                // Generate the TAR header for this file
334
                // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
335
                $header = str_pad($information['name'], 100, chr(0));
336
                $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0);
337
                $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0);
338
                $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0);
339
                $header .= str_pad(decoct($information['size']), 11, '0', STR_PAD_LEFT) . chr(0);
340
                $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0);
341
                $header .= str_repeat(' ', 8);
342
                $header .= '0';
343
                $header .= str_repeat(chr(0), 100);
344
                $header .= str_pad('ustar', 6, chr(32));
345
                $header .= chr(32) . chr(0);
346
                $header .= str_pad($information['user_name'], 32, chr(0)); // How do I get a file's user name from PHP?
347
                $header .= str_pad($information['group_name'], 32, chr(0)); // How do I get a file's group name from PHP?
348
                $header .= str_repeat(chr(0), 8);
349
                $header .= str_repeat(chr(0), 8);
350
                $header .= str_repeat(chr(0), 155);
351
                $header .= str_repeat(chr(0), 12);
352
                // Compute header checksum
353
                $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT);
354
                for ($i = 0; $i < 6; ++$i) {
355
                    $header[148 + $i] = substr($checksum, $i, 1);
356
                }
357
                $header[154] = chr(0);
358
                $header[155] = chr(32);
359
                // Pad file contents to byte count divisible by 512
360
                $file_contents = str_pad($information['file'], ceil($information['size'] / 512) * 512, chr(0));
361
                // Add new tar formatted data to tar file contents
362
                $this->tar_file .= $header . $file_contents;
363
            }
364
        }
365
        // Add 512 bytes of NULLs to designate EOF
366
        $this->tar_file .= str_repeat(chr(0), 512);
367
368
        return true;
369
    }
370
371
    /**
372
     * Open a TAR file
373
     *
374
     * @param  string $filename
375
     * @return bool
376
     */
377
    public function openTAR($filename)
378
    {
379
        // Clear any values from previous tar archives
380
        unset($this->filename, $this->isGzipped, $this->tar_file, $this->files, $this->directories, $this->numFiles, $this->numDirectories);
381
382
        // If the tar file doesn't exist...
383
        if (!file_exists($filename)) {
384
            return false;
385
        }
386
387
        $this->filename = $filename;
388
        // Parse this file
389
        $this->__readTar();
390
391
        return true;
392
    }
393
394
    /**
395
     * Appends a tar file to the end of the currently opened tar file.
396
     *
397
     * @param  string $filename
398
     * @return bool
399
     */
400
    public function appendTar($filename)
401
    {
402
        // If the tar file doesn't exist...
403
        if (!file_exists($filename)) {
404
            return false;
405
        }
406
        $this->__readTar($filename);
407
408
        return true;
409
    }
410
411
    /**
412
     * Retrieves information about a file in the current tar archive
413
     *
414
     * @param  string $filename
415
     * @return string|false FALSE on fail
416
     */
417
    public function getFile($filename)
418
    {
419
        if ($this->numFiles > 0) {
420
            foreach ($this->files as $key => $information) {
421
                if ($information['name'] == $filename) {
422
                    return $information;
423
                }
424
            }
425
        }
426
427
        return false;
428
    }
429
430
    /**
431
     * Retrieves information about a directory in the current tar archive
432
     *
433
     * @param  string $dirname
434
     * @return string|false FALSE on fail
435
     */
436
    public function getDirectory($dirname)
437
    {
438
        if ($this->numDirectories > 0) {
439
            foreach ($this->directories as $key => $information) {
440
                if ($information['name'] == $dirname) {
441
                    return $information;
442
                }
443
            }
444
        }
445
446
        return false;
447
    }
448
449
    /**
450
     * Check if this tar archive contains a specific file
451
     *
452
     * @param  string $filename
453
     * @return bool
454
     */
455
    public function containsFile($filename)
456
    {
457
        if ($this->numFiles > 0) {
458
            foreach ($this->files as $key => $information) {
459
                if ($information['name'] == $filename) {
460
                    return true;
461
                }
462
            }
463
        }
464
465
        return false;
466
    }
467
468
    /**
469
     * Check if this tar archive contains a specific directory
470
     *
471
     * @param  string $dirname
472
     * @return bool
473
     */
474
    public function containsDirectory($dirname)
475
    {
476
        if ($this->numDirectories > 0) {
477
            foreach ($this->directories as $key => $information) {
478
                if ($information['name'] == $dirname) {
479
                    return true;
480
                }
481
            }
482
        }
483
484
        return false;
485
    }
486
487
    /**
488
     * Add a directory to this tar archive
489
     *
490
     * @param  string $dirname
491
     * @return bool
492
     */
493
    public function addDirectory($dirname)
494
    {
495
        if (!file_exists($dirname)) {
496
            return false;
497
        }
498
        // Get directory information
499
        $file_information = stat($dirname);
500
        // Add directory to processed data
501
        $this->numDirectories++;
502
        $activeDir             =& $this->directories[];
503
        $activeDir['name']     = $dirname;
504
        $activeDir['mode']     = $file_information['mode'];
505
        $activeDir['time']     = $file_information['time'];
506
        $activeDir['user_id']  = $file_information['uid'];
507
        $activeDir['group_id'] = $file_information['gid'];
508
        $activeDir['checksum'] = $checksum;
509
510
        return true;
511
    }
512
513
    /**
514
     * Add a file to the tar archive
515
     *
516
     * @param  string  $filename
517
     * @param  boolean $binary Binary file?
518
     * @return bool
519
     */
520
    public function addFile($filename, $binary = false)
521
    {
522
        // Make sure the file we are adding exists!
523
        if (!file_exists($filename)) {
524
            return false;
525
        }
526
        // Make sure there are no other files in the archive that have this same filename
527
        if ($this->containsFile($filename)) {
528
            return false;
529
        }
530
        // Get file information
531
        $file_information = stat($filename);
532
        // Read in the file's contents
533
        if (!$binary) {
534
            $fp = fopen($filename, 'r');
535
        } else {
536
            $fp = fopen($filename, 'rb');
537
        }
538
        $file_contents = fread($fp, filesize($filename));
539
        fclose($fp);
540
        // Add file to processed data
541
        $this->numFiles++;
542
        $activeFile               =& $this->files[];
543
        $activeFile['name']       = $filename;
544
        $activeFile['mode']       = $file_information['mode'];
545
        $activeFile['user_id']    = $file_information['uid'];
546
        $activeFile['group_id']   = $file_information['gid'];
547
        $activeFile['size']       = $file_information['size'];
548
        $activeFile['time']       = $file_information['mtime'];
549
        $activeFile['checksum']   = isset($checksum) ? $checksum : '';
550
        $activeFile['user_name']  = '';
551
        $activeFile['group_name'] = '';
552
        $activeFile['file']       = trim($file_contents);
553
554
        return true;
555
    }
556
557
    /**
558
     * Remove a file from the tar archive
559
     *
560
     * @param  string $filename
561
     * @return bool
562
     */
563
    public function removeFile($filename)
564
    {
565
        if ($this->numFiles > 0) {
566
            foreach ($this->files as $key => $information) {
567
                if ($information['name'] == $filename) {
568
                    $this->numFiles--;
569
                    unset($this->files[$key]);
570
571
                    return true;
572
                }
573
            }
574
        }
575
576
        return false;
577
    }
578
579
    /**
580
     * Remove a directory from the tar archive
581
     *
582
     * @param  string $dirname
583
     * @return bool
584
     */
585
    public function removeDirectory($dirname)
586
    {
587
        if ($this->numDirectories > 0) {
588
            foreach ($this->directories as $key => $information) {
589
                if ($information['name'] == $dirname) {
590
                    $this->numDirectories--;
591
                    unset($this->directories[$key]);
592
593
                    return true;
594
                }
595
            }
596
        }
597
598
        return false;
599
    }
600
601
    /**
602
     * Write the currently loaded tar archive to disk
603
     *
604
     * @return bool
605
     */
606
    public function saveTar()
607
    {
608
        if (!$this->filename) {
609
            return false;
610
        }
611
        // Write tar to current file using specified gzip compression
612
        $this->toTar($this->filename, $this->isGzipped);
613
614
        return true;
615
    }
616
617
    /**
618
     * Saves tar archive to a different file than the current file
619
     *
620
     * @param  string $filename
621
     * @param  bool   $useGzip Use GZ compression?
622
     * @return bool
623
     */
624
    public function toTar($filename, $useGzip)
625
    {
626
        if (!$filename) {
627
            return false;
628
        }
629
        // Encode processed files into TAR file format
630
        $this->__generateTar();
631
        // GZ Compress the data if we need to
632
        if ($useGzip) {
633
            // Make sure we have gzip support
634
            if (!function_exists('gzencode')) {
635
                return false;
636
            }
637
            $file = gzencode($this->tar_file);
638
        } else {
639
            $file = $this->tar_file;
640
        }
641
        // Write the TAR file
642
        $fp = fopen($filename, 'wb');
643
        fwrite($fp, $file);
644
        fclose($fp);
645
646
        return true;
647
    }
648
649
    /**
650
     * Sends tar archive to stdout
651
     *
652
     * @param  string $filename
653
     * @param  bool   $useGzip Use GZ compression?
654
     * @return string|false
655
     */
656
    public function toTarOutput($filename, $useGzip)
657
    {
658
        if (!$filename) {
659
            return false;
660
        }
661
        // Encode processed files into TAR file format
662
        $this->__generateTar();
663
        // GZ Compress the data if we need to
664
        if ($useGzip) {
665
            // Make sure we have gzip support
666
            if (!function_exists('gzencode')) {
667
                return false;
668
            }
669
            $file = gzencode($this->tar_file);
670
        } else {
671
            $file = $this->tar_file;
672
        }
673
674
        return $file;
675
    }
676
}
677