LocalFileHeader::setFileName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 7
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 7
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace morgue\zip;
4
5
use morgue\archive\ArchiveEntry;
6
7
final class LocalFileHeader
8
{
9
    const SIGNATURE = 0x504b0304;
10
11
    /// Minimum length of this entry if neither file name not extra field are set
12
    const MIN_LENGTH = 30;
13
14
    /// Maximum length of this entry file name and extra field have the maximum allowed length
15
    const MAX_LENGTH = self::MIN_LENGTH + self::FILE_NAME_MAX_LENGTH + self::EXTRA_FIELD_MAX_LENGTH;
16
17
    /// File name can not be longer than this (the length field has only 2 bytes)
18
    const FILE_NAME_MAX_LENGTH = (255 * 255) - 1;
19
20
    /// Extra field can not be longer than this (the length field has only 2 bytes)
21
    const EXTRA_FIELD_MAX_LENGTH = (255 * 255) - 1;
22
23
    /**
24
     * @var int
25
     */
26
    private $versionNeededToExtract;
27
28
    /**
29
     * @var int
30
     */
31
    private $generalPurposeBitFlags;
32
33
    /**
34
     * @var int
35
     */
36
    private $compressionMethod;
37
38
    /**
39
     * @var int
40
     */
41
    private $lastModificationFileDate;
42
43
    /**
44
     * @var int
45
     */
46
    private $lastModificationFileTime;
47
48
    /**
49
     * @var \DateTimeInterface
50
     */
51
    private $lastModification;
52
53
    /**
54
     * @var int
55
     */
56
    private $crc32;
57
58
    /**
59
     * @var int
60
     */
61
    private $compressedSize;
62
63
    /**
64
     * @var int
65
     */
66
    private $uncompressedSize;
67
68
    /**
69
     * @var int
70
     */
71
    private $fileNameLength;
72
73
    /**
74
     * @var int
75
     */
76
    private $extraFieldLength;
77
78
    /**
79
     * @var string
80
     */
81
    private $fileName = "";
82
83
    /**
84
     * @var string
85
     */
86
    private $extraField = "";
87
88
    /**
89
     * @var bool
90
     */
91
    private $requireAdditionalData = false;
92
93 322
    public function __construct(
94
        int $versionNeededToExtract,
95
        int $generalPurposeBitFlags,
96
        int $compressionMethod,
97
        int $lastModificationFileTime,
98
        int $lastModificationFileDate,
99
        int $crc32,
100
        int $compressedSize,
101
        int $uncompressedSize,
102
        string $fileName = null,
103
        string $extraField = null
104
    ) {
105 322
        $this->versionNeededToExtract = $versionNeededToExtract;
106 322
        $this->generalPurposeBitFlags = $generalPurposeBitFlags;
107 322
        $this->compressionMethod = $compressionMethod;
108 322
        $this->lastModificationFileTime = $lastModificationFileTime;
109 322
        $this->lastModificationFileDate = $lastModificationFileDate;
110 322
        $this->crc32 = $crc32;
111 322
        $this->compressedSize = $compressedSize;
112 322
        $this->uncompressedSize = $uncompressedSize;
113
114 322
        if ($fileName !== null) {
115
            $this->fileName = $fileName;
116
            $this->fileNameLength = \strlen($this->fileName);
117
        }
118 322
        if ($extraField !== null) {
119
            $this->extraField = $extraField;
120
            $this->extraFieldLength = \strlen($this->extraField);
121
        }
122 322
    }
123
124
    /**
125
     * Create the binary on disk representation
126
     *
127
     * @return string
128
     */
129
    public function marshal() : string
130
    {
131
        return \pack(
132
                'NvvvvvVVVvv',
133
                self::SIGNATURE,
134
                $this->versionNeededToExtract,
135
                $this->generalPurposeBitFlags,
136
                $this->compressionMethod,
137
                $this->lastModificationFileTime,
138
                $this->lastModificationFileDate,
139
                $this->crc32,
140
                $this->compressedSize,
141
                $this->uncompressedSize,
142
                \strlen($this->fileName),
143
                \strlen($this->extraField)
144
            )
145
            . $this->fileName
146
            . $this->extraField
147
            ;
148
    }
149
150
    /**
151
     * Parse the local file header from a binary string.
152
     * Check $requireAdditionalData to check if parseAdditionalData() must be called to parse additional fields.
153
     *
154
     * @param string $input
155
     * @param int $offset Start at this position inside the string
156
     * @return static
157
     */
158 321
    public static function parse(string $input, int $offset = 0)
159
    {
160 321
        if (\strlen($input) < ($offset+self::MIN_LENGTH)) {
161
            throw new \InvalidArgumentException("Not enough data to parse local file header!");
162
        }
163
164 321
        $parsed = \unpack(
165
            'Nsignature'
166
            . '/vversionNeededToExtract'
167
            . '/vgeneralPurposeBitFlags'
168
            . '/vcompressionMethod'
169
            . '/vlastModificationFileTime'
170
            . '/vlastModificationFileDate'
171
            . '/Vcrc32'
172
            . '/VcompressedSize'
173
            . '/VuncompressedSize'
174
            . '/vfileNameLength'
175 321
            . '/vextraFieldLength',
176 321
            ($offset ? \substr($input, $offset) : $input)
177
        );
178 321
        if ($parsed['signature'] !== self::SIGNATURE) {
179
            throw new \InvalidArgumentException("Invalid signature for local file header!");
180
        }
181
182 321
        $localFileHeader = new static(
183 321
            $parsed['versionNeededToExtract'],
184 321
            $parsed['generalPurposeBitFlags'],
185 321
            $parsed['compressionMethod'],
186 321
            $parsed['lastModificationFileTime'],
187 321
            $parsed['lastModificationFileDate'],
188 321
            $parsed['crc32'],
189 321
            $parsed['compressedSize'],
190 321
            $parsed['uncompressedSize']
191
        );
192 321
        $localFileHeader->fileNameLength = $parsed['fileNameLength'];
193 321
        $localFileHeader->extraFieldLength = $parsed['extraFieldLength'];
194 321
        $localFileHeader->requireAdditionalData = $localFileHeader->fileNameLength + $localFileHeader->extraFieldLength;
195
196 321
        return $localFileHeader;
197
    }
198
199
    /**
200
     * After a new object has been created by parse(), this method must be called to initialize the file name and extra field entries which have dynamic field length.
201
     * The required number of bytes is written to the $requireAdditionalData attribute by parse().
202
     *
203
     * @param string $input
204
     * @param int $offset
205
     * @return int
206
     */
207 321
    public function parseAdditionalData(string $input, int $offset = 0) : int
208
    {
209 321
        if (!$this->requireAdditionalData) {
210
            throw new \BadMethodCallException("No additional data required!");
211
        }
212
213 321
        if (\strlen($input) < ($offset + $this->fileNameLength + $this->extraFieldLength)) {
214
            throw new \InvalidArgumentException("Not enough input to parse additional data!");
215
        }
216
217 321
        $this->fileName = \substr($input, $offset, $this->fileNameLength);
218 321
        $this->extraField = bin2hex(\substr($input, $offset+$this->fileNameLength, $this->extraFieldLength));
219 321
        $this->requireAdditionalData = null;
220
221 321
        return $this->fileNameLength + $this->extraFieldLength;
222
    }
223
224
    /**
225
     * Initialize a new local file header from the supplied archive entry object
226
     *
227
     * @param ArchiveEntry $archiveEntry
228
     * @return LocalFileHeader
229
     */
230
    public static function createFromArchiveEntry(ArchiveEntry $archiveEntry) : self
231
    {
232
        list($modificationTime, $modificationDate) = dateTime2Dos($archiveEntry->getModificationTime());
233
234
        return new self(
235
            0,
236
            0,
237
            COMPRESSION_METHOD_REVERSE_MAPPING[$archiveEntry->getTargetCompressionMethod()],
238
            $modificationTime,
239
            $modificationDate,
240
            $archiveEntry->getChecksumCrc32(),
241
            $archiveEntry->getTargetSize(),
242
            $archiveEntry->getUncompressedSize(),
243
            $archiveEntry->getName(),
244
            null
245
        );
246
    }
247
248
    /**
249
     * The number of bytes the fields with variable length require.
250
     *
251
     * @return int
252
     */
253 321
    public function getVariableLength(): int
254
    {
255 321
        return $this->fileNameLength + $this->extraFieldLength;
256
    }
257
258
    /**
259
     * @return int
260
     */
261 1
    public function getVersionNeededToExtract(): int
262
    {
263 1
        return $this->versionNeededToExtract;
264
    }
265
266
    /**
267
     * @param int $versionNeededToExtract
268
     * @return LocalFileHeader
269
     */
270 1
    public function setVersionNeededToExtract(int $versionNeededToExtract): LocalFileHeader
271
    {
272 1
        $obj = clone $this;
273 1
        $obj->versionNeededToExtract = $versionNeededToExtract;
274 1
        return $obj;
275
    }
276
277
    /**
278
     * @return int
279
     */
280 1
    public function getGeneralPurposeBitFlags(): int
281
    {
282 1
        return $this->generalPurposeBitFlags;
283
    }
284
285
    /**
286
     * @param int $generalPurposeBitFlags
287
     * @return LocalFileHeader
288
     */
289 1
    public function setGeneralPurposeBitFlags(int $generalPurposeBitFlags): LocalFileHeader
290
    {
291 1
        $obj = clone $this;
292 1
        $obj->generalPurposeBitFlags = $generalPurposeBitFlags;
293 1
        return $obj;
294
    }
295
296
    /**
297
     * @return int
298
     */
299 1
    public function getCompressionMethod(): int
300
    {
301 1
        return $this->compressionMethod;
302
    }
303
304
    /**
305
     * @param int $compressionMethod
306
     * @return LocalFileHeader
307
     */
308 1
    public function setCompressionMethod(int $compressionMethod): LocalFileHeader
309
    {
310 1
        $obj = clone $this;
311 1
        $obj->compressionMethod = $compressionMethod;
312 1
        return $obj;
313
    }
314
315
    /**
316
     * @return int
317
     */
318 1
    public function getLastModificationFileDate(): int
319
    {
320 1
        return $this->lastModificationFileDate;
321
    }
322
323
    /**
324
     * @param int $lastModificationFileDate
325
     * @return LocalFileHeader
326
     */
327 1
    public function setLastModificationFileDate(int $lastModificationFileDate): LocalFileHeader
328
    {
329 1
        $obj = clone $this;
330 1
        $obj->lastModificationFileDate = $lastModificationFileDate;
331 1
        return $obj;
332
    }
333
334
    /**
335
     * @return int
336
     */
337 1
    public function getLastModificationFileTime(): int
338
    {
339 1
        return $this->lastModificationFileTime;
340
    }
341
342
    /**
343
     * @param int $lastModificationFileTime
344
     * @return LocalFileHeader
345
     */
346 1
    public function setLastModificationFileTime(int $lastModificationFileTime): LocalFileHeader
347
    {
348 1
        $obj = clone $this;
349 1
        $obj->lastModificationFileTime = $lastModificationFileTime;
350 1
        return $obj;
351
    }
352
353
    /**
354
     * @return \DateTimeInterface
355
     * @throws \Exception
356
     */
357
    public function getLastModification(): \DateTimeInterface
358
    {
359
        return dos2DateTime($this->lastModificationFileTime, $this->lastModificationFileDate);
360
    }
361
362
    /**
363
     * @param \DateTimeInterface $lastModification
364
     * @return LocalFileHeader
365
     */
366
    public function setLastModification(\DateTimeInterface $lastModification): LocalFileHeader
367
    {
368
        $obj = clone $this;
369
        list($obj->lastModificationFileTime, $obj->lastModificationFileDate) = dateTime2Dos($lastModification);
370
        return $obj;
371
    }
372
373
    /**
374
     * @return int
375
     */
376 1
    public function getCrc32(): int
377
    {
378 1
        return $this->crc32;
379
    }
380
381
    /**
382
     * @param int $crc32
383
     * @return LocalFileHeader
384
     */
385 1
    public function setCrc32(int $crc32): LocalFileHeader
386
    {
387 1
        $obj = clone $this;
388 1
        $obj->crc32 = $crc32;
389 1
        return $obj;
390
    }
391
392
    /**
393
     * @return int
394
     */
395 1
    public function getCompressedSize(): int
396
    {
397 1
        return $this->compressedSize;
398
    }
399
400
    /**
401
     * @param int $compressedSize
402
     * @return LocalFileHeader
403
     */
404 1
    public function setCompressedSize(int $compressedSize): LocalFileHeader
405
    {
406 1
        $obj = clone $this;
407 1
        $obj->compressedSize = $compressedSize;
408 1
        return $obj;
409
    }
410
411
    /**
412
     * @return int
413
     */
414 1
    public function getUncompressedSize(): int
415
    {
416 1
        return $this->uncompressedSize;
417
    }
418
419
    /**
420
     * @param int $uncompressedSize
421
     * @return LocalFileHeader
422
     */
423 1
    public function setUncompressedSize(int $uncompressedSize): LocalFileHeader
424
    {
425 1
        $obj = clone $this;
426 1
        $obj->uncompressedSize = $uncompressedSize;
427 1
        return $obj;
428
    }
429
430
    /**
431
     * @return int
432
     */
433 1
    public function getFileNameLength(): int
434
    {
435 1
        return $this->fileNameLength;
436
    }
437
438
    /**
439
     * @return int
440
     */
441 1
    public function getExtraFieldLength(): int
442
    {
443 1
        return $this->extraFieldLength;
444
    }
445
446
    /**
447
     * @return string
448
     */
449 1
    public function getFileName(): string
450
    {
451 1
        return $this->fileName;
452
    }
453
454
    /**
455
     * @param string $fileName
456
     * @return LocalFileHeader
457
     */
458 1 View Code Duplication
    public function setFileName(string $fileName): LocalFileHeader
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
459
    {
460 1
        $obj = clone $this;
461 1
        $obj->fileName = $fileName;
462 1
        $obj->fileNameLength = \strlen($fileName);
463 1
        return $obj;
464
    }
465
466
    /**
467
     * @return string
468
     */
469 1
    public function getExtraField(): string
470
    {
471 1
        return $this->extraField;
472
    }
473
474
    /**
475
     * @param string $extraField
476
     * @return LocalFileHeader
477
     */
478 1 View Code Duplication
    public function setExtraField(string $extraField): LocalFileHeader
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
    {
480 1
        $obj = clone $this;
481 1
        $obj->extraField = $extraField;
482 1
        $obj->extraFieldLength = \strlen($extraField);
483 1
        return $obj;
484
    }
485
}
486