Issues (2268)

htdocs/class/class.tar.php (3 issues)

1
<?php
2
3
//
4
5
/**
6
 * package::i.tools
7
 *
8
 * php-downloader    v1.0    -    www.ipunkt.biz
9
 *
10
 * (c)    2002 - www.ipunkt.biz (rok)
11
 */
12
/**
13
 * =======================================================================
14
 * Name:
15
 * tar Class
16
 *
17
 * Author:
18
 * Josh Barger <[email protected]>
19
 *
20
 * Description:
21
 * This class reads and writes Tape-Archive (TAR) Files and Gzip
22
 * compressed TAR files, which are mainly used on UNIX systems.
23
 * This class works on both windows AND unix systems, and does
24
 * NOT rely on external applications!! Woohoo!
25
 *
26
 * Usage:
27
 * Copyright (C) 2002  Josh Barger
28
 *
29
 * This library is free software; you can redistribute it and/or
30
 * modify it under the terms of the GNU Lesser General Public
31
 * License as published by the Free Software Foundation; either
32
 * version 2.1 of the License, or (at your option) any later version.
33
 *
34
 * This library is distributed in the hope that it will be useful,
35
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
37
 * Lesser General Public License for more details at:
38
 * https://www.gnu.org/copyleft/lesser.html
39
 *
40
 * If you use this script in your application/website, please
41
 * send me an e-mail letting me know about it :)
42
 *
43
 * Bugs:
44
 * Please report any bugs you might find to my e-mail address
45
 * at [email protected].  If you have already created a fix/patch
46
 * for the bug, please do send it to me so I can incorporate it into my release.
47
 *
48
 * Version History:
49
 * 1.0    04/10/2002    - InitialRelease
50
 *
51
 * 2.0    04/11/2002    - Merged both tarReader and tarWriter
52
 * classes into one
53
 * - Added support for gzipped tar files
54
 * Remember to name for .tar.gz or .tgz
55
 * if you use gzip compression!
56
 * :: THIS REQUIRES ZLIB EXTENSION ::
57
 * - Added additional comments to
58
 * functions to help users
59
 * - Added ability to remove files and
60
 * directories from archive
61
 * 2.1    04/12/2002    - Fixed serious bug in generating tar
62
 * - Created another example file
63
 * - Added check to make sure ZLIB is
64
 * installed before running GZIP
65
 * compression on TAR
66
 * 2.2    05/07/2002    - Added automatic detection of Gzipped
67
 * tar files (Thanks go to Jidgen Falch
68
 * for the idea)
69
 * - Changed "private" functions to have
70
 * special function names beginning with
71
 * two underscores
72
 * =======================================================================
73
 * XOOPS changes onokazu <[email protected]>
74
 *
75
 * 12/25/2002 - Added flag to addFile() function for binary files
76
 *
77
 * =======================================================================
78
 */
79
80
/**
81
 * tar Class
82
 *
83
 * This class reads and writes Tape-Archive (TAR) Files and Gzip
84
 * compressed TAR files, which are mainly used on UNIX systems.
85
 * This class works on both windows AND unix systems, and does
86
 * NOT rely on external applications!! Woohoo!
87
 *
88
 * @author     Josh Barger <[email protected]>
89
 * @copyright  Copyright (C) 2002  Josh Barger
90
 * @package    kernel
91
 * @subpackage core
92
 */
