Passed
Push — master ( 3b7697...25d5dd )
by Alexey
03:48 queued 29s
created

ZipEntry::getCreatedOS()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/** @noinspection PhpUsageOfSilenceOperatorInspection */
4
5
namespace PhpZip\Model;
6
7
use PhpZip\Constants\DosAttrs;
8
use PhpZip\Constants\DosCodePage;
9
use PhpZip\Constants\GeneralPurposeBitFlag;
10
use PhpZip\Constants\UnixStat;
11
use PhpZip\Constants\ZipCompressionLevel;
12
use PhpZip\Constants\ZipCompressionMethod;
13
use PhpZip\Constants\ZipConstants;
14
use PhpZip\Constants\ZipEncryptionMethod;
15
use PhpZip\Constants\ZipPlatform;
16
use PhpZip\Constants\ZipVersion;
17
use PhpZip\Exception\InvalidArgumentException;
18
use PhpZip\Exception\RuntimeException;
19
use PhpZip\Exception\ZipUnsupportMethodException;
20
use PhpZip\Model\Extra\ExtraFieldsCollection;
21
use PhpZip\Model\Extra\Fields\AsiExtraField;
22
use PhpZip\Model\Extra\Fields\ExtendedTimestampExtraField;
23
use PhpZip\Model\Extra\Fields\NtfsExtraField;
24
use PhpZip\Model\Extra\Fields\OldUnixExtraField;
25
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
26
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
27
use PhpZip\Model\Extra\ZipExtraField;
28
use PhpZip\Util\DateTimeConverter;
29
use PhpZip\Util\StringUtil;
30
31
/**
32
 * ZIP file entry.
33
 *
34
 * @see     https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
35
 *
36
 * @author  Ne-Lexa [email protected]
37
 * @license MIT
38
 */
