Passed
Push — master ( d305ab...074443 )
by Alexey
03:50 queued 12s
created

ZipEntry::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

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

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 277
    public function __construct($name, $charset = null)
159
    {
160 277
        $this->setName($name, $charset);
161
162 274
        $this->cdExtraFields = new ExtraFieldsCollection();
163 274
        $this->localExtraFields = new ExtraFieldsCollection();
164 274
    }
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 84
    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 84
        $entry = new self($name);
211 84
        $entry->createdOS = (int) $createdOS;
212 84
        $entry->extractedOS = (int) $extractedOS;
213 84
        $entry->softwareVersion = (int) $softwareVersion;
214 84
        $entry->extractVersion = (int) $extractVersion;
215 84
        $entry->compressionMethod = (int) $compressionMethod;
216 84
        $entry->generalPurposeBitFlags = (int) $gpbf;
217 84
        $entry->dosTime = (int) $dosTime;
218 84
        $entry->crc = (int) $crc;
219 84
        $entry->compressedSize = (int) $compressedSize;
220 84
        $entry->uncompressedSize = (int) $uncompressedSize;
221 84
        $entry->internalAttributes = (int) $internalAttributes;
222 84
        $entry->externalAttributes = (int) $externalAttributes;
223 84
        $entry->localHeaderOffset = (int) $offsetLocalHeader;
224 84
        $entry->setComment($comment);
225 84
        $entry->setCharset($charset);
226 84
        $entry->updateCompressionLevel();
227
228 84
        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 277
    private function setName($name, $charset = null)
240
    {
241 277
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
242 1
            throw new InvalidArgumentException('zip entry name is null');
243
        }
244
245 276
        $name = ltrim((string) $name, '\\/');
246
247 276
        if ($name === '') {
248 2
            throw new InvalidArgumentException('Empty zip entry name');
249
        }
250
251 274
        $name = (string) $name;
252 274
        $length = \strlen($name);
253
254 274
        if ($length > 0xffff) {
255
            throw new InvalidArgumentException('Illegal zip entry name parameter');
256
        }
257
258 274
        $this->setCharset($charset);
259
260 274
        if ($this->charset === null && !StringUtil::isASCII($name)) {
261 22
            $this->enableUtf8Name(true);
262
        }
263 274
        $this->name = $name;
264 274
        $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/';
265 274
        $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE;
266
267 274
        if ($this->extractVersion !== self::UNKNOWN) {
268 3
            $this->extractVersion = max(
269 3
                $this->extractVersion,
270 3
                $this->isDirectory ?
271 1
                    ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO :
272 3
                    ZipVersion::v10_DEFAULT_MIN
273
            );
274
        }
275
276 274
        return $this;
277
    }
278
279
    /**
280
     * @param string|null $charset
281
     *
282
     * @return ZipEntry
283
     *
284
     * @see DosCodePage::getCodePages()
285
     */
286 274
    public function setCharset($charset = null)
287
    {
288 274
        if ($charset !== null && $charset === '') {
289 1
            throw new InvalidArgumentException('Empty charset');
290
        }
291 274
        $this->charset = $charset;
292
293 274
        return $this;
294
    }
295
296
    /**
297
     * @return string|null
298
     */
299 83
    public function getCharset()
300
    {
301 83
        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 6
    public function rename($newName)
312
    {
313 6
        $newEntry = clone $this;
314 6
        $newEntry->setName($newName);
315
316 6
        $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID);
317
318 6
        return $newEntry;
319
    }
320
321
    /**
322
     * Returns the ZIP entry name.
323
     *
324
     * @return string
325
     */
326 111
    public function getName()
327
    {
328 111
        return $this->name;
329
    }
330
331
    /**
332
     * @return ZipData|null
333
     *
334
     * @internal
335
     */
336 91
    public function getData()
337
    {
338 91
        return $this->data;
339
    }
