Zip64ExtraField   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Test Coverage

Coverage 71.15%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 89
c 1
b 0
f 1
dl 0
loc 293
ccs 74
cts 104
cp 0.7115
rs 9.36
wmc 38

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 28 5
A setCompressedSize() 0 3 1
A packSizes() 0 13 3
A getHeaderId() 0 3 1
A __construct() 0 10 1
B unpackCentralDirData() 0 43 9
A getLocalHeaderOffset() 0 3 1
A getUncompressedSize() 0 3 1
A unpackLocalFileData() 0 22 3
A getCompressedSize() 0 3 1
A getDiskStart() 0 3 1
A packCentralDirData() 0 13 3
A packLocalFileData() 0 13 5
A setLocalHeaderOffset() 0 3 1
A setUncompressedSize() 0 3 1
A setDiskStart() 0 3 1
1
<?php
2
3
namespace PhpZip\Model\Extra\Fields;
4
5
use PhpZip\Constants\ZipConstants;
6
use PhpZip\Exception\RuntimeException;
7
use PhpZip\Exception\ZipException;
8
use PhpZip\Model\Extra\ZipExtraField;
9
use PhpZip\Model\ZipEntry;
10
use PhpZip\Util\PackUtil;
11
12
/**
13
 * ZIP64 Extra Field.
14
 *
15
 * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
16
 */
17
class Zip64ExtraField implements ZipExtraField
18
{
19
    /** @var int The Header ID for a ZIP64 Extended Information Extra Field. */
20
    const HEADER_ID = 0x0001;
21
22
    /** @var int|null */
23
    private $uncompressedSize;
24
25
    /** @var int|null */
26
    private $compressedSize;
27
28
    /** @var int|null */
29
    private $localHeaderOffset;
30
31
    /** @var int|null */
32
    private $diskStart;
33
34
    /**
35
     * Zip64ExtraField constructor.
36
     *
37
     * @param int|null $uncompressedSize
38
     * @param int|null $compressedSize
39
     * @param int|null $localHeaderOffset
40
     * @param int|null $diskStart
41
     */
42 4
    public function __construct(
43
        $uncompressedSize = null,
44
        $compressedSize = null,
45
        $localHeaderOffset = null,
46
        $diskStart = null
47
    ) {
48 4
        $this->uncompressedSize = $uncompressedSize;
49 4
        $this->compressedSize = $compressedSize;
50 4
        $this->localHeaderOffset = $localHeaderOffset;
51 4
        $this->diskStart = $diskStart;
52 4
    }
53
54
    /**
55
     * Returns the Header ID (type) of this Extra Field.
56
     * The Header ID is an unsigned short integer (two bytes)
57
     * which must be constant during the life cycle of this object.
58
     *
59
     * @return int
60
     */
61 3
    public function getHeaderId()
62
    {
63 3
        return self::HEADER_ID;
64
    }
65
66
    /**
67
     * Populate data from this array as if it was in local file data.
68
     *
69
     * @param string        $buffer the buffer to read data from
70
     * @param ZipEntry|null $entry
71
     *
72
     * @throws ZipException on error
73
     *
74
     * @return Zip64ExtraField
75
     */
76 1
    public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
77
    {
78 1
        $length = \strlen($buffer);
79
80 1
        if ($length === 0) {
81
            // no local file data at all, may happen if an archive
82
            // only holds a ZIP64 extended information extra field
83
            // inside the central directory but not inside the local
84
            // file header
85
            return new self();
86
        }
87
88 1
        if ($length < 16) {
89
            throw new ZipException(
90
                'Zip64 extended information must contain both size values in the local file header.'
91
            );
92
        }
93
94 1
        $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, 0, 8));
95 1
        $compressedSize = PackUtil::unpackLongLE(substr($buffer, 8, 8));
96
97 1
        return new self($uncompressedSize, $compressedSize);
98
    }
99
100
    /**
101
     * Populate data from this array as if it was in central directory data.
102
     *
103
     * @param string        $buffer the buffer to read data from
104
     * @param ZipEntry|null $entry
105
     *
106
     * @throws ZipException
107
     *
108
     * @return Zip64ExtraField
109
     */
110 2
    public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
111
    {
112 2
        if ($entry === null) {
113
            throw new RuntimeException('zipEntry is null');
114
        }
115
116 2
        $length = \strlen($buffer);
117 2
        $remaining = $length;
118
119 2
        $uncompressedSize = null;
120 2
        $compressedSize = null;
121 2
        $localHeaderOffset = null;
122 2
        $diskStart = null;
123
124 2
        if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) {
125 1
            if ($remaining < 8) {
126
                throw new ZipException('ZIP64 extension corrupt (no uncompressed size).');
127
            }
128 1
            $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
129 1
            $remaining -= 8;
130
        }