39
class ZipEntry
40
{
41
    /** @var int the unknown value for numeric properties */
42
    const UNKNOWN = -1;
43
44
    /**
45
     * @var int DOS platform
46
     *
47
     * @deprecated Use {@see ZipPlatform::OS_DOS}
48
     */
49
    const PLATFORM_FAT = ZipPlatform::OS_DOS;
50
51
    /**
52
     * @var int Unix platform
53
     *
54
     * @deprecated Use {@see ZipPlatform::OS_UNIX}
55
     */
56
    const PLATFORM_UNIX = ZipPlatform::OS_UNIX;
57
58
    /**
59
     * @var int MacOS platform
60
     *
61
     * @deprecated Use {@see ZipPlatform::OS_MAC_OSX}
62
     */
63
    const PLATFORM_OS_X = ZipPlatform::OS_MAC_OSX;
64
65
    /**
66
     * Pseudo compression method for WinZip AES encrypted entries.
67
     * Require php extension openssl or mcrypt.
68
     *
69
     * @deprecated Use {@see ZipCompressionMethod::WINZIP_AES}
70
     */
71
    const METHOD_WINZIP_AES = ZipCompressionMethod::WINZIP_AES;
72
73
    /** @var string Entry name (filename in archive) */
74
    private $name;
75
76
    /** @var bool Is directory */
77
    private $isDirectory;
78
79
    /** @var ZipData|null Zip entry contents */
80
    private $data;
81
82
    /** @var int Made by platform */
83
    private $createdOS = self::UNKNOWN;
84
85
    /** @var int Extracted by platform */
86
    private $extractedOS = self::UNKNOWN;
87
88
    /** @var int Software version */
89
    private $softwareVersion = self::UNKNOWN;
90
91
    /** @var int Version needed to extract */
92
    private $extractVersion = self::UNKNOWN;
93
94
    /** @var int Compression method */
95
    private $compressionMethod = self::UNKNOWN;
96
97
    /** @var int General purpose bit flags */
98
    private $generalPurposeBitFlags = 0;
99
100
    /** @var int Dos time */
101
    private $dosTime = self::UNKNOWN;
102
103
    /** @var int Crc32 */
104
    private $crc = self::UNKNOWN;
105
106
    /** @var int Compressed size */
107
    private $compressedSize = self::UNKNOWN;
108
109
    /** @var int Uncompressed size */
110
    private $uncompressedSize = self::UNKNOWN;
111
112
    /** @var int Internal attributes */
113
    private $internalAttributes = 0;
114
115
    /** @var int External attributes */
116
    private $externalAttributes = 0;
117
118
    /** @var int relative Offset Of Local File Header */
119
    private $localHeaderOffset = 0;
120
121
    /**
122
     * Collections of Extra Fields in Central Directory.
123
     * Keys from Header ID [int] and value Extra Field [ExtraField].
124
     *
125
     * @var ExtraFieldsCollection
126
     */
127
    protected $cdExtraFields;
128
129
    /**
130
     * Collections of Extra Fields int local header.
131
     * Keys from Header ID [int] and value Extra Field [ExtraField].
132
     *
133
     * @var ExtraFieldsCollection
134
     */
135
    protected $localExtraFields;
136
137
    /** @var string|null comment field */
138
    private $comment;
139
140
    /** @var string|null entry password for read or write encryption data */
141
    private $password;
142
143
    /** @var int encryption method */
144
    private $encryptionMethod = ZipEncryptionMethod::NONE;
145
146
    /** @var int */
147
    private $compressionLevel = ZipCompressionLevel::NORMAL;
148
149
    /** @var string|null */
150
    private $charset;
151
152
    /**
153
     * ZipEntry constructor.
154
     *
155
     * @param string      $name    Entry name
156
     * @param string|null $charset DOS charset
157
     */
158
    public function __construct($name, $charset = null)
159
    {
160
        $this->setName($name, $charset);
161
162
        $this->cdExtraFields = new ExtraFieldsCollection();
163
        $this->localExtraFields = new ExtraFieldsCollection();
164
    }
165
166
    /**
167
     * This method only internal use.
168
     *
169
     * @param string      $name
170
     * @param int         $createdOS
171
     * @param int         $extractedOS
172
     * @param int         $softwareVersion
173
     * @param int         $extractVersion
174
     * @param int         $compressionMethod
175
     * @param int         $gpbf
176
     * @param int         $dosTime
177
     * @param int         $crc
178
     * @param int         $compressedSize
179
     * @param int         $uncompressedSize
180
     * @param int         $internalAttributes
181
     * @param int         $externalAttributes
182
     * @param int         $offsetLocalHeader
183
     * @param string|null $comment
184
     * @param string|null $charset
185
     *
186
     * @return ZipEntry
187
     *
188
     * @internal
189
     *
190
     * @noinspection PhpTooManyParametersInspection
191
     */
192
    public static function create(
193
        $name,
194
        $createdOS,
195
        $extractedOS,
196
        $softwareVersion,
197
        $extractVersion,
198
        $compressionMethod,
199
        $gpbf,
200
        $dosTime,
201
        $crc,
202
        $compressedSize,
203
        $uncompressedSize,
204
        $internalAttributes,
205
        $externalAttributes,
206
        $offsetLocalHeader,
207
        $comment,
208
        $charset
209
    ) {
210
        $entry = new self($name);
211
        $entry->createdOS = (int) $createdOS;
212
        $entry->extractedOS = (int) $extractedOS;
213
        $entry->softwareVersion = (int) $softwareVersion;
214
        $entry->extractVersion = (int) $extractVersion;
215
        $entry->compressionMethod = (int) $compressionMethod;
216
        $entry->generalPurposeBitFlags = (int) $gpbf;
217
        $entry->dosTime = (int) $dosTime;
218
        $entry->crc = (int) $crc;
219
        $entry->compressedSize = (int) $compressedSize;
220
        $entry->uncompressedSize = (int) $uncompressedSize;
221
        $entry->internalAttributes = (int) $internalAttributes;
222
        $entry->externalAttributes = (int) $externalAttributes;
223
        $entry->localHeaderOffset = (int) $offsetLocalHeader;
224
        $entry->setComment($comment);
225
        $entry->setCharset($charset);
226
        $entry->updateCompressionLevel();
227
228
        return $entry;
229
    }
230
231
    /**
232
     * Set entry name.
233
     *
234
     * @param string      $name    New entry name
235
     * @param string|null $charset
236
     *
237
     * @return ZipEntry
238
     */
239
    private function setName($name, $charset = null)
240
    {
241
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
242
            throw new InvalidArgumentException('zip entry name is null');
243
        }
244
245
        $name = ltrim((string) $name, '\\/');
246
247
        if ($name === '') {
248
            throw new InvalidArgumentException('Empty zip entry name');
249
        }
250
251
        $name = (string) $name;
252
        $length = \strlen($name);
253
254
        if ($length > 0xffff) {
255
            throw new InvalidArgumentException('Illegal zip entry name parameter');
256
        }
257
258
        $this->setCharset($charset);
259
260
        if ($this->charset === null && !StringUtil::isASCII($name)) {
261
            $this->enableUtf8Name(true);
262
        }
263
        $this->name = $name;
264
        $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/';
265
        $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE;
266
267
        if ($this->extractVersion !== self::UNKNOWN) {
268
            $this->extractVersion = max(
269
                $this->extractVersion,
270
                $this->isDirectory ?
271
                    ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO :
272
                    ZipVersion::v10_DEFAULT_MIN
273
            );
274
        }
275
276
        return $this;
277
    }
278
279
    /**
280
     * @param string|null $charset
281
     *
282
     * @return ZipEntry
283
     *
284
     * @see DosCodePage::getCodePages()
285
     */
286
    public function setCharset($charset = null)
287
    {
288
        if ($charset !== null && $charset === '') {
289
            throw new InvalidArgumentException('Empty charset');
290
        }
291
        $this->charset = $charset;
292
293
        return $this;
294
    }
295
296
    /**
297
     * @return string|null
298
     */
299
    public function getCharset()
300
    {
301
        return $this->charset;
302
    }
303
304
    /**
305
     * @param string $newName New entry name
306
     *
307
     * @return ZipEntry new {@see ZipEntry} object with new name
308
     *
309
     * @internal
310
     */
311
    public function rename($newName)
312
    {
313
        $newEntry = clone $this;
314
        $newEntry->setName($newName);
315
316
        $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID);
317
318
        return $newEntry;
319
    }
320
321
    /**
322
     * Returns the ZIP entry name.
323
     *
324
     * @return string
325
     */
326
    public function getName()
327
    {
328
        return $this->name;
329
    }
330
331
    /**
332
     * @return ZipData|null
333
     *
334
     * @internal
335
     */
336
    public function getData()
337
    {
338
        return $this->data;
339
    }
340
341
    /**
342
     * @param ZipData|null $data
343
     *
344
     * @internal
345
     */
346
    public function setData($data)