340
341
    /**
342
     * @param ZipData|null $data
343
     *
344
     * @internal
345
     */
346 105
    public function setData($data)
347
    {
348 105
        $this->data = $data;
349 105
    }
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 99
    public function getCreatedOS()
381
    {
382 99
        return $this->createdOS;
383
    }
384
385
    /**
386
     * Set platform.
387
     *
388
     * @param int $platform
389
     *
390
     * @return ZipEntry
391
     */
392 108
    public function setCreatedOS($platform)
393
    {
394 108
        $platform = (int) $platform;
395
396 108
        if ($platform < 0x00 || $platform > 0xff) {
397 2
            throw new InvalidArgumentException('Platform out of range');
398
        }
399 106
        $this->createdOS = $platform;
400
401 106
        return $this;
402
    }
403
404
    /**
405
     * @return int
406
     */
407 84
    public function getExtractedOS()
408
    {
409 84
        return $this->extractedOS;
410
    }
411
412
    /**
413
     * Set extracted OS.
414
     *
415
     * @param int $platform
416
     *
417
     * @return ZipEntry
418
     */
419 108
    public function setExtractedOS($platform)
420
    {
421 108
        $platform = (int) $platform;
422
423 108
        if ($platform < 0x00 || $platform > 0xff) {
424 2
            throw new InvalidArgumentException('Platform out of range');
425
        }
426 106
        $this->extractedOS = $platform;
427
428 106
        return $this;
429
    }
430
431
    /**
432
     * @return int
433
     */
434 82
    public function getSoftwareVersion()
435
    {
436 82
        if ($this->softwareVersion === self::UNKNOWN) {
437 79
            return $this->getExtractVersion();
438
        }
439
440 23
        return $this->softwareVersion;
441
    }
442
443
    /**
444
     * @param int $softwareVersion
445
     *
446
     * @return ZipEntry
447
     */
448 1
    public function setSoftwareVersion($softwareVersion)