93
class Tar
94
{
95
    /**
96
     * *#@+
97
     * Unprocessed Archive Information
98
     */
99
    public $filename;
100
    public $isGzipped;
101
    public $tar_file;
102
    /**
103
     * *#@-
104
     */
105
106
    /**
107
     * *#@+
108
     * Processed Archive Information
109
     */
110
    public $files;
111
    public $directories;
112
    public $numFiles;
113
    public $numDirectories;
114
    /**
115
     * *#@-
116
     */
117
118
    /**
119
     * Class Constructor -- Does nothing...
120
     */
121
    public function __construct() {}
122
123
    /**
124
     * Computes the unsigned Checksum of a file's header
125
     * to try to ensure valid file
126
     *
127
     * @param string $bytestring
128
     *
129
     * @return int|string
130
     * @access private
131
     */
132
    public function __computeUnsignedChecksum($bytestring)
133
    {
134
        $unsigned_chksum = '';
135
        for ($i = 0; $i < 512; ++$i) {
136
            $unsigned_chksum += ord($bytestring[$i]);
137
        }
138
        for ($i = 0; $i < 8; ++$i) {
139
            $unsigned_chksum -= ord($bytestring[148 + $i]);
140
            $unsigned_chksum += ord(' ') * 8;
141
        }
142
143
        return $unsigned_chksum;
144
    }
145
146
    /**
147
     * Converts a NULL padded string to a non-NULL padded string
148
     *
149
     * @param  string $string
150
     * @return string
151
     * @access private
152
     */
153
    public function __parseNullPaddedString($string)
154
    {
155
        $position = strpos($string, chr(0));
156
157
        return substr($string, 0, $position);
158
    }
159
160
    /**
161
     * This function parses the current TAR file
162
     *
163
     * @return bool always TRUE
164
     * @access private
165
     */
166
    public function __parseTar()
167
    {
168
        // Read Files from archive
169
        $tar_length     = strlen($this->tar_file);
170
        $main_offset    = 0;
171
        $this->numFiles = 0;
172
        while ($main_offset < $tar_length) {
173
            // If we read a block of 512 nulls, we are at the end of the archive
174
            if (substr($this->tar_file, $main_offset, 512) == str_repeat(chr(0), 512)) {
175
                break;
176
            }
177
            // Parse file name
178
            $file_name = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset, 100));
179
            // Parse the file mode
180
            $file_mode = substr($this->tar_file, $main_offset + 100, 8);
181
            // Parse the file user ID
182
            $file_uid = octdec(substr($this->tar_file, $main_offset + 108, 8));
183
            // Parse the file group ID
184
            $file_gid = octdec(substr($this->tar_file, $main_offset + 116, 8));
185
            // Parse the file size
186
            $file_size = octdec(substr($this->tar_file, $main_offset + 124, 12));
187
            // Parse the file update time - unix timestamp format
188
            $file_time = octdec(substr($this->tar_file, $main_offset + 136, 12));
189
            // Parse Checksum
190
            $file_chksum = octdec(substr($this->tar_file, $main_offset + 148, 6));
191
            // Parse user name
192
            $file_uname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 265, 32));
193
            // Parse Group name
194
            $file_gname = $this->__parseNullPaddedString(substr($this->tar_file, $main_offset + 297, 32));
195
            // Make sure our file is valid
196
            if ($this->__computeUnsignedChecksum(substr($this->tar_file, $main_offset, 512)) != $file_chksum) {
197
                return false;
198
            }
199
            // Parse File Contents
200
            $file_contents = substr($this->tar_file, $main_offset + 512, $file_size);
201
202
            /**
203
             * ### Unused Header Information ###
204
             * $activeFile["typeflag"]        = substr($this->tar_file,$main_offset + 156,1);
205
             * $activeFile["linkname"]        = substr($this->tar_file,$main_offset + 157,100);
206
             * $activeFile["magic"]        = substr($this->tar_file,$main_offset + 257,6);
207
             * $activeFile["version"]        = substr($this->tar_file,$main_offset + 263,2);
208
             * $activeFile["devmajor"]        = substr($this->tar_file,$main_offset + 329,8);
209
             * $activeFile["devminor"]        = substr($this->tar_file,$main_offset + 337,8);
210
             * $activeFile["prefix"]        = substr($this->tar_file,$main_offset + 345,155);
211
             * $activeFile["endheader"]    = substr($this->tar_file,$main_offset + 500,12);
212
             */
213
214
            if ($file_size > 0) {
215
                // Increment number of files
216
                $this->numFiles++;
217
                // Create us a new file in our array
218
                $activeFile = & $this->files[];
219
                // Asign Values
220
                $activeFile['name']       = $file_name;
221
                $activeFile['mode']       = $file_mode;
222
                $activeFile['size']       = $file_size;
223
                $activeFile['time']       = $file_time;
224
                $activeFile['user_id']    = $file_uid;
225
                $activeFile['group_id']   = $file_gid;
226
                $activeFile['user_name']  = $file_uname;
227
                $activeFile['group_name'] = $file_gname;
228
                $activeFile['checksum']   = $file_chksum;
229
                $activeFile['file']       = $file_contents;
230
            } else {
231
                // Increment number of directories
232
                $this->numDirectories++;
233
                // Create a new directory in our array
234
                $activeDir = & $this->directories[];
235
                // Assign values
236
                $activeDir['name']       = $file_name;
237
                $activeDir['mode']       = $file_mode;
238
                $activeDir['time']       = $file_time;
239
                $activeDir['user_id']    = $file_uid;
240
                $activeDir['group_id']   = $file_gid;
241
                $activeDir['user_name']  = $file_uname;
242
                $activeDir['group_name'] = $file_gname;
243
                $activeDir['checksum']   = $file_chksum;
244
            }
245
            // Move our offset the number of blocks we have processed