347
    {
348
        $this->data = $data;
349
    }
350
351
    /**
352
     * @return int Get platform
353
     *
354
     * @deprecated Use {@see ZipEntry::getCreatedOS()}
355
     */
356
    public function getPlatform()
357
    {
358
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCreatedOS()', \E_USER_DEPRECATED);
359
360
        return $this->getCreatedOS();
361
    }
362
363
    /**
364
     * @param int $platform
365
     *
366
     * @return ZipEntry
367
     *
368
     * @deprecated Use {@see ZipEntry::setCreatedOS()}
369
     */
370
    public function setPlatform($platform)
371
    {
372
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCreatedOS()', \E_USER_DEPRECATED);
373
374
        return $this->setCreatedOS($platform);
375
    }
376
377
    /**
378
     * @return int platform
379
     */
380
    public function getCreatedOS()
381
    {
382
        return $this->createdOS;
383
    }
384
385
    /**
386
     * Set platform.
387
     *
388
     * @param int $platform
389
     *
390
     * @return ZipEntry
391
     */
392
    public function setCreatedOS($platform)
393
    {
394
        $platform = (int) $platform;
395
396
        if ($platform < 0x00 || $platform > 0xff) {
397
            throw new InvalidArgumentException('Platform out of range');
398
        }
399
        $this->createdOS = $platform;
400
401
        return $this;
402
    }
403
404
    /**
405
     * @return int
406
     */
407
    public function getExtractedOS()
408
    {
409
        return $this->extractedOS;
410
    }
411
412
    /**
413
     * Set extracted OS.
414
     *
415
     * @param int $platform
416
     *
417
     * @return ZipEntry
418
     */
419
    public function setExtractedOS($platform)
420
    {
421
        $platform = (int) $platform;
422
423
        if ($platform < 0x00 || $platform > 0xff) {
424
            throw new InvalidArgumentException('Platform out of range');
425
        }
426
        $this->extractedOS = $platform;
427
428
        return $this;
429
    }
430
431
    /**
432
     * @return int
433
     */
434
    public function getSoftwareVersion()
435
    {
436
        if ($this->softwareVersion === self::UNKNOWN) {
437
            return $this->getExtractVersion();
438
        }
439
440
        return $this->softwareVersion;
441
    }
442
443
    /**
444
     * @param int $softwareVersion
445
     *
446
     * @return ZipEntry
447
     */
448
    public function setSoftwareVersion($softwareVersion)
449
    {
450
        $this->softwareVersion = (int) $softwareVersion;
451
452
        return $this;
453
    }
454
455
    /**
456
     * Version needed to extract.
457
     *
458
     * @return int
459
     *
460
     * @deprecated Use {@see ZipEntry::getExtractVersion()}
461
     */
462
    public function getVersionNeededToExtract()
463
    {
464
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getExtractVersion()', \E_USER_DEPRECATED);
465
466
        return $this->getExtractVersion();
467
    }
468
469
    /**
470
     * Version needed to extract.
471
     *
472
     * @return int
473
     */
474
    public function getExtractVersion()
475
    {
476
        if ($this->extractVersion === self::UNKNOWN) {
477
            if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) {
478
                return ZipVersion::v51_ENCR_AES_RC2_CORRECT;
479
            }
480
481
            if ($this->compressionMethod === ZipCompressionMethod::BZIP2) {
482
                return ZipVersion::v46_BZIP2;
483
            }
484
485
            if ($this->isZip64ExtensionsRequired()) {
486
                return ZipVersion::v45_ZIP64_EXT;
487
            }
488
489
            if (
490
                $this->compressionMethod === ZipCompressionMethod::DEFLATED ||
491
                $this->isDirectory ||
492
                $this->encryptionMethod === ZipEncryptionMethod::PKWARE
493
            ) {
494
                return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO;
495
            }
496
497
            return ZipVersion::v10_DEFAULT_MIN;
498
        }
499
500
        return $this->extractVersion;
501
    }
502
503
    /**
504
     * Set version needed to extract.
505
     *
506
     * @param int $version
507
     *
508
     * @return ZipEntry
509
     *
510
     * @deprecated Use {@see ZipEntry::setExtractVersion()}
511
     */
512
    public function setVersionNeededToExtract($version)
513
    {
514
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setExtractVersion()', \E_USER_DEPRECATED);
515
516
        return $this->setExtractVersion($version);
517
    }
518
519
    /**
520
     * Set version needed to extract.
521
     *
522
     * @param int $version
523
     *
524
     * @return ZipEntry
525
     */
526
    public function setExtractVersion($version)
527
    {
528
        $this->extractVersion = max(ZipVersion::v10_DEFAULT_MIN, (int) $version);
529
530
        return $this;
531
    }
532
533
    /**
534
     * Returns the compressed size of this entry.
535
     *
536
     * @return int
537
     */
538
    public function getCompressedSize()
539
    {
540
        return $this->compressedSize;
541
    }
542
543
    /**
544
     * Sets the compressed size of this entry.
545
     *
546
     * @param int $compressedSize the Compressed Size
547
     *
548
     * @return ZipEntry
549
     *
550
     * @internal
551
     */
552
    public function setCompressedSize($compressedSize)
553
    {
554
        $compressedSize = (int) $compressedSize;
555
556
        if ($compressedSize < self::UNKNOWN) {
557
            throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN);
558
        }
559
        $this->compressedSize = $compressedSize;