449
    {
450 1
        $this->softwareVersion = (int) $softwareVersion;
451
452 1
        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 90
    public function getExtractVersion()
475
    {
476 90
        if ($this->extractVersion === self::UNKNOWN) {
477 88
            if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) {
478 14
                return ZipVersion::v51_ENCR_AES_RC2_CORRECT;
479
            }
480
481 83
            if ($this->compressionMethod === ZipCompressionMethod::BZIP2) {
482 4
                return ZipVersion::v46_BZIP2;
483
            }
484
485 83
            if ($this->isZip64ExtensionsRequired()) {
486
                return ZipVersion::v45_ZIP64_EXT;
487
            }
488
489
            if (
490 83
                $this->compressionMethod === ZipCompressionMethod::DEFLATED ||
491 73
                $this->isDirectory ||
492 83
                $this->encryptionMethod === ZipEncryptionMethod::PKWARE
493
            ) {
494 46
                return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO;
495
            }
496
497 66
            return ZipVersion::v10_DEFAULT_MIN;
498
        }
499
500 19
        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 2
    public function setExtractVersion($version)
527
    {
528 2
        $this->extractVersion = max(ZipVersion::v10_DEFAULT_MIN, (int) $version);
529
530 2
        return $this;
531
    }
532
533
    /**
534
     * Returns the compressed size of this entry.
535
     *
536
     * @return int
537
     */
538 97
    public function getCompressedSize()
539
    {
540 97
        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 96
    public function setCompressedSize($compressedSize)
553
    {
554 96
        $compressedSize = (int) $compressedSize;
555
556 96
        if ($compressedSize < self::UNKNOWN) {
557 1
            throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN);
558
        }
559 95
        $this->compressedSize = $compressedSize;
560
561 95
        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 97
    public function getUncompressedSize()
602
    {
603 97
        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 112
    public function setUncompressedSize($uncompressedSize)
616
    {
617 112
        $uncompressedSize = (int) $uncompressedSize;
618
619 112
        if ($uncompressedSize < self::UNKNOWN) {
620 1
            throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN);
621
        }
622 111
        $this->uncompressedSize = $uncompressedSize;
623
624 111
        return $this;
625
    }
626
627
    /**
628
     * Return relative Offset Of Local File Header.
629
     *
630
     * @return int
631
     */
632 89
    public function getLocalHeaderOffset()
633
    {
634 89
        return $this->localHeaderOffset;
635
    }
636
637
    /**
638
     * @param int $localHeaderOffset
639
     *
640
     * @return ZipEntry
641
     *
642
     * @internal
643
     */
644 81
    public function setLocalHeaderOffset($localHeaderOffset)
645
    {
646 81
        $localHeaderOffset = (int) $localHeaderOffset;
647
648 81
        if ($localHeaderOffset < 0) {
649 1
            throw new InvalidArgumentException('Negative $localHeaderOffset');
650
        }
651 81
        $this->localHeaderOffset = $localHeaderOffset;
652
653 81
        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 96
    public function getGeneralPurposeBitFlags()
698
    {
699 96
        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 8
    public function setGeneralPurposeBitFlags($gpbf)
712
    {
713 8
        $gpbf = (int) $gpbf;
714
715 8
        if ($gpbf < 0x0000 || $gpbf > 0xffff) {
716 2
            throw new InvalidArgumentException('general purpose bit flags out of range');
717
        }
718 6
        $this->generalPurposeBitFlags = $gpbf;
719 6
        $this->updateCompressionLevel();
720
721 6
        return $this;
722
    }
723
724 127
    private function updateCompressionLevel()
725
    {
726 127
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
727 58
            $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1);
728 58
            $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2);
729
730 58
            if ($bit1 && !$bit2) {
731 3
                $this->compressionLevel = ZipCompressionLevel::MAXIMUM;
732 57
            } elseif (!$bit1 && $bit2) {
733 2
                $this->compressionLevel = ZipCompressionLevel::FAST;
734 57
            } elseif ($bit1 && $bit2) {
735 3
                $this->compressionLevel = ZipCompressionLevel::SUPER_FAST;
736
            } else {
737 57
                $this->compressionLevel = ZipCompressionLevel::NORMAL;
738
            }
739
        }
740 127
    }
741
742
    /**
743
     * @param int  $mask
744
     * @param bool $enable
745
     *
746
     * @return ZipEntry
747
     */
748 49
    private function setGeneralBitFlag($mask, $enable)
749
    {
750 49
        if ($enable) {
751 44
            $this->generalPurposeBitFlags |= $mask;
752
        } else {
753 8
            $this->generalPurposeBitFlags &= ~$mask;
754
        }
755
756 49
        return $this;
757
    }
758
759
    /**
760
     * @param int $mask
761
     *
762
     * @return bool
763
     */
764 129
    private function isSetGeneralBitFlag($mask)
765
    {
766 129
        return ($this->generalPurposeBitFlags & $mask) === $mask;
767
    }
768
769
    /**
770
     * @return bool
771
     */
772 82
    public function isDataDescriptorEnabled()
773
    {
774 82
        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 5
    public function enableDataDescriptor($enabled = true)
783
    {
784 5
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR, (bool) $enabled);
785 5
    }
786
787
    /**
788
     * @param bool $enabled
789
     */
790 25
    public function enableUtf8Name($enabled)
791
    {
792 25
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled);
793 25
    }
794
795
    /**
796
     * @return bool
797
     */
798 3
    public function isUtf8Flag()
799
    {
800 3
        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 105
    public function isEncrypted()
809
    {
810 105
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION);
811
    }
812
813
    /**
814
     * @return bool
815
     */
816 55
    public function isStrongEncryption()