246
            $main_offset += 512 + (ceil($file_size / 512) * 512);
247
        }
248
249
        return true;
250
    }
251
252
    /**
253
     * Read a non gzipped tar file in for processing.
254
     *
255
     * @param  string $filename full filename
256
     * @return bool   always TRUE
257
     * @access private
258
     */
259
    public function __readTar($filename = '')
260
    {
261
        // Set the filename to load
262
        if (!$filename) {
263
            $filename = $this->filename;
264
        }
265
        // Read in the TAR file
266
        $fp             = fopen($filename, 'rb');
267
        $this->tar_file = fread($fp, filesize($filename));
268
        fclose($fp);
269
270
        if ($this->tar_file[0] == chr(31) && $this->tar_file[1] == chr(139) && $this->tar_file[2] == chr(8)) {
271
            if (!function_exists('gzinflate')) {
272
                return false;
273
            }
274
            $this->isGzipped = true;
275
            $this->tar_file  = gzinflate(substr($this->tar_file, 10, -4));
276
        }
277
        // Parse the TAR file
278
        $this->__parseTar();
279
280
        return true;
281
    }
282
283
    /**
284
     * Generates a TAR file from the processed data
285
     *
286
     * @return bool always TRUE
287
     * @access private
288
     */
289
    public function __generateTar()
290
    {
291
        // Clear any data currently in $this->tar_file
292
        unset($this->tar_file);
293
        // Generate Records for each directory, if we have directories
294
        if ($this->numDirectories > 0) {
295
            foreach ($this->directories as $key => $information) {
296
                unset($header);
297
                // Generate tar header for this directory
298
                // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
299
                $header .= str_pad($information['name'], 100, chr(0));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $header seems to be defined later in this foreach loop on line 319. Are you sure it is defined here?
Loading history...
300
                $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0);
301
                $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0);
302
                $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0);
303
                $header .= str_pad(decoct(0), 11, '0', STR_PAD_LEFT) . chr(0);
304
                $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0);
305
                $header .= str_repeat(' ', 8);
306
                $header .= '5';
307
                $header .= str_repeat(chr(0), 100);
308
                $header .= str_pad('ustar', 6, chr(32));
309
                $header .= chr(32) . chr(0);
310
                $header .= str_pad('', 32, chr(0));
311
                $header .= str_pad('', 32, chr(0));
312
                $header .= str_repeat(chr(0), 8);
313
                $header .= str_repeat(chr(0), 8);
314
                $header .= str_repeat(chr(0), 155);
315
                $header .= str_repeat(chr(0), 12);
316
                // Compute header checksum
317
                $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT);
318
                for ($i = 0; $i < 6; ++$i) {
319
                    $header[148 + $i] = substr($checksum, $i, 1);
320
                }
321
                $header[154] = chr(0);
322
                $header[155] = chr(32);
323
                // Add new tar formatted data to tar file contents
324
                $this->tar_file .= $header;
325
            }
326
        }
327
        // Generate Records for each file, if we have files (We should...)
328
        if ($this->numFiles > 0) {
329
            $this->tar_file = '';
330
            foreach ($this->files as $key => $information) {
331
                unset($header);
332
                // Generate the TAR header for this file
333
                // Filename, Permissions, UID, GID, size, Time, checksum, typeflag, linkname, magic, version, user name, group name, devmajor, devminor, prefix, end
334
                $header = str_pad($information['name'], 100, chr(0));
335
                $header .= str_pad(decoct($information['mode']), 7, '0', STR_PAD_LEFT) . chr(0);
336
                $header .= str_pad(decoct($information['user_id']), 7, '0', STR_PAD_LEFT) . chr(0);
337
                $header .= str_pad(decoct($information['group_id']), 7, '0', STR_PAD_LEFT) . chr(0);
338
                $header .= str_pad(decoct($information['size']), 11, '0', STR_PAD_LEFT) . chr(0);
339
                $header .= str_pad(decoct($information['time']), 11, '0', STR_PAD_LEFT) . chr(0);
340
                $header .= str_repeat(' ', 8);
341
                $header .= '0';
342
                $header .= str_repeat(chr(0), 100);
343
                $header .= str_pad('ustar', 6, chr(32));
344
                $header .= chr(32) . chr(0);
345
                $header .= str_pad($information['user_name'], 32, chr(0)); // How do I get a file's user name from PHP?
346
                $header .= str_pad($information['group_name'], 32, chr(0)); // How do I get a file's group name from PHP?
347
                $header .= str_repeat(chr(0), 8);
348
                $header .= str_repeat(chr(0), 8);
349
                $header .= str_repeat(chr(0), 155);
350
                $header .= str_repeat(chr(0), 12);
351
                // Compute header checksum
352
                $checksum = str_pad(decoct($this->__computeUnsignedChecksum($header)), 6, '0', STR_PAD_LEFT);
353
                for ($i = 0; $i < 6; ++$i) {
354
                    $header[148 + $i] = substr($checksum, $i, 1);
355
                }
356
                $header[154] = chr(0);
357
                $header[155] = chr(32);
358
                // Pad file contents to byte count divisible by 512
359
                $file_contents = str_pad($information['file'], ceil($information['size'] / 512) * 512, chr(0));
360
                // Add new tar formatted data to tar file contents
361
                $this->tar_file .= $header . $file_contents;
362
            }
363
        }