560
561
        return $this;
562
    }
563
564
    /**
565
     * Returns the uncompressed size of this entry.
566
     *
567
     * @return int
568
     *
569
     * @deprecated Use {@see ZipEntry::getUncompressedSize()}
570
     */
571
    public function getSize()
572
    {
573
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getUncompressedSize()', \E_USER_DEPRECATED);
574
575
        return $this->getUncompressedSize();
576
    }
577
578
    /**
579
     * Sets the uncompressed size of this entry.
580
     *
581
     * @param int $size the (Uncompressed) Size
582
     *
583
     * @return ZipEntry
584
     *
585
     * @deprecated Use {@see ZipEntry::setUncompressedSize()}
586
     *
587
     * @internal
588
     */
589
    public function setSize($size)
590
    {
591
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setUncompressedSize()', \E_USER_DEPRECATED);
592
593
        return $this->setUncompressedSize($size);
594
    }
595
596
    /**
597
     * Returns the uncompressed size of this entry.
598
     *
599
     * @return int
600
     */
601
    public function getUncompressedSize()
602
    {
603
        return $this->uncompressedSize;
604
    }
605
606
    /**
607
     * Sets the uncompressed size of this entry.
608
     *
609
     * @param int $uncompressedSize the (Uncompressed) Size
610
     *
611
     * @return ZipEntry
612
     *
613
     * @internal
614
     */
615
    public function setUncompressedSize($uncompressedSize)
616
    {
617
        $uncompressedSize = (int) $uncompressedSize;
618
619
        if ($uncompressedSize < self::UNKNOWN) {
620
            throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN);
621
        }
622
        $this->uncompressedSize = $uncompressedSize;
623
624
        return $this;
625
    }
626
627
    /**
628
     * Return relative Offset Of Local File Header.
629
     *
630
     * @return int
631
     */
632
    public function getLocalHeaderOffset()
633
    {
634
        return $this->localHeaderOffset;
635
    }
636
637
    /**
638
     * @param int $localHeaderOffset
639
     *
640
     * @return ZipEntry
641
     *
642
     * @internal
643
     */
644
    public function setLocalHeaderOffset($localHeaderOffset)
645
    {
646
        $localHeaderOffset = (int) $localHeaderOffset;
647
648
        if ($localHeaderOffset < 0) {
649
            throw new InvalidArgumentException('Negative $localHeaderOffset');
650
        }
651
        $this->localHeaderOffset = $localHeaderOffset;
652
653
        return $this;
654
    }
655
656
    /**
657
     * Return relative Offset Of Local File Header.
658
     *
659
     * @return int
660
     *
661
     * @deprecated Use {@see ZipEntry::getLocalHeaderOffset()}
662
     */
663
    public function getOffset()
664
    {
665
        @trigger_error(
666
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getLocalHeaderOffset()',
667
            \E_USER_DEPRECATED
668
        );
669
670
        return $this->getLocalHeaderOffset();
671
    }
672
673
    /**
674
     * @param int $offset
675
     *
676
     * @return ZipEntry
677
     *
678
     * @deprecated Use {@see ZipEntry::setLocalHeaderOffset()}
679
     *
680
     * @internal
681
     */
682
    public function setOffset($offset)
683
    {
684
        @trigger_error(
685
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setLocalHeaderOffset()',
686
            \E_USER_DEPRECATED
687
        );
688
689
        return $this->setLocalHeaderOffset($offset);
690
    }
691
692
    /**
693
     * Returns the General Purpose Bit Flags.
694
     *
695
     * @return int
696
     */
697
    public function getGeneralPurposeBitFlags()
698
    {
699
        return $this->generalPurposeBitFlags;
700
    }
701
702
    /**
703
     * Sets the General Purpose Bit Flags.
704
     *
705
     * @param int $gpbf general purpose bit flags
706
     *
707
     * @return ZipEntry
708
     *
709
     * @internal
710
     */
711
    public function setGeneralPurposeBitFlags($gpbf)
712
    {
713
        $gpbf = (int) $gpbf;
714
715
        if ($gpbf < 0x0000 || $gpbf > 0xffff) {
716
            throw new InvalidArgumentException('general purpose bit flags out of range');
717
        }
718
        $this->generalPurposeBitFlags = $gpbf;
719
        $this->updateCompressionLevel();
720
721
        return $this;
722
    }
723
724
    private function updateCompressionLevel()
725
    {
726
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
727
            $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1);
728
            $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2);
729
730
            if ($bit1 && !$bit2) {
731
                $this->compressionLevel = ZipCompressionLevel::MAXIMUM;
732
            } elseif (!$bit1 && $bit2) {
733
                $this->compressionLevel = ZipCompressionLevel::FAST;
734
            } elseif ($bit1 && $bit2) {
735
                $this->compressionLevel = ZipCompressionLevel::SUPER_FAST;
736
            } else {
737
                $this->compressionLevel = ZipCompressionLevel::NORMAL;
738
            }
739
        }
740
    }
741
742
    /**
743
     * @param int  $mask
744
     * @param bool $enable
745
     *
746
     * @return ZipEntry
747
     */
748
    private function setGeneralBitFlag($mask, $enable)
749
    {
750
        if ($enable) {
751
            $this->generalPurposeBitFlags |= $mask;
752
        } else {
753
            $this->generalPurposeBitFlags &= ~$mask;
754
        }
755
756
        return $this;
757
    }