817
    {
818 55
        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 5
    public function disableEncryption()
828
    {
829 5
        $this->setEncrypted(false);
830 5
        $this->removeExtraField(WinZipAesExtraField::HEADER_ID);
831 5
        $this->encryptionMethod = ZipEncryptionMethod::NONE;
832 5
        $this->password = null;
833 5
        $this->extractVersion = self::UNKNOWN;
834
835 5
        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 31
    private function setEncrypted($encrypted)
846
    {
847 31
        $encrypted = (bool) $encrypted;
848 31
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted);
849
850 31
        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 87
    public function getCompressionMethod()
876
    {
877 87
        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 143
    public function setCompressionMethod($compressionMethod)
915
    {
916 143
        $compressionMethod = (int) $compressionMethod;
917
918 143
        if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
919 2
            throw new InvalidArgumentException('method out of range: ' . $compressionMethod);
920
        }
921
922 141
        ZipCompressionMethod::checkSupport($compressionMethod);
923
924 116
        $this->compressionMethod = $compressionMethod;
925 116
        $this->updateCompressionLevel();
926 116
        $this->extractVersion = self::UNKNOWN;
927
928 116
        return $this;
929
    }
930
931
    /**
932
     * Get Unix Timestamp.
933
     *
934
     * @return int
935
     */
936 10
    public function getTime()
937
    {
938 10
        if ($this->getDosTime() === self::UNKNOWN) {
939 1
            return self::UNKNOWN;
940
        }
941
942 9
        return DateTimeConverter::msDosToUnix($this->getDosTime());
943
    }
944
945
    /**
946
     * Get Dos Time.
947
     *
948
     * @return int
949
     */
950 92
    public function getDosTime()
951
    {
952 92
        return $this->dosTime;
953
    }
954
955
    /**
956
     * Set Dos Time.
957
     *
958
     * @param int $dosTime
959
     *
960
     * @return ZipEntry
961
     */
962 104
    public function setDosTime($dosTime)
963
    {
964 104
        $dosTime = (int) $dosTime;
965
966 104
        if (\PHP_INT_SIZE === 8) {
967 104
            if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
968 2
                throw new InvalidArgumentException('DosTime out of range');
969
            }
970
        }
971
972 102
        $this->dosTime = $dosTime;
973
974 102
        return $this;
975
    }
976
977
    /**
978
     * Set time from unix timestamp.
979
     *
980
     * @param int $unixTimestamp
981
     *
982
     * @return ZipEntry
983
     */
984 98
    public function setTime($unixTimestamp)
985
    {
986 98
        if ($unixTimestamp !== self::UNKNOWN) {
987 98
            $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp));
988
        } else {
989 1
            $this->dosTime = 0;
990
        }
991
992 98
        return $this;
993
    }
994
995
    /**
996
     * Returns the external file attributes.
997
     *
998
     * @return int the external file attributes
999
     */
1000 90
    public function getExternalAttributes()
1001
    {
1002 90
        return $this->externalAttributes;
1003
    }
1004
1005
    /**
1006
     * Sets the external file attributes.
1007
     *
1008
     * @param int $externalAttributes the external file attributes
1009
     *
1010
     * @return ZipEntry
1011
     */
1012 129
    public function setExternalAttributes($externalAttributes)
1013
    {
1014 129
        $this->externalAttributes = (int) $externalAttributes;
1015
1016 129
        if (\PHP_INT_SIZE === 8) {
1017 129
            if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
1018 2
                throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
1019
            }
1020
        }
1021
1022 127
        $this->externalAttributes = $externalAttributes;
1023
1024 127
        return $this;
1025
    }
1026
1027
    /**
1028
     * Returns the internal file attributes.
1029
     *
1030
     * @return int the internal file attributes
1031
     */
1032 82
    public function getInternalAttributes()
1033
    {
1034 82
        return $this->internalAttributes;
1035
    }
1036
1037
    /**
1038
     * Sets the internal file attributes.
1039
     *
1040
     * @param int $internalAttributes the internal file attributes
1041
     *
1042
     * @return ZipEntry
1043
     */
1044 4
    public function setInternalAttributes($internalAttributes)
