1 | <?php |
||||
2 | |||||
3 | namespace PhpZip\IO; |
||||
4 | |||||
5 | use PhpZip\Constants\DosCodePage; |
||||
6 | use PhpZip\Constants\ZipCompressionMethod; |
||||
7 | use PhpZip\Constants\ZipConstants; |
||||
8 | use PhpZip\Constants\ZipEncryptionMethod; |
||||
9 | use PhpZip\Constants\ZipPlatform; |
||||
10 | use PhpZip\Constants\ZipVersion; |
||||
11 | use PhpZip\Exception\ZipException; |
||||
12 | use PhpZip\Exception\ZipUnsupportMethodException; |
||||
13 | use PhpZip\IO\Filter\Cipher\Pkware\PKEncryptionStreamFilter; |
||||
14 | use PhpZip\IO\Filter\Cipher\WinZipAes\WinZipAesEncryptionStreamFilter; |
||||
15 | use PhpZip\Model\Data\ZipSourceFileData; |
||||
16 | use PhpZip\Model\Extra\Fields\ApkAlignmentExtraField; |
||||
17 | use PhpZip\Model\Extra\Fields\WinZipAesExtraField; |
||||
18 | use PhpZip\Model\Extra\Fields\Zip64ExtraField; |
||||
19 | use PhpZip\Model\ZipContainer; |
||||
20 | use PhpZip\Model\ZipEntry; |
||||
21 | use PhpZip\Util\PackUtil; |
||||
22 | use PhpZip\Util\StringUtil; |
||||
23 | |||||
24 | /** |
||||
25 | * Class ZipWriter. |
||||
26 | */ |
||||
27 | class ZipWriter |
||||
28 | { |
||||
29 | /** @var int Chunk read size */ |
||||
30 | const CHUNK_SIZE = 8192; |
||||
31 | |||||
32 | /** @var ZipContainer */ |
||||
33 | protected $zipContainer; |
||||
34 | |||||
35 | /** |
||||
36 | * ZipWriter constructor. |
||||
37 | * |
||||
38 | * @param ZipContainer $container |
||||
39 | */ |
||||
40 | 136 | public function __construct(ZipContainer $container) |
|||
41 | { |
||||
42 | // we clone the container so that the changes made to |
||||
43 | // it do not affect the data in the ZipFile class |
||||
44 | 136 | $this->zipContainer = clone $container; |
|||
45 | 136 | } |
|||
46 | |||||
47 | /** |
||||
48 | * @param resource $outStream |
||||
49 | * |
||||
50 | * @throws ZipException |
||||
51 | */ |
||||
52 | 136 | public function write($outStream) |
|||
53 | { |
||||
54 | 136 | if (!\is_resource($outStream)) { |
|||
55 | throw new \InvalidArgumentException('$outStream must be resource'); |
||||
56 | } |
||||
57 | 136 | $this->beforeWrite(); |
|||
58 | 136 | $this->writeLocalBlock($outStream); |
|||
59 | 136 | $cdOffset = ftell($outStream); |
|||
60 | 136 | $this->writeCentralDirectoryBlock($outStream); |
|||
61 | 136 | $cdSize = ftell($outStream) - $cdOffset; |
|||
62 | 136 | $this->writeEndOfCentralDirectoryBlock($outStream, $cdOffset, $cdSize); |
|||
63 | 136 | } |
|||
64 | |||||
65 | 136 | protected function beforeWrite() |
|||
66 | { |
||||
67 | 136 | } |
|||
68 | |||||
69 | /** |
||||
70 | * @param resource $outStream |
||||
71 | * |
||||
72 | * @throws ZipException |
||||
73 | */ |
||||
74 | 136 | protected function writeLocalBlock($outStream) |
|||
75 | { |
||||
76 | 136 | $zipEntries = $this->zipContainer->getEntries(); |
|||
77 | |||||
78 | 136 | foreach ($zipEntries as $zipEntry) { |
|||
79 | 134 | $this->writeLocalHeader($outStream, $zipEntry); |
|||
80 | 134 | $this->writeData($outStream, $zipEntry); |
|||
81 | |||||
82 | 134 | if ($zipEntry->isDataDescriptorEnabled()) { |
|||
83 | 4 | $this->writeDataDescriptor($outStream, $zipEntry); |
|||
84 | } |
||||
85 | } |
||||
86 | 136 | } |
|||
87 | |||||
88 | /** |
||||
89 | * @param resource $outStream |
||||
90 | * @param ZipEntry $entry |
||||
91 | * |
||||
92 | * @throws ZipException |
||||
93 | */ |
||||
94 | 134 | protected function writeLocalHeader($outStream, ZipEntry $entry) |
|||
95 | { |
||||
96 | // todo in 4.0 version move zipalign functional to ApkWriter class |
||||
97 | 134 | if ($this->zipContainer->isZipAlign()) { |
|||
98 | 4 | $this->zipAlign($outStream, $entry); |
|||
99 | } |
||||
100 | |||||
101 | 134 | $relativeOffset = ftell($outStream); |
|||
102 | 134 | $entry->setLocalHeaderOffset($relativeOffset); |
|||
103 | |||||
104 | 134 | if ($entry->isEncrypted() && $entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) { |
|||
105 | 4 | $entry->enableDataDescriptor(true); |
|||
106 | } |
||||
107 | |||||
108 | 134 | $dd = $entry->isDataDescriptorRequired() || |
|||
109 | 134 | $entry->isDataDescriptorEnabled(); |
|||
110 | |||||
111 | 134 | $compressedSize = $entry->getCompressedSize(); |
|||
112 | 134 | $uncompressedSize = $entry->getUncompressedSize(); |
|||
113 | |||||
114 | 134 | $entry->getLocalExtraFields()->remove(Zip64ExtraField::HEADER_ID); |
|||
115 | |||||
116 | 134 | if ($compressedSize > ZipConstants::ZIP64_MAGIC || $uncompressedSize > ZipConstants::ZIP64_MAGIC) { |
|||
117 | $entry->getLocalExtraFields()->add( |
||||
118 | new Zip64ExtraField($uncompressedSize, $compressedSize) |
||||
119 | ); |
||||
120 | |||||
121 | $compressedSize = ZipConstants::ZIP64_MAGIC; |
||||
122 | $uncompressedSize = ZipConstants::ZIP64_MAGIC; |
||||
123 | } |
||||
124 | |||||
125 | 134 | $compressionMethod = $entry->getCompressionMethod(); |
|||
126 | 134 | $crc = $entry->getCrc(); |
|||
127 | |||||
128 | 134 | if ($entry->isEncrypted() && ZipEncryptionMethod::isWinZipAesMethod($entry->getEncryptionMethod())) { |
|||
129 | /** @var WinZipAesExtraField|null $winZipAesExtra */ |
||||
130 | 9 | $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); |
|||
131 | |||||
132 | 9 | if ($winZipAesExtra === null) { |
|||
133 | 8 | $winZipAesExtra = WinZipAesExtraField::create($entry); |
|||
134 | } |
||||
135 | |||||
136 | 9 | if ($winZipAesExtra->isV2()) { |
|||
137 | 7 | $crc = 0; |
|||
138 | } |
||||
139 | 9 | $compressionMethod = ZipCompressionMethod::WINZIP_AES; |
|||
140 | } |
||||
141 | |||||
142 | 134 | $extra = $this->getExtraFieldsContents($entry, true); |
|||
143 | 134 | $name = $entry->getName(); |
|||
144 | 134 | $dosCharset = $entry->getCharset(); |
|||
145 | |||||
146 | 134 | if ($dosCharset !== null && !$entry->isUtf8Flag()) { |
|||
147 | $name = DosCodePage::fromUTF8($name, $dosCharset); |
||||
148 | } |
||||
149 | |||||
150 | 134 | $nameLength = \strlen($name); |
|||
151 | 134 | $extraLength = \strlen($extra); |
|||
152 | |||||
153 | 134 | $size = $nameLength + $extraLength; |
|||
154 | |||||
155 | 134 | if ($size > 0xffff) { |
|||
156 | throw new ZipException( |
||||
157 | sprintf( |
||||
158 | '%s (the total size of %s bytes for the name, extra fields and comment exceeds the maximum size of %d bytes)', |
||||
159 | $entry->getName(), |
||||
160 | $size, |
||||
161 | 0xffff |
||||
162 | ) |
||||
163 | ); |
||||
164 | } |
||||
165 | |||||
166 | 134 | $extractedBy = ($entry->getExtractedOS() << 8) | $entry->getExtractVersion(); |
|||
167 | |||||
168 | 134 | fwrite( |
|||
169 | 134 | $outStream, |
|||
170 | pack( |
||||
171 | 134 | 'VvvvVVVVvv', |
|||
172 | // local file header signature 4 bytes (0x04034b50) |
||||
173 | 134 | ZipConstants::LOCAL_FILE_HEADER, |
|||
174 | // version needed to extract 2 bytes |
||||
175 | $extractedBy, |
||||
176 | // general purpose bit flag 2 bytes |
||||
177 | 134 | $entry->getGeneralPurposeBitFlags(), |
|||
178 | // compression method 2 bytes |
||||
179 | $compressionMethod, |
||||
180 | // last mod file time 2 bytes |
||||
181 | // last mod file date 2 bytes |
||||
182 | 134 | $entry->getDosTime(), |
|||
183 | // crc-32 4 bytes |
||||
184 | 134 | $dd ? 0 : $crc, |
|||
185 | // compressed size 4 bytes |
||||
186 | 134 | $dd ? 0 : $compressedSize, |
|||
187 | // uncompressed size 4 bytes |
||||
188 | 134 | $dd ? 0 : $uncompressedSize, |
|||
189 | // file name length 2 bytes |
||||
190 | $nameLength, |
||||
191 | // extra field length 2 bytes |
||||
192 | $extraLength |
||||
193 | ) |
||||
194 | ); |
||||
195 | |||||
196 | 134 | if ($nameLength > 0) { |
|||
197 | 134 | fwrite($outStream, $name); |
|||
198 | } |
||||
199 | |||||
200 | 134 | if ($extraLength > 0) { |
|||
201 | 15 | fwrite($outStream, $extra); |
|||
202 | } |
||||
203 | 134 | } |
|||
204 | |||||
205 | /** |
||||
206 | * @param resource $outStream |
||||
207 | * @param ZipEntry $entry |
||||
208 | * |
||||
209 | * @throws ZipException |
||||
210 | */ |
||||
211 | 4 | private function zipAlign($outStream, ZipEntry $entry) |
|||
212 | { |
||||
213 | 4 | if (!$entry->isDirectory() && $entry->getCompressionMethod() === ZipCompressionMethod::STORED) { |
|||
214 | 4 | $entry->removeExtraField(ApkAlignmentExtraField::HEADER_ID); |
|||
215 | |||||
216 | 4 | $extra = $this->getExtraFieldsContents($entry, true); |
|||
217 | 4 | $extraLength = \strlen($extra); |
|||
218 | 4 | $name = $entry->getName(); |
|||
219 | |||||
220 | 4 | $dosCharset = $entry->getCharset(); |
|||
221 | |||||
222 | 4 | if ($dosCharset !== null && !$entry->isUtf8Flag()) { |
|||
223 | $name = DosCodePage::fromUTF8($name, $dosCharset); |
||||
224 | } |
||||
225 | 4 | $nameLength = \strlen($name); |
|||
226 | |||||
227 | 4 | $multiple = ApkAlignmentExtraField::ALIGNMENT_BYTES; |
|||
228 | |||||
229 | 4 | if (StringUtil::endsWith($name, '.so')) { |
|||
230 | $multiple = ApkAlignmentExtraField::COMMON_PAGE_ALIGNMENT_BYTES; |
||||
231 | } |
||||
232 | |||||
233 | 4 | $offset = ftell($outStream); |
|||
234 | |||||
235 | $dataMinStartOffset = |
||||
236 | $offset + |
||||
237 | 4 | ZipConstants::LFH_FILENAME_POS + |
|||
238 | 4 | $extraLength + |
|||
239 | 4 | $nameLength; |
|||
240 | |||||
241 | $padding = |
||||
242 | 4 | ($multiple - ($dataMinStartOffset % $multiple)) |
|||
243 | 4 | % $multiple; |
|||
244 | |||||
245 | 4 | if ($padding > 0) { |
|||
246 | 4 | $dataMinStartOffset += ApkAlignmentExtraField::MIN_SIZE; |
|||
247 | $padding = |
||||
248 | 4 | ($multiple - ($dataMinStartOffset % $multiple)) |
|||
249 | 4 | % $multiple; |
|||
250 | |||||
251 | 4 | $entry->getLocalExtraFields()->add( |
|||
252 | 4 | new ApkAlignmentExtraField($multiple, $padding) |
|||
253 | ); |
||||
254 | } |
||||
255 | } |
||||
256 | 4 | } |
|||
257 | |||||
258 | /** |
||||
259 | * Merges the local file data fields of the given ZipExtraFields. |
||||
260 | * |
||||
261 | * @param ZipEntry $entry |
||||
262 | * @param bool $local |
||||
263 | * |
||||
264 | * @throws ZipException |
||||
265 | * |
||||
266 | * @return string |
||||
267 | */ |
||||
268 | 134 | protected function getExtraFieldsContents(ZipEntry $entry, $local) |
|||
269 | { |
||||
270 | 134 | $local = (bool) $local; |
|||
271 | 134 | $collection = $local ? |
|||
272 | 134 | $entry->getLocalExtraFields() : |
|||
273 | 134 | $entry->getCdExtraFields(); |
|||
274 | 134 | $extraData = ''; |
|||
275 | |||||
276 | 134 | foreach ($collection as $extraField) { |
|||
277 | 15 | if ($local) { |
|||
278 | 15 | $data = $extraField->packLocalFileData(); |
|||
279 | } else { |
||||
280 | 12 | $data = $extraField->packCentralDirData(); |
|||
281 | } |
||||
282 | 15 | $extraData .= pack( |
|||
283 | 15 | 'vv', |
|||
284 | 15 | $extraField->getHeaderId(), |
|||
285 | 15 | \strlen($data) |
|||
286 | ); |
||||
287 | 15 | $extraData .= $data; |
|||
288 | } |
||||
289 | |||||
290 | 134 | $size = \strlen($extraData); |
|||
291 | |||||
292 | 134 | if ($size > 0xffff) { |
|||
293 | throw new ZipException( |
||||
294 | sprintf( |
||||
295 | 'Size extra out of range: %d. Extra data: %s', |
||||
296 | $size, |
||||
297 | $extraData |
||||
298 | ) |
||||
299 | ); |
||||
300 | } |
||||
301 | |||||
302 | 134 | return $extraData; |
|||
303 | } |
||||
304 | |||||
305 | /** |
||||
306 | * @param resource $outStream |
||||
307 | * @param ZipEntry $entry |
||||
308 | * |
||||
309 | * @throws ZipException |
||||
310 | */ |
||||
311 | 134 | protected function writeData($outStream, ZipEntry $entry) |
|||
312 | { |
||||
313 | 134 | $zipData = $entry->getData(); |
|||
314 | |||||
315 | 134 | if ($zipData === null) { |
|||
316 | 23 | if ($entry->isDirectory()) { |
|||
317 | 23 | return; |
|||
318 | } |
||||
319 | |||||
320 | throw new ZipException(sprintf('No zip data for entry "%s"', $entry->getName())); |
||||
321 | } |
||||
322 | |||||
323 | // data write variants: |
||||
324 | // -------------------- |
||||
325 | // * data of source zip file -> copy compressed data |
||||
326 | // * store - simple write |
||||
327 | // * store and encryption - apply encryption filter and simple write |
||||
328 | // * deflate or bzip2 - apply compression filter and simple write |
||||
329 | // * (deflate or bzip2) and encryption - create temp stream and apply |
||||
330 | // compression filter to it, then apply encryption filter to root |
||||
331 | // stream and write temp stream data. |
||||
332 | // (PHP cannot apply the filter for encryption after the compression |
||||
333 | // filter, so a temporary stream is created for the compressed data) |
||||
334 | |||||
335 | 134 | if ($zipData instanceof ZipSourceFileData && !$zipData->hasRecompressData($entry)) { |
|||
336 | // data of source zip file -> copy compressed data |
||||
337 | 28 | $zipData->copyCompressedDataToStream($outStream); |
|||
338 | |||||
339 | 28 | return; |
|||
340 | } |
||||
341 | |||||
342 | 133 | $entryStream = $zipData->getDataAsStream(); |
|||
343 | |||||
344 | 133 | if (stream_get_meta_data($entryStream)['seekable']) { |
|||
345 | 132 | rewind($entryStream); |
|||
346 | } |
||||
347 | |||||
348 | 133 | $uncompressedSize = $entry->getUncompressedSize(); |
|||
349 | |||||
350 | 133 | $posBeforeWrite = ftell($outStream); |
|||
351 | 133 | $compressionMethod = $entry->getCompressionMethod(); |
|||
352 | |||||
353 | 133 | if ($entry->isEncrypted()) { |
|||
354 | 10 | if ($compressionMethod === ZipCompressionMethod::STORED) { |
|||
355 | 7 | $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $uncompressedSize); |
|||
0 ignored issues
–
show
|
|||||
356 | 7 | $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize); |
|||
357 | } else { |
||||
358 | 3 | $compressStream = fopen('php://temp', 'w+b'); |
|||
359 | 3 | $contextFilter = $this->appendCompressionFilter($compressStream, $entry); |
|||
0 ignored issues
–
show
It seems like
$compressStream can also be of type false ; however, parameter $outStream of PhpZip\IO\ZipWriter::appendCompressionFilter() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
360 | 3 | $checksum = $this->writeAndCountChecksum($entryStream, $compressStream, $uncompressedSize); |
|||
0 ignored issues
–
show
It seems like
$compressStream can also be of type false ; however, parameter $outStream of PhpZip\IO\ZipWriter::writeAndCountChecksum() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
361 | |||||
362 | 3 | if ($contextFilter !== null) { |
|||
363 | 3 | stream_filter_remove($contextFilter); |
|||
364 | 3 | $contextFilter = null; |
|||
0 ignored issues
–
show
|
|||||
365 | } |
||||
366 | |||||
367 | 3 | rewind($compressStream); |
|||
0 ignored issues
–
show
It seems like
$compressStream can also be of type false ; however, parameter $handle of rewind() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
368 | |||||
369 | 3 | $compressedSize = fstat($compressStream)['size']; |
|||
0 ignored issues
–
show
It seems like
$compressStream can also be of type false ; however, parameter $handle of fstat() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
370 | 3 | $contextFilter = $this->appendEncryptionFilter($outStream, $entry, $compressedSize); |
|||
0 ignored issues
–
show
Are you sure the assignment to
$contextFilter is correct as $this->appendEncryptionF...entry, $compressedSize) targeting PhpZip\IO\ZipWriter::appendEncryptionFilter() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||
371 | |||||
372 | 10 | stream_copy_to_stream($compressStream, $outStream); |
|||
0 ignored issues
–
show
It seems like
$compressStream can also be of type false ; however, parameter $source of stream_copy_to_stream() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
373 | } |
||||
374 | } else { |
||||
375 | 126 | $contextFilter = $this->appendCompressionFilter($outStream, $entry); |
|||
376 | 126 | $checksum = $this->writeAndCountChecksum($entryStream, $outStream, $uncompressedSize); |
|||
377 | } |
||||
378 | |||||
379 | 133 | if ($contextFilter !== null) { |
|||
380 | 56 | stream_filter_remove($contextFilter); |
|||
381 | 56 | $contextFilter = null; |
|||
382 | } |
||||
383 | |||||
384 | // my hack {@see https://bugs.php.net/bug.php?id=49874} |
||||
385 | 133 | fseek($outStream, 0, \SEEK_END); |
|||
386 | 133 | $compressedSize = ftell($outStream) - $posBeforeWrite; |
|||
387 | |||||
388 | 133 | $entry->setCompressedSize($compressedSize); |
|||
389 | 133 | $entry->setCrc($checksum); |
|||
390 | |||||
391 | 133 | if (!$entry->isDataDescriptorEnabled()) { |
|||
392 | 132 | if ($uncompressedSize > ZipConstants::ZIP64_MAGIC || $compressedSize > ZipConstants::ZIP64_MAGIC) { |
|||
393 | /** @var Zip64ExtraField|null $zip64ExtraLocal */ |
||||
394 | $zip64ExtraLocal = $entry->getLocalExtraField(Zip64ExtraField::HEADER_ID); |
||||
395 | |||||
396 | // if there is a zip64 extra record, then update it; |
||||
397 | // if not, write data to data descriptor |
||||
398 | if ($zip64ExtraLocal !== null) { |
||||
399 | $zip64ExtraLocal->setCompressedSize($compressedSize); |
||||
400 | $zip64ExtraLocal->setUncompressedSize($uncompressedSize); |
||||
401 | |||||
402 | $posExtra = $entry->getLocalHeaderOffset() + ZipConstants::LFH_FILENAME_POS + \strlen($entry->getName()); |
||||
403 | fseek($outStream, $posExtra); |
||||
404 | fwrite($outStream, $this->getExtraFieldsContents($entry, true)); |
||||
405 | } else { |
||||
406 | $posGPBF = $entry->getLocalHeaderOffset() + 6; |
||||
407 | $entry->enableDataDescriptor(true); |
||||
408 | fseek($outStream, $posGPBF); |
||||
409 | fwrite( |
||||
410 | $outStream, |
||||
411 | pack( |
||||
412 | 'v', |
||||
413 | // general purpose bit flag 2 bytes |
||||
414 | $entry->getGeneralPurposeBitFlags() |
||||
415 | ) |
||||
416 | ); |
||||
417 | } |
||||
418 | |||||
419 | $compressedSize = ZipConstants::ZIP64_MAGIC; |
||||
420 | $uncompressedSize = ZipConstants::ZIP64_MAGIC; |
||||
421 | } |
||||
422 | |||||
423 | 132 | $posChecksum = $entry->getLocalHeaderOffset() + 14; |
|||
424 | |||||
425 | /** @var WinZipAesExtraField|null $winZipAesExtra */ |
||||
426 | 132 | $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); |
|||
427 | |||||
428 | 132 | if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) { |
|||
429 | 7 | $checksum = 0; |
|||
430 | } |
||||
431 | |||||
432 | 132 | fseek($outStream, $posChecksum); |
|||
433 | 132 | fwrite( |
|||
434 | 132 | $outStream, |
|||
435 | pack( |
||||
436 | 132 | 'VVV', |
|||
437 | // crc-32 4 bytes |
||||
438 | $checksum, |
||||
439 | // compressed size 4 bytes |
||||
440 | $compressedSize, |
||||
441 | // uncompressed size 4 bytes |
||||
442 | $uncompressedSize |
||||
443 | ) |
||||
444 | ); |
||||
445 | 132 | fseek($outStream, 0, \SEEK_END); |
|||
446 | } |
||||
447 | 133 | } |
|||
448 | |||||
449 | /** |
||||
450 | * @param resource $inStream |
||||
451 | * @param resource $outStream |
||||
452 | * @param int $size |
||||
453 | * |
||||
454 | * @return int |
||||
455 | */ |
||||
456 | 133 | private function writeAndCountChecksum($inStream, $outStream, $size) |
|||
457 | { |
||||
458 | 133 | $contextHash = hash_init('crc32b'); |
|||
459 | 133 | $offset = 0; |
|||
460 | |||||
461 | 133 | while ($offset < $size) { |
|||
462 | 128 | $read = min(self::CHUNK_SIZE, $size - $offset); |
|||
463 | 128 | $buffer = fread($inStream, $read); |
|||
464 | 128 | fwrite($outStream, $buffer); |
|||
465 | 128 | hash_update($contextHash, $buffer); |
|||
466 | 128 | $offset += $read; |
|||
467 | } |
||||
468 | |||||
469 | 133 | return (int) hexdec(hash_final($contextHash)); |
|||
470 | } |
||||
471 | |||||
472 | /** |
||||
473 | * @param resource $outStream |
||||
474 | * @param ZipEntry $entry |
||||
475 | * |
||||
476 | * @throws ZipUnsupportMethodException |
||||
477 | * |
||||
478 | * @return resource|null |
||||
479 | */ |
||||
480 | 128 | protected function appendCompressionFilter($outStream, ZipEntry $entry) |
|||
481 | { |
||||
482 | 128 | $contextCompress = null; |
|||
483 | 128 | switch ($entry->getCompressionMethod()) { |
|||
484 | case ZipCompressionMethod::DEFLATED: |
||||
485 | 49 | if (!($contextCompress = stream_filter_append( |
|||
486 | 49 | $outStream, |
|||
487 | 49 | 'zlib.deflate', |
|||
488 | 49 | \STREAM_FILTER_WRITE, |
|||
489 | 49 | ['level' => $entry->getCompressionLevel()] |
|||
490 | ))) { |
||||
491 | throw new \RuntimeException('Could not append filter "zlib.deflate" to out stream'); |
||||
492 | } |
||||
493 | 49 | break; |
|||
494 | |||||
495 | case ZipCompressionMethod::BZIP2: |
||||
496 | 4 | if (!($contextCompress = stream_filter_append( |
|||
497 | 4 | $outStream, |
|||
498 | 4 | 'bzip2.compress', |
|||
499 | 4 | \STREAM_FILTER_WRITE, |
|||
500 | 4 | ['blocks' => $entry->getCompressionLevel(), 'work' => 0] |
|||
501 | ))) { |
||||
502 | throw new \RuntimeException('Could not append filter "bzip2.compress" to out stream'); |
||||
503 | } |
||||
504 | 4 | break; |
|||
505 | |||||
506 | case ZipCompressionMethod::STORED: |
||||
507 | // file without compression, do nothing |
||||
508 | 107 | break; |
|||
509 | |||||
510 | default: |
||||
511 | throw new ZipUnsupportMethodException( |
||||
512 | sprintf( |
||||
513 | '%s (compression method %d (%s) is not supported)', |
||||
514 | $entry->getName(), |
||||
515 | $entry->getCompressionMethod(), |
||||
516 | ZipCompressionMethod::getCompressionMethodName($entry->getCompressionMethod()) |
||||
517 | ) |
||||
518 | ); |
||||
519 | } |
||||
520 | |||||
521 | 128 | return $contextCompress; |
|||
522 | } |
||||
523 | |||||
524 | /** |
||||
525 | * @param resource $outStream |
||||
526 | * @param ZipEntry $entry |
||||
527 | * @param int $size |
||||
528 | * |
||||
529 | * @return resource|null |
||||
530 | */ |
||||
531 | 10 | protected function appendEncryptionFilter($outStream, ZipEntry $entry, $size) |
|||
532 | { |
||||
533 | 10 | $encContextFilter = null; |
|||
534 | |||||
535 | 10 | if ($entry->isEncrypted()) { |
|||
536 | 10 | if ($entry->getEncryptionMethod() === ZipEncryptionMethod::PKWARE) { |
|||
537 | 4 | PKEncryptionStreamFilter::register(); |
|||
538 | 4 | $cipherFilterName = PKEncryptionStreamFilter::FILTER_NAME; |
|||
539 | } else { |
||||
540 | 9 | WinZipAesEncryptionStreamFilter::register(); |
|||
541 | 9 | $cipherFilterName = WinZipAesEncryptionStreamFilter::FILTER_NAME; |
|||
542 | } |
||||
543 | 10 | $encContextFilter = stream_filter_append( |
|||
544 | 10 | $outStream, |
|||
545 | $cipherFilterName, |
||||
546 | 10 | \STREAM_FILTER_WRITE, |
|||
547 | [ |
||||
548 | 10 | 'entry' => $entry, |
|||
549 | 10 | 'size' => $size, |
|||
550 | ] |
||||
551 | ); |
||||
552 | |||||
553 | 10 | if (!$encContextFilter) { |
|||
0 ignored issues
–
show
|
|||||
554 | throw new \RuntimeException('Not apply filter ' . $cipherFilterName); |
||||
555 | } |
||||
556 | } |
||||
557 | |||||
558 | 10 | return $encContextFilter; |
|||
559 | } |
||||
560 | |||||
561 | /** |
||||
562 | * @param resource $outStream |
||||
563 | * @param ZipEntry $entry |
||||
564 | */ |
||||
565 | 4 | protected function writeDataDescriptor($outStream, ZipEntry $entry) |
|||
566 | { |
||||
567 | 4 | $crc = $entry->getCrc(); |
|||
568 | |||||
569 | /** @var WinZipAesExtraField|null $winZipAesExtra */ |
||||
570 | 4 | $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); |
|||
571 | |||||
572 | 4 | if ($winZipAesExtra !== null && $winZipAesExtra->isV2()) { |
|||
573 | $crc = 0; |
||||
574 | } |
||||
575 | |||||
576 | 4 | fwrite( |
|||
577 | 4 | $outStream, |
|||
578 | pack( |
||||
579 | 4 | 'VV', |
|||
580 | // data descriptor signature 4 bytes (0x08074b50) |
||||
581 | 4 | ZipConstants::DATA_DESCRIPTOR, |
|||
582 | // crc-32 4 bytes |
||||
583 | $crc |
||||
584 | ) |
||||
585 | ); |
||||
586 | |||||
587 | if ( |
||||
588 | 4 | $entry->isZip64ExtensionsRequired() || |
|||
589 | 4 | $entry->getLocalExtraFields()->has(Zip64ExtraField::HEADER_ID) |
|||
590 | ) { |
||||
591 | $dd = |
||||
592 | // compressed size 8 bytes |
||||
593 | PackUtil::packLongLE($entry->getCompressedSize()) . |
||||
594 | // uncompressed size 8 bytes |
||||
595 | PackUtil::packLongLE($entry->getUncompressedSize()); |
||||
596 | } else { |
||||
597 | 4 | $dd = pack( |
|||
598 | 4 | 'VV', |
|||
599 | // compressed size 4 bytes |
||||
600 | 4 | $entry->getCompressedSize(), |
|||
601 | // uncompressed size 4 bytes |
||||
602 | 4 | $entry->getUncompressedSize() |
|||
603 | ); |
||||
604 | } |
||||
605 | |||||
606 | 4 | fwrite($outStream, $dd); |
|||
607 | 4 | } |
|||
608 | |||||
609 | /** |
||||
610 | * @param resource $outStream |
||||
611 | * |
||||
612 | * @throws ZipException |
||||
613 | */ |
||||
614 | 136 | protected function writeCentralDirectoryBlock($outStream) |
|||
615 | { |
||||
616 | 136 | foreach ($this->zipContainer->getEntries() as $outputEntry) { |
|||
617 | 134 | $this->writeCentralDirectoryHeader($outStream, $outputEntry); |
|||
618 | } |
||||
619 | 136 | } |
|||
620 | |||||
621 | /** |
||||
622 | * Writes a Central File Header record. |
||||
623 | * |
||||
624 | * @param resource $outStream |
||||
625 | * @param ZipEntry $entry |
||||
626 | * |
||||
627 | * @throws ZipException |
||||
628 | */ |
||||
629 | 134 | protected function writeCentralDirectoryHeader($outStream, ZipEntry $entry) |
|||
630 | { |
||||
631 | 134 | $compressedSize = $entry->getCompressedSize(); |
|||
632 | 134 | $uncompressedSize = $entry->getUncompressedSize(); |
|||
633 | 134 | $localHeaderOffset = $entry->getLocalHeaderOffset(); |
|||
634 | |||||
635 | 134 | $entry->getCdExtraFields()->remove(Zip64ExtraField::HEADER_ID); |
|||
636 | |||||
637 | if ( |
||||
638 | 134 | $localHeaderOffset > ZipConstants::ZIP64_MAGIC || |
|||
639 | 134 | $compressedSize > ZipConstants::ZIP64_MAGIC || |
|||
640 | 134 | $uncompressedSize > ZipConstants::ZIP64_MAGIC |
|||
641 | ) { |
||||
642 | $zip64ExtraField = new Zip64ExtraField(); |
||||
643 | |||||
644 | if ($uncompressedSize >= ZipConstants::ZIP64_MAGIC) { |
||||
645 | $zip64ExtraField->setUncompressedSize($uncompressedSize); |
||||
646 | $uncompressedSize = ZipConstants::ZIP64_MAGIC; |
||||
647 | } |
||||
648 | |||||
649 | if ($compressedSize >= ZipConstants::ZIP64_MAGIC) { |
||||
650 | $zip64ExtraField->setCompressedSize($compressedSize); |
||||
651 | $compressedSize = ZipConstants::ZIP64_MAGIC; |
||||
652 | } |
||||
653 | |||||
654 | if ($localHeaderOffset >= ZipConstants::ZIP64_MAGIC) { |
||||
655 | $zip64ExtraField->setLocalHeaderOffset($localHeaderOffset); |
||||
656 | $localHeaderOffset = ZipConstants::ZIP64_MAGIC; |
||||
657 | } |
||||
658 | |||||
659 | $entry->getCdExtraFields()->add($zip64ExtraField); |
||||
660 | } |
||||
661 | |||||
662 | 134 | $extra = $this->getExtraFieldsContents($entry, false); |
|||
663 | 134 | $extraLength = \strlen($extra); |
|||
664 | |||||
665 | 134 | $name = $entry->getName(); |
|||
666 | 134 | $comment = $entry->getComment(); |
|||
667 | |||||
668 | 134 | $dosCharset = $entry->getCharset(); |
|||
669 | |||||
670 | 134 | if ($dosCharset !== null && !$entry->isUtf8Flag()) { |
|||
671 | $name = DosCodePage::fromUTF8($name, $dosCharset); |
||||
672 | |||||
673 | if ($comment) { |
||||
674 | $comment = DosCodePage::fromUTF8($comment, $dosCharset); |
||||
675 | } |
||||
676 | } |
||||
677 | |||||
678 | 134 | $commentLength = \strlen($comment); |
|||
679 | |||||
680 | 134 | $compressionMethod = $entry->getCompressionMethod(); |
|||
681 | 134 | $crc = $entry->getCrc(); |
|||
682 | |||||
683 | /** @var WinZipAesExtraField|null $winZipAesExtra */ |
||||
684 | 134 | $winZipAesExtra = $entry->getLocalExtraField(WinZipAesExtraField::HEADER_ID); |
|||
685 | |||||
686 | 134 | if ($winZipAesExtra !== null) { |
|||
687 | 9 | if ($winZipAesExtra->isV2()) { |
|||
688 | 7 | $crc = 0; |
|||
689 | } |
||||
690 | 9 | $compressionMethod = ZipCompressionMethod::WINZIP_AES; |
|||
691 | } |
||||
692 | |||||
693 | 134 | fwrite( |
|||
694 | 134 | $outStream, |
|||
695 | pack( |
||||
696 | 134 | 'VvvvvVVVVvvvvvVV', |
|||
697 | // central file header signature 4 bytes (0x02014b50) |
||||
698 | 134 | ZipConstants::CENTRAL_FILE_HEADER, |
|||
699 | // version made by 2 bytes |
||||
700 | 134 | ($entry->getCreatedOS() << 8) | $entry->getSoftwareVersion(), |
|||
701 | // version needed to extract 2 bytes |
||||
702 | 134 | ($entry->getExtractedOS() << 8) | $entry->getExtractVersion(), |
|||
703 | // general purpose bit flag 2 bytes |
||||
704 | 134 | $entry->getGeneralPurposeBitFlags(), |
|||
705 | // compression method 2 bytes |
||||
706 | $compressionMethod, |
||||
707 | // last mod file datetime 4 bytes |
||||
708 | 134 | $entry->getDosTime(), |
|||
709 | // crc-32 4 bytes |
||||
710 | $crc, |
||||
711 | // compressed size 4 bytes |
||||
712 | $compressedSize, |
||||
713 | // uncompressed size 4 bytes |
||||
714 | $uncompressedSize, |
||||
715 | // file name length 2 bytes |
||||
716 | 134 | \strlen($name), |
|||
717 | // extra field length 2 bytes |
||||
718 | $extraLength, |
||||
719 | // file comment length 2 bytes |
||||
720 | $commentLength, |
||||
721 | // disk number start 2 bytes |
||||
722 | 134 | 0, |
|||
723 | // internal file attributes 2 bytes |
||||
724 | 134 | $entry->getInternalAttributes(), |
|||
725 | // external file attributes 4 bytes |
||||
726 | 134 | $entry->getExternalAttributes(), |
|||
727 | // relative offset of local header 4 bytes |
||||
728 | $localHeaderOffset |
||||
729 | ) |
||||
730 | ); |
||||
731 | |||||
732 | // file name (variable size) |
||||
733 | 134 | fwrite($outStream, $name); |
|||
734 | |||||
735 | 134 | if ($extraLength > 0) { |
|||
736 | // extra field (variable size) |
||||
737 | 12 | fwrite($outStream, $extra); |
|||
738 | } |
||||
739 | |||||
740 | 134 | if ($commentLength > 0) { |
|||
741 | // file comment (variable size) |
||||
742 | 3 | fwrite($outStream, $comment); |
|||
743 | } |
||||
744 | 134 | } |
|||
745 | |||||
746 | /** |
||||
747 | * @param resource $outStream |
||||
748 | * @param int $centralDirectoryOffset |
||||
749 | * @param int $centralDirectorySize |
||||
750 | */ |
||||
751 | 136 | protected function writeEndOfCentralDirectoryBlock( |
|||
752 | $outStream, |
||||
753 | $centralDirectoryOffset, |
||||
754 | $centralDirectorySize |
||||
755 | ) { |
||||
756 | 136 | $cdEntriesCount = \count($this->zipContainer); |
|||
757 | |||||
758 | 136 | $cdEntriesZip64 = $cdEntriesCount > 0xffff; |
|||
759 | 136 | $cdSizeZip64 = $centralDirectorySize > ZipConstants::ZIP64_MAGIC; |
|||
760 | 136 | $cdOffsetZip64 = $centralDirectoryOffset > ZipConstants::ZIP64_MAGIC; |
|||
761 | |||||
762 | 136 | $zip64Required = $cdEntriesZip64 |
|||
763 | 135 | || $cdSizeZip64 |
|||
764 | 136 | || $cdOffsetZip64; |
|||
765 | |||||
766 | 136 | if ($zip64Required) { |
|||
767 | 1 | $zip64EndOfCentralDirectoryOffset = ftell($outStream); |
|||
768 | |||||
769 | // find max software version, version needed to extract and most common platform |
||||
770 | 1 | list($softwareVersion, $versionNeededToExtract) = array_reduce( |
|||
771 | 1 | $this->zipContainer->getEntries(), |
|||
772 | 1 | static function (array $carry, ZipEntry $entry) { |
|||
773 | 1 | $carry[0] = max($carry[0], $entry->getSoftwareVersion() & 0xFF); |
|||
774 | 1 | $carry[1] = max($carry[1], $entry->getExtractVersion() & 0xFF); |
|||
775 | |||||
776 | 1 | return $carry; |
|||
777 | 1 | }, |
|||
778 | [ZipVersion::v10_DEFAULT_MIN, ZipVersion::v45_ZIP64_EXT] |
||||
779 | ); |
||||
780 | |||||
781 | $createdOS = $extractedOS = ZipPlatform::OS_DOS; |
||||
782 | $versionMadeBy = ($createdOS << 8) | max($softwareVersion, ZipVersion::v45_ZIP64_EXT); |
||||
783 | $versionExtractedBy = ($extractedOS << 8) | max($versionNeededToExtract, ZipVersion::v45_ZIP64_EXT); |
||||
784 | |||||
785 | // write zip64 end of central directory signature |
||||
786 | fwrite( |
||||
787 | $outStream, |
||||
788 | pack( |
||||
789 | 'V', |
||||
790 | // signature 4 bytes (0x06064b50) |
||||
791 | ZipConstants::ZIP64_END_CD |
||||
792 | ) |
||||
793 | ); |
||||
794 | // size of zip64 end of central |
||||
795 | // directory record 8 bytes |
||||
796 | fwrite($outStream, PackUtil::packLongLE(ZipConstants::ZIP64_END_OF_CD_LEN - 12)); |
||||
797 | fwrite( |
||||
798 | $outStream, |
||||
799 | pack( |
||||
800 | 'vvVV', |
||||
801 | // version made by 2 bytes |
||||
802 | $versionMadeBy & 0xFFFF, |
||||
803 | // version needed to extract 2 bytes |
||||
804 | $versionExtractedBy & 0xFFFF, |
||||
805 | // number of this disk 4 bytes |
||||
806 | 0, |
||||
807 | // number of the disk with the |
||||
808 | // start of the central directory 4 bytes |
||||
809 | 0 |
||||
810 | ) |
||||
811 | ); |
||||
812 | |||||
813 | fwrite( |
||||
814 | $outStream, |
||||
815 | // total number of entries in the |
||||
816 | // central directory on this disk 8 bytes |
||||
817 | PackUtil::packLongLE($cdEntriesCount) . |
||||
818 | // total number of entries in the |
||||
819 | // central directory 8 bytes |
||||
820 | PackUtil::packLongLE($cdEntriesCount) . |
||||
821 | // size of the central directory 8 bytes |
||||
822 | PackUtil::packLongLE($centralDirectorySize) . |
||||
823 | // offset of start of central |
||||
824 | // directory with respect to |
||||
825 | // the starting disk number 8 bytes |
||||
826 | PackUtil::packLongLE($centralDirectoryOffset) |
||||
827 | ); |
||||
828 | |||||
829 | // write zip64 end of central directory locator |
||||
830 | fwrite( |
||||
831 | $outStream, |
||||
832 | pack( |
||||
833 | 'VV', |
||||
834 | // zip64 end of central dir locator |
||||
835 | // signature 4 bytes (0x07064b50) |
||||
836 | ZipConstants::ZIP64_END_CD_LOC, |
||||
837 | // number of the disk with the |
||||
838 | // start of the zip64 end of |
||||
839 | // central directory 4 bytes |
||||
840 | 0 |
||||
841 | ) . |
||||
842 | // relative offset of the zip64 |
||||
843 | // end of central directory record 8 bytes |
||||
844 | PackUtil::packLongLE($zip64EndOfCentralDirectoryOffset) . |
||||
845 | // total number of disks 4 bytes |
||||
846 | pack('V', 1) |
||||
847 | ); |
||||
848 | } |
||||
849 | |||||
850 | $comment = $this->zipContainer->getArchiveComment(); |
||||
851 | $commentLength = $comment !== null ? \strlen($comment) : 0; |
||||
852 | |||||
853 | fwrite( |
||||
854 | $outStream, |
||||
855 | pack( |
||||
856 | 'VvvvvVVv', |
||||
857 | // end of central dir signature 4 bytes (0x06054b50) |
||||
858 | ZipConstants::END_CD, |
||||
859 | // number of this disk 2 bytes |
||||
860 | 0, |
||||
861 | // number of the disk with the |
||||
862 | // start of the central directory 2 bytes |
||||
863 | 0, |
||||
864 | // total number of entries in the |
||||
865 | // central directory on this disk 2 bytes |
||||
866 | $cdEntriesZip64 ? 0xffff : $cdEntriesCount, |
||||
867 | // total number of entries in |
||||
868 | // the central directory 2 bytes |
||||
869 | $cdEntriesZip64 ? 0xffff : $cdEntriesCount, |
||||
870 | // size of the central directory 4 bytes |
||||
871 | $cdSizeZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectorySize, |
||||
872 | // offset of start of central |
||||
873 | // directory with respect to |
||||
874 | // the starting disk number 4 bytes |
||||
875 | $cdOffsetZip64 ? ZipConstants::ZIP64_MAGIC : $centralDirectoryOffset, |
||||
876 | // .ZIP file comment length 2 bytes |
||||
877 | $commentLength |
||||
878 | ) |
||||
879 | ); |
||||
880 | |||||
881 | if ($comment !== null && $commentLength > 0) { |
||||
882 | // .ZIP file comment (variable size) |
||||
883 | fwrite($outStream, $comment); |
||||
884 | } |
||||
885 | } |
||||
886 | } |
||||
887 |
This check looks for function or method calls that always return null and whose return value is assigned to a variable.
The method
getObject()
can return nothing but null, so it makes no sense to assign that value to a variable.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.