758
759
    /**
760
     * @param int $mask
761
     *
762
     * @return bool
763
     */
764
    private function isSetGeneralBitFlag($mask)
765
    {
766
        return ($this->generalPurposeBitFlags & $mask) === $mask;
767
    }
768
769
    /**
770
     * @return bool
771
     */
772
    public function isDataDescriptorEnabled()
773
    {
774
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR);
775
    }
776
777
    /**
778
     * Enabling or disabling the use of the Data Descriptor block.
779
     *
780
     * @param bool $enabled
781
     */
782
    public function enableDataDescriptor($enabled = true)
783
    {
784
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR, (bool) $enabled);
785
    }
786
787
    /**
788
     * @param bool $enabled
789
     */
790
    public function enableUtf8Name($enabled)
791
    {
792
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled);
793
    }
794
795
    /**
796
     * @return bool
797
     */
798
    public function isUtf8Flag()
799
    {
800
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::UTF8);
801
    }
802
803
    /**
804
     * Returns true if and only if this ZIP entry is encrypted.
805
     *
806
     * @return bool
807
     */
808
    public function isEncrypted()
809
    {
810
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION);
811
    }
812
813
    /**
814
     * @return bool
815
     */
816
    public function isStrongEncryption()
817
    {
818
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::STRONG_ENCRYPTION);
819
    }
820
821
    /**
822
     * Sets the encryption property to false and removes any other
823
     * encryption artifacts.
824
     *
825
     * @return ZipEntry
826
     */
827
    public function disableEncryption()
828
    {
829
        $this->setEncrypted(false);
830
        $this->removeExtraField(WinZipAesExtraField::HEADER_ID);
831
        $this->encryptionMethod = ZipEncryptionMethod::NONE;
832
        $this->password = null;
833
        $this->extractVersion = self::UNKNOWN;
834
835
        return $this;
836
    }
837
838
    /**
839
     * Sets the encryption flag for this ZIP entry.
840
     *
841
     * @param bool $encrypted
842
     *
843
     * @return ZipEntry
844
     */
845
    private function setEncrypted($encrypted)
846
    {
847
        $encrypted = (bool) $encrypted;
848
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted);
849
850
        return $this;
851
    }
852
853
    /**
854
     * Returns the compression method for this entry.
855
     *
856
     * @return int
857
     *
858
     * @deprecated Use {@see ZipEntry::getCompressionMethod()}
859
     */
860
    public function getMethod()
861
    {
862
        @trigger_error(
863
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCompressionMethod()',
864
            \E_USER_DEPRECATED
865
        );
866
867
        return $this->getCompressionMethod();
868
    }
869
870
    /**
871
     * Returns the compression method for this entry.
872
     *
873
     * @return int
874
     */
875
    public function getCompressionMethod()
876
    {
877
        return $this->compressionMethod;
878
    }
879
880
    /**
881
     * Sets the compression method for this entry.
882
     *
883
     * @param int $method
884
     *
885
     * @throws ZipUnsupportMethodException
886
     *
887
     * @return ZipEntry
888
     *
889
     * @deprecated Use {@see ZipEntry::setCompressionMethod()}
890
     */
891
    public function setMethod($method)
892
    {
893
        @trigger_error(
894
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCompressionMethod()',
895
            \E_USER_DEPRECATED
896
        );
897
898
        return $this->setCompressionMethod($method);
899
    }
900
901
    /**
902
     * Sets the compression method for this entry.
903
     *
904
     * @param int $compressionMethod
905
     *
906
     * @throws ZipUnsupportMethodException
907
     *
908
     * @return ZipEntry
909
     *
910
     * @see ZipCompressionMethod::STORED
911
     * @see ZipCompressionMethod::DEFLATED
912
     * @see ZipCompressionMethod::BZIP2
913
     */
914
    public function setCompressionMethod($compressionMethod)
915
    {
916
        $compressionMethod = (int) $compressionMethod;
917
918
        if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
919
            throw new InvalidArgumentException('method out of range: ' . $compressionMethod);
920
        }
921
922
        ZipCompressionMethod::checkSupport($compressionMethod);
923
924
        $this->compressionMethod = $compressionMethod;
925
        $this->updateCompressionLevel();
926
        $this->extractVersion = self::UNKNOWN;
927
928
        return $this;
929
    }
930
931
    /**
932
     * Get Unix Timestamp.
933
     *
934
     * @return int
935
     */
936
    public function getTime()
937
    {
938
        if ($this->getDosTime() === self::UNKNOWN) {
939
            return self::UNKNOWN;
940
        }
941
942
        return DateTimeConverter::msDosToUnix($this->getDosTime());
943
    }
944
945
    /**
946
     * Get Dos Time.
947
     *
948
     * @return int
949
     */
950
    public function getDosTime()
951
    {
952
        return $this->dosTime;
953
    }
954
955
    /**
956
     * Set Dos Time.
957
     *
958
     * @param int $dosTime
959
     *
960
     * @return ZipEntry
961
     */
962
    public function setDosTime($dosTime)
963
    {
964
        $dosTime = (int) $dosTime;
965
966
        if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
967
            throw new InvalidArgumentException('DosTime out of range');
968
        }
969
        $this->dosTime = $dosTime;
970
971
        return $this;
972
    }
973
974
    /**
975
     * Set time from unix timestamp.
976
     *
977
     * @param int $unixTimestamp
978
     *
979
     * @return ZipEntry
980
     */