1045
    {
1046 4
        $internalAttributes = (int) $internalAttributes;
1047
1048 4
        if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) {
1049 2
            throw new InvalidArgumentException('internal attributes out of range');
1050
        }
1051 2
        $this->internalAttributes = $internalAttributes;
1052
1053 2
        return $this;
1054
    }
1055
1056
    /**
1057
     * Returns true if and only if this ZIP entry represents a directory entry
1058
     * (i.e. end with '/').
1059
     *
1060
     * @return bool
1061
     */
1062 133
    final public function isDirectory()
1063
    {
1064 133
        return $this->isDirectory;
1065
    }
1066
1067
    /**
1068
     * @return ExtraFieldsCollection
1069
     */
1070 95
    public function getCdExtraFields()
1071
    {
1072 95
        return $this->cdExtraFields;
1073
    }
1074
1075
    /**
1076
     * @param int $headerId
1077
     *
1078
     * @return ZipExtraField|null
1079
     */
1080 95
    public function getCdExtraField($headerId)
1081
    {
1082 95
        return $this->cdExtraFields->get((int) $headerId);
1083
    }
1084
1085
    /**
1086
     * @param ExtraFieldsCollection $cdExtraFields
1087
     *
1088
     * @return ZipEntry
1089
     */
1090 1
    public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields)
1091
    {
1092 1
        $this->cdExtraFields = $cdExtraFields;
1093
1094 1
        return $this;
1095
    }
1096
1097
    /**
1098
     * @return ExtraFieldsCollection
1099
     */
1100 94
    public function getLocalExtraFields()
1101
    {
1102 94
        return $this->localExtraFields;
1103
    }
1104
1105
    /**
1106
     * @param int $headerId
1107
     *
1108
     * @return ZipExtraField|null
1109
     */
1110 99
    public function getLocalExtraField($headerId)
1111
    {
1112 99
        return $this->localExtraFields[(int) $headerId];
1113
    }
1114
1115
    /**
1116
     * @param ExtraFieldsCollection $localExtraFields
1117
     *
1118
     * @return ZipEntry
1119
     */
1120 1
    public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields)
1121
    {
1122 1
        $this->localExtraFields = $localExtraFields;
1123
1124 1
        return $this;
1125
    }
1126
1127
    /**
1128
     * @param int $headerId
1129
     *
1130
     * @return ZipExtraField|null
1131
     */
1132 92
    public function getExtraField($headerId)
1133
    {
1134 92
        $headerId = (int) $headerId;
1135 92
        $local = $this->getLocalExtraField($headerId);
1136
1137 92
        if ($local === null) {
1138 91
            return $this->getCdExtraField($headerId);
1139
        }
1140
1141 16
        return $local;
1142
    }
1143
1144
    /**
1145
     * @param int $headerId
1146
     *
1147
     * @return bool
1148
     */
1149 17
    public function hasExtraField($headerId)
1150
    {
1151 17
        $headerId = (int) $headerId;
1152
1153
        return
1154 17
            isset($this->localExtraFields[$headerId]) ||
1155 17
            isset($this->cdExtraFields[$headerId]);
1156
    }
1157
1158
    /**
1159
     * @param int $headerId
1160
     */
1161 16
    public function removeExtraField($headerId)
1162
    {
1163 16
        $headerId = (int) $headerId;
1164
1165 16
        $this->cdExtraFields->remove($headerId);
1166 16
        $this->localExtraFields->remove($headerId);
1167 16
    }
1168
1169
    /**
1170
     * @param ZipExtraField $zipExtraField
1171
     */
1172 4
    public function addExtraField(ZipExtraField $zipExtraField)
1173
    {
1174 4
        $this->addLocalExtraField($zipExtraField);
1175 4
        $this->addCdExtraField($zipExtraField);
1176 4
    }
1177
1178
    /**
1179
     * @param ZipExtraField $zipExtraField
1180
     */
