@@ -386,8 +386,6 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
386
386
entry .timestamp = phar_zip_d2u_time (zipentry .timestamp , zipentry .datestamp );
387
387
entry .flags = PHAR_ENT_PERM_DEF_FILE ;
388
388
entry .header_offset = PHAR_GET_32 (zipentry .offset );
389
- entry .offset = entry .offset_abs = PHAR_GET_32 (zipentry .offset ) + sizeof (phar_zip_file_header ) + PHAR_GET_16 (zipentry .filename_len ) +
390
- PHAR_GET_16 (zipentry .extra_len );
391
389
392
390
if (PHAR_GET_16 (zipentry .flags ) & PHAR_ZIP_FLAG_ENCRYPTED ) {
393
391
PHAR_ZIP_FAIL ("Cannot process encrypted zip files" );
@@ -417,6 +415,42 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
417
415
entry .is_dir = 0 ;
418
416
}
419
417
418
+ phar_zip_file_header local ; /* Warning: only filled in when the entry is not a directory! */
419
+ if (!entry .is_dir ) {
420
+ /* A file has a central directory entry, and a local file header. Both of these contain the filename
421
+ * and the extra field data. However, at least the extra field data does not have to match between the two!
422
+ * This happens for example for the "Extended Timestamp extra field" where the local header has 2 extra fields
423
+ * in comparison to the central header. So we have to use the local header to find the right offset to the file
424
+ * contents, otherwise we're reading some garbage bytes before reading the actual file contents. */
425
+ zend_off_t current_central_dir_pos = php_stream_tell (fp );
426
+
427
+ php_stream_seek (fp , entry .header_offset , SEEK_SET );
428
+ if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
429
+ pefree (entry .filename , entry .is_persistent );
430
+ PHAR_ZIP_FAIL ("phar error: internal corruption (cannot read local file header)" );
431
+ }
432
+ php_stream_seek (fp , current_central_dir_pos , SEEK_SET );
433
+
434
+ /* verify local header
435
+ * Note: normally I'd check the crc32, and file sizes too here, but that breaks tests zip/bug48791.phpt & zip/odt.phpt,
436
+ * suggesting that something may be wrong with those files or the assumption doesn't hold. Anyway, the other checks
437
+ * _are_ performed for the alias file as was done in the past too. */
438
+ if (entry .filename_len != PHAR_GET_16 (local .filename_len )) {
439
+ pefree (entry .filename , entry .is_persistent );
440
+ PHAR_ZIP_FAIL ("phar error: internal corruption (local file header does not match central directory)" );
441
+ }
442
+
443
+ entry .offset = entry .offset_abs = entry .header_offset
444
+ + sizeof (phar_zip_file_header )
445
+ + entry .filename_len
446
+ + PHAR_GET_16 (local .extra_len );
447
+ } else {
448
+ entry .offset = entry .offset_abs = entry .header_offset
449
+ + sizeof (phar_zip_file_header )
450
+ + entry .filename_len
451
+ + PHAR_GET_16 (zipentry .extra_len );
452
+ }
453
+
420
454
if (entry .filename_len == sizeof (".phar/signature.bin" )- 1 && !strncmp (entry .filename , ".phar/signature.bin" , sizeof (".phar/signature.bin" )- 1 )) {
421
455
size_t read ;
422
456
php_stream * sigfile ;
@@ -444,7 +478,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
444
478
if (metadata ) {
445
479
php_stream_write (sigfile , metadata , PHAR_GET_16 (locator .comment_len ));
446
480
}
447
- php_stream_seek (fp , sizeof ( phar_zip_file_header ) + entry .header_offset + entry . filename_len + PHAR_GET_16 ( zipentry . extra_len ) , SEEK_SET );
481
+ php_stream_seek (fp , entry .offset , SEEK_SET );
448
482
sig = (char * ) emalloc (entry .uncompressed_filesize );
449
483
read = php_stream_read (fp , sig , entry .uncompressed_filesize );
450
484
if (read != entry .uncompressed_filesize || read <= 8 ) {
@@ -562,28 +596,17 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
562
596
563
597
if (!actual_alias && entry .filename_len == sizeof (".phar/alias.txt" )- 1 && !strncmp (entry .filename , ".phar/alias.txt" , sizeof (".phar/alias.txt" )- 1 )) {
564
598
php_stream_filter * filter ;
565
- zend_off_t saveloc ;
566
- /* verify local file header */
567
- phar_zip_file_header local ;
568
599
569
600
/* archive alias found */
570
- saveloc = php_stream_tell (fp );
571
- php_stream_seek (fp , PHAR_GET_32 (zipentry .offset ), SEEK_SET );
572
-
573
- if (sizeof (local ) != php_stream_read (fp , (char * ) & local , sizeof (local ))) {
574
- pefree (entry .filename , entry .is_persistent );
575
- PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (cannot read local file header for alias)" );
576
- }
577
601
578
602
/* verify local header */
579
- if (entry .filename_len != PHAR_GET_16 (local .filename_len ) || entry .crc32 != PHAR_GET_32 (local .crc32 ) || entry .uncompressed_filesize != PHAR_GET_32 (local .uncompsize ) || entry .compressed_filesize != PHAR_GET_32 (local .compsize )) {
603
+ ZEND_ASSERT (!entry .is_dir );
604
+ if (entry .crc32 != PHAR_GET_32 (local .crc32 ) || entry .uncompressed_filesize != PHAR_GET_32 (local .uncompsize ) || entry .compressed_filesize != PHAR_GET_32 (local .compsize )) {
580
605
pefree (entry .filename , entry .is_persistent );
581
606
PHAR_ZIP_FAIL ("phar error: internal corruption of zip-based phar (local header of alias does not match central directory)" );
582
607
}
583
608
584
- /* construct actual offset to file start - local extra_len can be different from central extra_len */
585
- entry .offset = entry .offset_abs =
586
- sizeof (local ) + entry .header_offset + PHAR_GET_16 (local .filename_len ) + PHAR_GET_16 (local .extra_len );
609
+ zend_off_t restore_pos = php_stream_tell (fp );
587
610
php_stream_seek (fp , entry .offset , SEEK_SET );
588
611
/* these next lines should be for php < 5.2.6 after 5.3 filters are fixed */
589
612
fp -> writepos = 0 ;
@@ -679,7 +702,7 @@ int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alia
679
702
}
680
703
681
704
/* return to central directory parsing */
682
- php_stream_seek (fp , saveloc , SEEK_SET );
705
+ php_stream_seek (fp , restore_pos , SEEK_SET );
683
706
}
684
707
685
708
phar_set_inode (& entry );
0 commit comments