981
    public function setTime($unixTimestamp)
982
    {
983
        if ($unixTimestamp !== self::UNKNOWN) {
984
            $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp));
985
        } else {
986
            $this->dosTime = 0;
987
        }
988
989
        return $this;
990
    }
991
992
    /**
993
     * Returns the external file attributes.
994
     *
995
     * @return int the external file attributes
996
     */
997
    public function getExternalAttributes()
998
    {
999
        return $this->externalAttributes;
1000
    }
1001
1002
    /**
1003
     * Sets the external file attributes.
1004
     *
1005
     * @param int $externalAttributes the external file attributes
1006
     *
1007
     * @return ZipEntry
1008
     */
1009
    public function setExternalAttributes($externalAttributes)
1010
    {
1011
        $this->externalAttributes = (int) $externalAttributes;
1012
1013
        if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
1014
            throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
1015
        }
1016
        $this->externalAttributes = $externalAttributes;
1017
1018
        return $this;
1019
    }
1020
1021
    /**
1022
     * Returns the internal file attributes.
1023
     *
1024
     * @return int the internal file attributes
1025
     */
1026
    public function getInternalAttributes()
1027
    {
1028
        return $this->internalAttributes;
1029
    }
1030
1031
    /**
1032
     * Sets the internal file attributes.
1033
     *
1034
     * @param int $internalAttributes the internal file attributes
1035
     *
1036
     * @return ZipEntry
1037
     */
1038
    public function setInternalAttributes($internalAttributes)
1039
    {
1040
        $internalAttributes = (int) $internalAttributes;
1041
1042
        if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) {
1043
            throw new InvalidArgumentException('internal attributes out of range');
1044
        }
1045
        $this->internalAttributes = $internalAttributes;
1046
1047
        return $this;
1048
    }
1049
1050
    /**
1051
     * Returns true if and only if this ZIP entry represents a directory entry
1052
     * (i.e. end with '/').
1053
     *
1054
     * @return bool
1055
     */
1056
    final public function isDirectory()
1057
    {
1058
        return $this->isDirectory;
1059
    }
1060
1061
    /**
1062
     * @return ExtraFieldsCollection
1063
     */
1064
    public function getCdExtraFields()
1065
    {
1066
        return $this->cdExtraFields;
1067
    }
1068
1069
    /**
1070
     * @param int $headerId
1071
     *
1072
     * @return ZipExtraField|null
1073
     */
1074
    public function getCdExtraField($headerId)
1075
    {
1076
        return $this->cdExtraFields->get((int) $headerId);
1077
    }
1078
1079
    /**
1080
     * @param ExtraFieldsCollection $cdExtraFields
1081
     *
1082
     * @return ZipEntry
1083
     */
1084
    public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields)
1085
    {
1086
        $this->cdExtraFields = $cdExtraFields;
1087
1088
        return $this;
1089
    }
1090
1091
    /**
1092
     * @return ExtraFieldsCollection
1093
     */
1094
    public function getLocalExtraFields()
1095
    {
1096
        return $this->localExtraFields;
1097
    }
1098
1099
    /**
1100
     * @param int $headerId
1101
     *
1102
     * @return ZipExtraField|null
1103
     */
1104
    public function getLocalExtraField($headerId)
1105
    {
1106
        return $this->localExtraFields[(int) $headerId];
1107
    }
1108
1109
    /**
1110
     * @param ExtraFieldsCollection $localExtraFields
1111
     *
1112
     * @return ZipEntry
1113
     */
1114
    public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields)
1115
    {
1116
        $this->localExtraFields = $localExtraFields;
1117
1118
        return $this;
1119
    }
1120
1121
    /**
1122
     * @param int $headerId
1123
     *
1124
     * @return ZipExtraField|null
1125
     */
1126
    public function getExtraField($headerId)
1127
    {
1128
        $headerId = (int) $headerId;
1129
        $local = $this->getLocalExtraField($headerId);
1130
1131
        if ($local === null) {
1132
            return $this->getCdExtraField($headerId);
1133
        }
1134
1135
        return $local;
1136
    }
1137
1138
    /**
1139
     * @param int $headerId
1140
     *
1141
     * @return bool
1142
     */
1143
    public function hasExtraField($headerId)
1144
    {
1145
        $headerId = (int) $headerId;
1146
1147
        return
1148
            isset($this->localExtraFields[$headerId]) ||
1149
            isset($this->cdExtraFields[$headerId]);
1150
    }
1151
1152
    /**
1153
     * @param int $headerId
1154
     */
1155
    public function removeExtraField($headerId)
1156
    {
1157
        $headerId = (int) $headerId;
1158
1159
        $this->cdExtraFields->remove($headerId);
1160
        $this->localExtraFields->remove($headerId);
1161
    }
1162
1163
    /**
1164
     * @param ZipExtraField $zipExtraField
1165
     */
1166
    public function addExtraField(ZipExtraField $zipExtraField)
1167
    {
1168
        $this->addLocalExtraField($zipExtraField);
1169
        $this->addCdExtraField($zipExtraField);
1170
    }
1171
1172
    /**
1173
     * @param ZipExtraField $zipExtraField
1174
     */
1175
    public function addLocalExtraField(ZipExtraField $zipExtraField)
1176
    {
1177
        $this->localExtraFields->add($zipExtraField);
1178
    }