1181 4
    public function addLocalExtraField(ZipExtraField $zipExtraField)
1182
    {
1183 4
        $this->localExtraFields->add($zipExtraField);
1184 4
    }
1185
1186
    /**
1187
     * @param ZipExtraField $zipExtraField
1188
     */
1189 4
    public function addCdExtraField(ZipExtraField $zipExtraField)
1190
    {
1191 4
        $this->cdExtraFields->add($zipExtraField);
1192 4
    }
1193
1194
    /**
1195
     * Returns comment entry.
1196
     *
1197
     * @return string
1198
     */
1199 82
    public function getComment()
1200
    {
1201 82
        return $this->comment !== null ? $this->comment : '';
1202
    }
1203
1204
    /**
1205
     * Set entry comment.
1206
     *
1207
     * @param string|null $comment
1208
     *
1209
     * @return ZipEntry
1210
     */
1211 87
    public function setComment($comment)
1212
    {
1213 87
        if ($comment !== null) {
1214 5
            $commentLength = \strlen($comment);
1215
1216 5
            if ($commentLength > 0xffff) {
1217 2
                throw new InvalidArgumentException('Comment too long');
1218
            }
1219
1220 3
            if ($this->charset === null && !StringUtil::isASCII($comment)) {
1221 3
                $this->enableUtf8Name(true);
1222
            }
1223
        }
1224 85
        $this->comment = $comment;
1225
1226 85
        return $this;
1227
    }
1228
1229
    /**
1230
     * @return bool
1231
     */
1232 88
    public function isDataDescriptorRequired()
1233
    {
1234 88
        return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN;
1235
    }
1236
1237
    /**
1238
     * Return crc32 content or 0 for WinZip AES v2.
1239
     *
1240
     * @return int
1241
     */
1242 92
    public function getCrc()
1243
    {
1244 92
        return $this->crc;
1245
    }
1246
1247
    /**
1248
     * Set crc32 content.
1249
     *
1250
     * @param int $crc
1251
     *
1252
     * @return ZipEntry
1253
     *
1254
     * @internal
1255
     */
1256 86
    public function setCrc($crc)
1257
    {
1258 86
        $this->crc = (int) $crc;
1259
1260 86
        return $this;
1261
    }
1262
1263
    /**
1264
     * @return string|null
1265
     */
1266 36
    public function getPassword()
1267
    {
1268 36
        return $this->password;
1269
    }
1270
1271
    /**
1272
     * Set password and encryption method from entry.
1273
     *
1274
     * @param string|null $password
1275
     * @param int|null    $encryptionMethod
1276
     *
1277
     * @return ZipEntry
1278
     */
1279 26
    public function setPassword($password, $encryptionMethod = null)
1280
    {
1281 26
        if (!$this->isDirectory) {
1282 25
            if ($password === null || $password === '') {
1283 4
                $this->password = null;
1284 4
                $this->disableEncryption();
1285
            } else {
1286 23
                $this->password = (string) $password;
1287
1288 23
                if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) {
1289 13
                    $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256;
1290
                }
1291
1292 23
                if ($encryptionMethod !== null) {
1293 21
                    $this->setEncryptionMethod($encryptionMethod);
1294
                }
1295 22
                $this->setEncrypted(true);
1296
            }
1297
        }
1298
1299 25
        return $this;
1300
    }
1301
1302
    /**
1303
     * @return int
1304
     */
1305 42
    public function getEncryptionMethod()
1306
    {
1307 42
        return $this->encryptionMethod;
1308
    }
1309
1310
    /**
1311
     * Set encryption method.
1312
     *
1313
     * @param int|null $encryptionMethod
1314
     *
1315
     * @return ZipEntry
1316
     *
1317
     * @see ZipEncryptionMethod::NONE
1318
     * @see ZipEncryptionMethod::PKWARE
1319
     * @see ZipEncryptionMethod::WINZIP_AES_256
1320
     * @see ZipEncryptionMethod::WINZIP_AES_192
1321
     * @see ZipEncryptionMethod::WINZIP_AES_128
1322
     */