131
132 2
        if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) {
133 1
            if ($remaining < 8) {
134
                throw new ZipException('ZIP64 extension corrupt (no compressed size).');
135
            }
136 1
            $compressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
137 1
            $remaining -= 8;
138
        }
139
140 2
        if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) {
141 1
            if ($remaining < 8) {
142
                throw new ZipException('ZIP64 extension corrupt (no relative local header offset).');
143
            }
144 1
            $localHeaderOffset = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
145 1
            $remaining -= 8;
146
        }
147
148 2
        if ($remaining === 4) {
149
            $diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1];
150
        }
151
152 2
        return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart);
153
    }
154
155
    /**
156
     * The actual data to put into local file data - without Header-ID
157
     * or length specifier.
158
     *
159
     * @return string the data
160
     */
161 1
    public function packLocalFileData()
162
    {
163 1
        if ($this->uncompressedSize !== null || $this->compressedSize !== null) {
164 1
            if ($this->uncompressedSize === null || $this->compressedSize === null) {
165
                throw new \InvalidArgumentException(
166
                    'Zip64 extended information must contain both size values in the local file header.'
167
                );
168
            }
169
170 1
            return $this->packSizes();
171
        }
172
173
        return '';
174
    }
175
176
    /**
177
     * @return string
178
     */
179 3
    private function packSizes()
180
    {
181 3
        $data = '';
182
183 3
        if ($this->uncompressedSize !== null) {
184 2
            $data .= PackUtil::packLongLE($this->uncompressedSize);
185
        }
186
187 3
        if ($this->compressedSize !== null) {
188 2
            $data .= PackUtil::packLongLE($this->compressedSize);
189
        }
190
191 3
        return $data;
192
    }
193
194
    /**
195
     * The actual data to put into central directory - without Header-ID or
196
     * length specifier.
197
     *
198
     * @return string the data
199
     */
200 2
    public function packCentralDirData()
201
    {
202 2
        $data = $this->packSizes();
203
204 2
        if ($this->localHeaderOffset !== null) {
205 1
            $data .= PackUtil::packLongLE($this->localHeaderOffset);
206
        }
207
208 2
        if ($this->diskStart !== null) {
209
            $data .= pack('V', $this->diskStart);
210
        }
211
212 2
        return $data;
213
    }
214
215
    /**
216
     * @return int|null
217
     */
218 4
    public function getUncompressedSize()
219
    {
220 4
        return $this->uncompressedSize;
221
    }
222
223
    /**
224
     * @param int|null $uncompressedSize
225
     */
226 1
    public function setUncompressedSize($uncompressedSize)
227
    {
228 1
        $this->uncompressedSize = $uncompressedSize;
229 1
    }
230
231
    /**
232
     * @return int|null
233
     */
234 4
    public function getCompressedSize()
235
    {
236 4
        return $this->compressedSize;
237
    }
238
239
    /**
240
     * @param int|null $compressedSize
241
     */
242 1
    public function setCompressedSize($compressedSize)
243
    {
244 1
        $this->compressedSize = $compressedSize;
245 1
    }
246
247
    /**
248
     * @return int|null
249
     */
250 4
    public function getLocalHeaderOffset()
251
    {
252 4
        return $this->localHeaderOffset;
253
    }
254
255
    /**
256
     * @param int|null $localHeaderOffset
257
     */
258 1
    public function setLocalHeaderOffset($localHeaderOffset)
259
    {
260 1
        $this->localHeaderOffset = $localHeaderOffset;
261 1
    }
262
263
    /**
264
     * @return int|null
265
     */
266 4
    public function getDiskStart()
267
    {
268 4
        return $this->diskStart;
269
    }
270
271
    /**
272
     * @param int|null $diskStart
273
     */
274 1
    public function setDiskStart($diskStart)
275
    {
276 1
        $this->diskStart = $diskStart;
277 1
    }
278
279
    /**
280
     * @return string
281
     */
282
    public function __toString()
283
    {
284
        $args = [self::HEADER_ID];
285
        $format = '0x%04x ZIP64: ';
286
        $formats = [];
287
288
        if ($this->uncompressedSize !== null) {
289
            $formats[] = 'SIZE=%d';
290
            $args[] = $this->uncompressedSize;
291
        }
292
293
        if ($this->compressedSize !== null) {
294
            $formats[] = 'COMP_SIZE=%d';
295
            $args[] = $this->compressedSize;
296
        }
297
298
        if ($this->localHeaderOffset !== null) {
299
            $formats[] = 'OFFSET=%d';
300
            $args[] = $this->localHeaderOffset;
301
        }
302
303
        if ($this->diskStart !== null) {
304
            $formats[] = 'DISK_START=%d';
305
            $args[] = $this->diskStart;
306
        }
307
        $format .= implode(' ', $formats);
308
309
        return vsprintf($format, $args);
310
    }
311
}
312