364
        // Add 512 bytes of NULLs to designate EOF
365
        $this->tar_file .= str_repeat(chr(0), 512);
366
367
        return true;
368
    }
369
370
    /**
371
     * Open a TAR file
372
     *
373
     * @param  string $filename
374
     * @return bool
375
     */
376
    public function openTAR($filename)
377
    {
378
        // Clear any values from previous tar archives
379
        unset($this->filename, $this->isGzipped, $this->tar_file, $this->files, $this->directories, $this->numFiles, $this->numDirectories);
380
381
        // If the tar file doesn't exist...
382
        if (!file_exists($filename)) {
383
            return false;
384
        }
385
386
        $this->filename = $filename;
387
        // Parse this file
388
        $this->__readTar();
389
390
        return true;
391
    }
392
393
    /**
394
     * Appends a tar file to the end of the currently opened tar file.
395
     *
396
     * @param  string $filename
397
     * @return bool
398
     */
399
    public function appendTar($filename)
400
    {
401
        // If the tar file doesn't exist...
402
        if (!file_exists($filename)) {
403
            return false;
404
        }
405
        $this->__readTar($filename);
406
407
        return true;
408
    }
409
410
    /**
411
     * Retrieves information about a file in the current tar archive
412
     *
413
     * @param  string $filename
414
     * @return string|false FALSE on fail
415
     */
416
    public function getFile($filename)
417
    {
418
        if ($this->numFiles > 0) {
419
            foreach ($this->files as $key => $information) {
420
                if ($information['name'] == $filename) {
421
                    return $information;
422
                }
423
            }
424
        }
425
426
        return false;
427
    }
428
429
    /**
430
     * Retrieves information about a directory in the current tar archive
431
     *
432
     * @param  string $dirname
433
     * @return string|false FALSE on fail
434
     */
435
    public function getDirectory($dirname)
436
    {
437
        if ($this->numDirectories > 0) {
438
            foreach ($this->directories as $key => $information) {
439
                if ($information['name'] == $dirname) {
440
                    return $information;
441
                }
442
            }
443
        }
444
445
        return false;
446
    }
447
448
    /**
449
     * Check if this tar archive contains a specific file
450
     *
451
     * @param  string $filename
452
     * @return bool
453
     */
454
    public function containsFile($filename)
455
    {
456
        if ($this->numFiles > 0) {
457
            foreach ($this->files as $key => $information) {
458
                if ($information['name'] == $filename) {
459
                    return true;
460
                }
461
            }
462
        }
463
464
        return false;
465
    }
466
467
    /**
468
     * Check if this tar archive contains a specific directory
469
     *
470
     * @param  string $dirname
471
     * @return bool
472
     */
473
    public function containsDirectory($dirname)
474
    {
475
        if ($this->numDirectories > 0) {
476
            foreach ($this->directories as $key => $information) {
477
                if ($information['name'] == $dirname) {
478
                    return true;
479
                }
480
            }
481
        }
482
483
        return false;
484
    }
485
486
    /**
487
     * Add a directory to this tar archive
488
     *
489
     * @param  string $dirname
490
     * @return bool
491
     */
492
    public function addDirectory($dirname)
493
    {
494
        if (!file_exists($dirname)) {
495
            return false;
496
        }
497
        // Get directory information
498
        $file_information = stat($dirname);
499
        // Add directory to processed data
500
        $this->numDirectories++;
501
        $activeDir             = & $this->directories[];
502
        $activeDir['name']     = $dirname;
503
        $activeDir['mode']     = $file_information['mode'];
504
        $activeDir['time']     = $file_information['time'];
505
        $activeDir['user_id']  = $file_information['uid'];
506
        $activeDir['group_id'] = $file_information['gid'];
507
        $activeDir['checksum'] = $checksum;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $checksum seems to be never defined.
Loading history...
508
509
        return true;
510
    }