1323 32
    public function setEncryptionMethod($encryptionMethod)
1324
    {
1325 32
        if ($encryptionMethod === null) {
1326 1
            $encryptionMethod = ZipEncryptionMethod::NONE;
1327
        }
1328
1329 32
        $encryptionMethod = (int) $encryptionMethod;
1330 32
        ZipEncryptionMethod::checkSupport($encryptionMethod);
1331 28
        $this->encryptionMethod = $encryptionMethod;
1332
1333 28
        $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE);
1334 28
        $this->extractVersion = self::UNKNOWN;
1335
1336 28
        return $this;
1337
    }
1338
1339
    /**
1340
     * @return int
1341
     */
1342 52
    public function getCompressionLevel()
1343
    {
1344 52
        return $this->compressionLevel;
1345
    }
1346
1347
    /**
1348
     * @param int $compressionLevel
1349
     *
1350
     * @return ZipEntry
1351
     */
1352 26
    public function setCompressionLevel($compressionLevel)
1353
    {
1354 26
        $compressionLevel = (int) $compressionLevel;
1355
1356 26
        if ($compressionLevel === self::UNKNOWN) {
1357 1
            $compressionLevel = ZipCompressionLevel::NORMAL;
1358
        }
1359
1360
        if (
1361 26
            $compressionLevel < ZipCompressionLevel::LEVEL_MIN ||
1362 26
            $compressionLevel > ZipCompressionLevel::LEVEL_MAX
1363
        ) {
1364 10
            throw new InvalidArgumentException(
1365
                'Invalid compression level. Minimum level ' .
1366 10
                ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX
1367
            );
1368
        }
1369 16
        $this->compressionLevel = $compressionLevel;
1370
1371 16
        $this->updateGbpfCompLevel();
1372
1373 16
        return $this;
1374
    }
1375
1376
    /**
1377
     * Update general purpose bit flogs.
1378
     */
1379 16
    private function updateGbpfCompLevel()
1380
    {
1381 16
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
1382 16
            $bit1 = false;
1383 16
            $bit2 = false;
1384
1385 16
            switch ($this->compressionLevel) {
1386
                case ZipCompressionLevel::MAXIMUM:
1387 4
                    $bit1 = true;
1388 4
                    break;
1389
1390
                case ZipCompressionLevel::FAST:
1391 2
                    $bit2 = true;
1392 2
                    break;
1393
1394
                case ZipCompressionLevel::SUPER_FAST:
1395 3
                    $bit1 = true;
1396 3
                    $bit2 = true;
1397 3
                    break;
1398
                // default is ZipCompressionLevel::NORMAL
1399
            }
1400
1401 16
            $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0);
1402 16
            $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0);
1403
        }
1404 16
    }
1405
1406
    /**
1407
     * Sets Unix permissions in a way that is understood by Info-Zip's
1408
     * unzip command.
1409
     *
1410
     * @param int $mode mode an int value
1411
     *
1412
     * @return ZipEntry
1413
     */
1414 124
    public function setUnixMode($mode)
1415
    {
1416 124
        $mode = (int) $mode;
1417 124
        $this->setExternalAttributes(
1418
            ($mode << 16)
1419
            // MS-DOS read-only attribute
1420 124
            | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0)
1421
            // MS-DOS directory flag
1422 124
            | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE)
1423
        );
1424 124
        $this->createdOS = ZipPlatform::OS_UNIX;
1425
1426 124
        return $this;
1427
    }
1428
1429
    /**
1430
     * Unix permission.
1431
     *
1432
     * @return int the unix permissions
1433
     */
1434 47
    public function getUnixMode()
