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

ZipEntry::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 18
c 1
b 0
f 1
nc 1
nop 16
dl 0
loc 37
rs 9.6666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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