1179
1180
    /**
1181
     * @param ZipExtraField $zipExtraField
1182
     */
1183
    public function addCdExtraField(ZipExtraField $zipExtraField)
1184
    {
1185
        $this->cdExtraFields->add($zipExtraField);
1186
    }
1187
1188
    /**
1189
     * Returns comment entry.
1190
     *
1191
     * @return string
1192
     */
1193
    public function getComment()
1194
    {
1195
        return $this->comment !== null ? $this->comment : '';
1196
    }
1197
1198
    /**
1199
     * Set entry comment.
1200
     *
1201
     * @param string|null $comment
1202
     *
1203
     * @return ZipEntry
1204
     */
1205
    public function setComment($comment)
1206
    {
1207
        if ($comment !== null) {
1208
            $commentLength = \strlen($comment);
1209
1210
            if ($commentLength > 0xffff) {
1211
                throw new InvalidArgumentException('Comment too long');
1212
            }
1213
1214
            if ($this->charset === null && !StringUtil::isASCII($comment)) {
1215
                $this->enableUtf8Name(true);
1216
            }
1217
        }
1218
        $this->comment = $comment;
1219
1220
        return $this;
1221
    }
1222
1223
    /**
1224
     * @return bool
1225
     */
1226
    public function isDataDescriptorRequired()
1227
    {
1228
        return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN;
1229
    }
1230
1231
    /**
1232
     * Return crc32 content or 0 for WinZip AES v2.
1233
     *
1234
     * @return int
1235
     */
1236
    public function getCrc()
1237
    {
1238
        return $this->crc;
1239
    }
1240
1241
    /**
1242
     * Set crc32 content.
1243
     *
1244
     * @param int $crc
1245
     *
1246
     * @return ZipEntry
1247
     *
1248
     * @internal
1249
     */
1250
    public function setCrc($crc)
1251
    {
1252
        $this->crc = (int) $crc;
1253
1254
        return $this;
1255
    }
1256
1257
    /**
1258
     * @return string|null
1259
     */
1260
    public function getPassword()
1261
    {
1262
        return $this->password;
1263
    }
1264
1265
    /**
1266
     * Set password and encryption method from entry.
1267
     *
1268
     * @param string|null $password
1269
     * @param int|null    $encryptionMethod
1270
     *
1271
     * @return ZipEntry
1272
     */
1273
    public function setPassword($password, $encryptionMethod = null)
1274
    {
1275
        if (!$this->isDirectory) {
1276
            if ($password === null || $password === '') {
1277
                $this->password = null;
1278
                $this->disableEncryption();
1279
            } else {
1280
                $this->password = (string) $password;
1281
1282
                if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) {
1283
                    $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256;
1284
                }
1285
1286
                if ($encryptionMethod !== null) {
1287
                    $this->setEncryptionMethod($encryptionMethod);
1288
                }
1289
                $this->setEncrypted(true);
1290
            }
1291
        }
1292
1293
        return $this;
1294
    }
1295
1296
    /**
1297
     * @return int
1298
     */
1299
    public function getEncryptionMethod()
1300
    {
1301
        return $this->encryptionMethod;
1302
    }
1303
1304
    /**
1305
     * Set encryption method.
1306
     *
1307
     * @param int|null $encryptionMethod
1308
     *
1309
     * @return ZipEntry
1310
     *
1311
     * @see ZipEncryptionMethod::NONE
1312
     * @see ZipEncryptionMethod::PKWARE
1313
     * @see ZipEncryptionMethod::WINZIP_AES_256
1314
     * @see ZipEncryptionMethod::WINZIP_AES_192
1315
     * @see ZipEncryptionMethod::WINZIP_AES_128
1316
     */
1317
    public function setEncryptionMethod($encryptionMethod)
1318
    {
1319
        if ($encryptionMethod === null) {
1320
            $encryptionMethod = ZipEncryptionMethod::NONE;
1321
        }
1322
1323
        $encryptionMethod = (int) $encryptionMethod;
1324
        ZipEncryptionMethod::checkSupport($encryptionMethod);
1325
        $this->encryptionMethod = $encryptionMethod;
1326
1327
        $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE);
1328
        $this->extractVersion = self::UNKNOWN;
1329
1330
        return $this;
1331
    }
1332
1333
    /**
1334
     * @return int
1335
     */
1336
    public function getCompressionLevel()
1337
    {
1338
        return $this->compressionLevel;
1339
    }
1340
1341
    /**
1342
     * @param int $compressionLevel
1343
     *
1344
     * @return ZipEntry
1345
     */
1346
    public function setCompressionLevel($compressionLevel)
1347
    {
1348
        $compressionLevel = (int) $compressionLevel;
1349
1350
        if ($compressionLevel === self::UNKNOWN) {
1351
            $compressionLevel = ZipCompressionLevel::NORMAL;
1352
        }
1353
1354
        if (
1355
            $compressionLevel < ZipCompressionLevel::LEVEL_MIN ||
1356
            $compressionLevel > ZipCompressionLevel::LEVEL_MAX
1357
        ) {
1358
            throw new InvalidArgumentException(
1359
                'Invalid compression level. Minimum level ' .
1360
                ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX
1361
            );
1362
        }
1363
        $this->compressionLevel = $compressionLevel;
1364
1365
        $this->updateGbpfCompLevel();
1366
1367
        return $this;
1368
    }
1369
1370
    /**
1371
     * Update general purpose bit flogs.
1372
     */