1435
    {
1436 47
        $mode = 0;
1437
1438 47
        if ($this->createdOS === ZipPlatform::OS_UNIX) {
1439 41
            $mode = ($this->externalAttributes >> 16) & 0xFFFF;
1440 7
        } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) {
1441
            /** @var AsiExtraField $asiExtraField */
1442 1
            $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID);
1443 1
            $mode = $asiExtraField->getMode();
1444
        }
1445
1446 47
        if ($mode > 0) {
1447 39
            return $mode;
1448
        }
1449
1450 8
        return $this->isDirectory ? 040755 : 0100644;
1451
    }
1452
1453
    /**
1454
     * Offset MUST be considered in decision about ZIP64 format - see
1455
     * description of Data Descriptor in ZIP File Format Specification.
1456
     *
1457
     * @return bool
1458
     */
1459 91
    public function isZip64ExtensionsRequired()
1460
    {
1461 91
        return $this->compressedSize > ZipConstants::ZIP64_MAGIC
1462 91
            || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC;
1463
    }
1464
1465
    /**
1466
     * Returns true if this entry represents a unix symlink,
1467
     * in which case the entry's content contains the target path
1468
     * for the symlink.
1469
     *
1470
     * @return bool true if the entry represents a unix symlink,
1471
     *              false otherwise
1472
     */
1473 21
    public function isUnixSymlink()
1474
    {
1475 21
        return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK;
1476
    }
1477
1478
    /**
1479
     * @return \DateTimeInterface
1480
     */
1481 11
    public function getMTime()
1482
    {
1483
        /** @var NtfsExtraField|null $ntfsExtra */
1484 11
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1485
1486 11
        if ($ntfsExtra !== null) {
1487 3
            return $ntfsExtra->getModifyDateTime();
1488
        }
1489
1490
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1491 10
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1492
1493 10
        if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) {
1494 3
            return $mtime;
1495
        }
1496
1497
        /** @var OldUnixExtraField|null $oldUnixExtra */
1498 9
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1499
1500 9
        if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) {
1501 1
            return $mtime;
1502
        }
1503
1504 9
        $timestamp = $this->getTime();
1505
1506
        try {
1507 9
            return new \DateTimeImmutable('@' . $timestamp);
1508
        } catch (\Exception $e) {
1509
            throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e);
1510
        }
1511
    }
1512
1513
    /**
1514
     * @return \DateTimeInterface|null
1515
     */
1516 12
    public function getATime()
1517
    {
1518
        /** @var NtfsExtraField|null $ntfsExtra */
1519 12
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1520
1521 12
        if ($ntfsExtra !== null) {
1522 3
            return $ntfsExtra->getAccessDateTime();
1523
        }
1524
1525
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1526 11
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1527
1528 11
        if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) {
1529 3
            return $atime;
1530
        }
1531
1532
        /** @var OldUnixExtraField|null $oldUnixExtra */
1533 10
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1534
1535 10
        if ($oldUnixExtra !== null) {
1536 1
            return $oldUnixExtra->getAccessDateTime();
1537
        }
1538
1539 10
        return null;
1540
    }
1541
1542
    /**
1543
     * @return \DateTimeInterface|null
1544
     */
1545 6
    public function getCTime()
1546
    {
1547
        /** @var NtfsExtraField|null $ntfsExtra */
1548 6
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1549
1550 6
        if ($ntfsExtra !== null) {
1551 2
            return $ntfsExtra->getCreateDateTime();
1552
        }
1553
1554
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1555 6
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1556
1557 6
        if ($extendedExtra !== null) {
1558 2
            return $extendedExtra->getCreateDateTime();
1559
        }
1560
1561 6
        return null;
1562
    }
1563
1564 89
    public function __clone()
1565
    {
1566 89
        $this->cdExtraFields = clone $this->cdExtraFields;
1567 89
        $this->localExtraFields = clone $this->localExtraFields;
1568
1569 89
        if ($this->data !== null) {
1570 86
            $this->data = clone $this->data;
1571
        }
1572 89
    }
1573
}
1574