511
512
    /**
513
     * Add a file to the tar archive
514
     *
515
     * @param  string  $filename
516
     * @param  boolean $binary Binary file?
517
     * @return bool
518
     */
519
    public function addFile($filename, $binary = false)
520
    {
521
        // Make sure the file we are adding exists!
522
        if (!file_exists($filename)) {
523
            return false;
524
        }
525
        // Make sure there are no other files in the archive that have this same filename
526
        if ($this->containsFile($filename)) {
527
            return false;
528
        }
529
        // Get file information
530
        $file_information = stat($filename);
531
        // Read in the file's contents
532
        if (!$binary) {
533
            $fp = fopen($filename, 'r');
534
        } else {
535
            $fp = fopen($filename, 'rb');
536
        }
537
        $file_contents = fread($fp, filesize($filename));
538
        fclose($fp);
539
        // Add file to processed data
540
        $this->numFiles++;
541
        $activeFile               = & $this->files[];
542
        $activeFile['name']       = $filename;
543
        $activeFile['mode']       = $file_information['mode'];
544
        $activeFile['user_id']    = $file_information['uid'];
545
        $activeFile['group_id']   = $file_information['gid'];
546
        $activeFile['size']       = $file_information['size'];
547
        $activeFile['time']       = $file_information['mtime'];
548
        $activeFile['checksum']   = $checksum ?? '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $checksum seems to never exist and therefore isset should always be false.
Loading history...
549
        $activeFile['user_name']  = '';
550
        $activeFile['group_name'] = '';
551
        $activeFile['file']       = trim($file_contents);
552
553
        return true;
554
    }
555
556
    /**
557
     * Remove a file from the tar archive
558
     *
559
     * @param  string $filename
560
     * @return bool
561
     */
562
    public function removeFile($filename)
563
    {
564
        if ($this->numFiles > 0) {
565
            foreach ($this->files as $key => $information) {
566
                if ($information['name'] == $filename) {
567
                    $this->numFiles--;
568
                    unset($this->files[$key]);
569
570
                    return true;
571
                }
572
            }
573
        }
574
575
        return false;
576
    }
577
578
    /**
579
     * Remove a directory from the tar archive
580
     *
581
     * @param  string $dirname
582
     * @return bool
583
     */
584
    public function removeDirectory($dirname)
585
    {
586
        if ($this->numDirectories > 0) {
587
            foreach ($this->directories as $key => $information) {
588
                if ($information['name'] == $dirname) {
589
                    $this->numDirectories--;
590
                    unset($this->directories[$key]);
591
592
                    return true;
593
                }
594
            }
595
        }
596
597
        return false;
598
    }
599
600
    /**
601
     * Write the currently loaded tar archive to disk
602
     *
603
     * @return bool
604
     */
605
    public function saveTar()
606
    {
607
        if (!$this->filename) {
608
            return false;
609
        }
610
        // Write tar to current file using specified gzip compression
611
        $this->toTar($this->filename, $this->isGzipped);
612
613
        return true;
614
    }
615
616
    /**
617
     * Saves tar archive to a different file than the current file
618
     *
619
     * @param  string $filename
620
     * @param  bool   $useGzip Use GZ compression?
621
     * @return bool
622
     */
623
    public function toTar($filename, $useGzip)
624
    {
625
        if (!$filename) {
626
            return false;
627
        }
628
        // Encode processed files into TAR file format
629
        $this->__generateTar();
630
        // GZ Compress the data if we need to
631
        if ($useGzip) {
632
            // Make sure we have gzip support
633
            if (!function_exists('gzencode')) {
634
                return false;
635
            }
636
            $file = gzencode($this->tar_file);
637
        } else {
638
            $file = $this->tar_file;
639
        }
640
        // Write the TAR file
641
        $fp = fopen($filename, 'wb');
642
        fwrite($fp, $file);
643
        fclose($fp);
644
645
        return true;
646
    }
647
648
    /**
649
     * Sends tar archive to stdout
650
     *
651
     * @param  string $filename
652
     * @param  bool   $useGzip Use GZ compression?
653
     * @return string|false
654
     */
655
    public function toTarOutput($filename, $useGzip)
656
    {
657
        if (!$filename) {
658
            return false;
659
        }
660
        // Encode processed files into TAR file format
661
        $this->__generateTar();
662
        // GZ Compress the data if we need to
663
        if ($useGzip) {
664
            // Make sure we have gzip support
665
            if (!function_exists('gzencode')) {
666
                return false;
667
            }
668
            $file = gzencode($this->tar_file);
669
        } else {
670
            $file = $this->tar_file;
671
        }
672
673
        return $file;
674
    }
675
}
676