1373
    private function updateGbpfCompLevel()
1374
    {
1375
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
1376
            $bit1 = false;
1377
            $bit2 = false;
1378
1379
            switch ($this->compressionLevel) {
1380
                case ZipCompressionLevel::MAXIMUM:
1381
                    $bit1 = true;
1382
                    break;
1383
1384
                case ZipCompressionLevel::FAST:
1385
                    $bit2 = true;
1386
                    break;
1387
1388
                case ZipCompressionLevel::SUPER_FAST:
1389
                    $bit1 = true;
1390
                    $bit2 = true;
1391
                    break;
1392
                // default is ZipCompressionLevel::NORMAL
1393
            }
1394
1395
            $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0);
1396
            $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0);
1397
        }
1398
    }
1399
1400
    /**
1401
     * Sets Unix permissions in a way that is understood by Info-Zip's
1402
     * unzip command.
1403
     *
1404
     * @param int $mode mode an int value
1405
     *
1406
     * @return ZipEntry
1407
     */
1408
    public function setUnixMode($mode)
1409
    {
1410
        $mode = (int) $mode;
1411
        $this->setExternalAttributes(
1412
            ($mode << 16)
1413
            // MS-DOS read-only attribute
1414
            | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0)
1415
            // MS-DOS directory flag
1416
            | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE)
1417
        );
1418
        $this->createdOS = ZipPlatform::OS_UNIX;
1419
1420
        return $this;
1421
    }
1422
1423
    /**
1424
     * Unix permission.
1425
     *
1426
     * @return int the unix permissions
1427
     */
1428
    public function getUnixMode()
1429
    {
1430
        $mode = 0;
1431
1432
        if ($this->createdOS === ZipPlatform::OS_UNIX) {
1433
            $mode = ($this->externalAttributes >> 16) & 0xFFFF;
1434
        } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) {
1435
            /** @var AsiExtraField $asiExtraField */
1436
            $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID);
1437
            $mode = $asiExtraField->getMode();
1438
        }
1439
1440
        if ($mode > 0) {
1441
            return $mode;
1442
        }
1443
1444
        return $this->isDirectory ? 040755 : 0100644;
1445
    }
1446
1447
    /**
1448
     * Offset MUST be considered in decision about ZIP64 format - see
1449
     * description of Data Descriptor in ZIP File Format Specification.
1450
     *
1451
     * @return bool
1452
     */
1453
    public function isZip64ExtensionsRequired()
1454
    {
1455
        return $this->compressedSize > ZipConstants::ZIP64_MAGIC
1456
            || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC;
1457
    }
1458
1459
    /**
1460
     * Returns true if this entry represents a unix symlink,
1461
     * in which case the entry's content contains the target path
1462
     * for the symlink.
1463
     *
1464
     * @return bool true if the entry represents a unix symlink,
1465
     *              false otherwise
1466
     */
1467
    public function isUnixSymlink()
1468
    {
1469
        return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK;
1470
    }
1471
1472
    /**
1473
     * @return \DateTimeInterface
1474
     */
1475
    public function getMTime()
1476
    {
1477
        /** @var NtfsExtraField|null $ntfsExtra */
1478
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1479
1480
        if ($ntfsExtra !== null) {
1481
            return $ntfsExtra->getModifyDateTime();
1482
        }
1483
1484
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1485
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1486
1487
        if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) {
1488
            return $mtime;
1489
        }
1490
1491
        /** @var OldUnixExtraField|null $oldUnixExtra */
1492
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1493
1494
        if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) {
1495
            return $mtime;
1496
        }
1497
1498
        $timestamp = $this->getTime();
1499
1500
        try {
1501
            return new \DateTimeImmutable('@' . $timestamp);
1502
        } catch (\Exception $e) {
1503
            throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e);
1504
        }
1505
    }
1506
1507
    /**
1508
     * @return \DateTimeInterface|null
1509
     */
1510
    public function getATime()
1511
    {
1512
        /** @var NtfsExtraField|null $ntfsExtra */
1513
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1514
1515
        if ($ntfsExtra !== null) {
1516
            return $ntfsExtra->getAccessDateTime();
1517
        }
1518
1519
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1520
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1521
1522
        if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) {
1523
            return $atime;
1524
        }
1525
1526
        /** @var OldUnixExtraField|null $oldUnixExtra */
1527
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1528
1529
        if ($oldUnixExtra !== null) {
1530
            return $oldUnixExtra->getAccessDateTime();
1531
        }
1532
1533
        return null;
1534
    }
1535
1536
    /**
1537
     * @return \DateTimeInterface|null
1538
     */
1539
    public function getCTime()
1540
    {
1541
        /** @var NtfsExtraField|null $ntfsExtra */
1542
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1543
1544
        if ($ntfsExtra !== null) {
1545
            return $ntfsExtra->getCreateDateTime();
1546
        }
1547
1548
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1549
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1550
1551
        if ($extendedExtra !== null) {
1552
            return $extendedExtra->getCreateDateTime();
1553
        }
1554
1555
        return null;
1556
    }
1557
1558
    public function __clone()
1559
    {
1560
        $this->cdExtraFields = clone $this->cdExtraFields;
1561
        $this->localExtraFields = clone $this->localExtraFields;
1562
1563
        if ($this->data !== null) {
1564
            $this->data = clone $this->data;
1565
        }
1566
    }
1567
}
1568