From e4453b26c3c5572f55bb3dab8d9a2e61003a274a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 28 Oct 2023 19:48:01 +0200 Subject: [PATCH] Fix GH-12532: PharData created from zip has incorrect timestamp The datetime stored in the DOS time fields, which is what zip standard uses, is local time without a timezone. There's an extension to the zip file format since '97 that allows storing a unix timestamp (in UTC) in the header for both the central directory and the local entries. This patch adds support for this. --- ext/phar/pharzip.h | 7 +++++++ ext/phar/tests/gh12532.phpt | 22 ++++++++++++++++++++++ ext/phar/tests/gh12532.zip | Bin 0 -> 213 bytes ext/phar/zip.c | 30 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 ext/phar/tests/gh12532.phpt create mode 100644 ext/phar/tests/gh12532.zip diff --git a/ext/phar/pharzip.h b/ext/phar/pharzip.h index 2daca5b339966..5c814747b6923 100644 --- a/ext/phar/pharzip.h +++ b/ext/phar/pharzip.h @@ -146,6 +146,13 @@ typedef struct _phar_zip_unix3 { /* (var.) variable symbolic link filename */ } phar_zip_unix3; +/* See https://libzip.org/specifications/extrafld.txt */ +typedef struct _phar_zip_unix_time { + phar_zip_extra_field_header header; + char flags; /* flags 1 byte */ + char time[4]; /* time in standard Unix format 4 bytes */ +} phar_zip_unix_time; + typedef struct _phar_zip_central_dir_file { char signature[4]; /* central file header signature 4 bytes (0x02014b50) */ char madeby[2]; /* version made by 2 bytes */ diff --git a/ext/phar/tests/gh12532.phpt b/ext/phar/tests/gh12532.phpt new file mode 100644 index 0000000000000..2c1419198e897 --- /dev/null +++ b/ext/phar/tests/gh12532.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-12532 (PharData created from zip has incorrect timestamp) +--EXTENSIONS-- +phar +--FILE-- +getMTime(), "\n"; +echo $phar->getFileInfo()->getMTime(), "\n"; +echo date('Y-m-d H:i:s', $phar->getMTime()), "\n"; +echo date('Y-m-d H:i:s', $phar->getCTime()), "\n"; +echo date('Y-m-d H:i:s', $phar->getATime()), "\n"; + +?> +--EXPECT-- +1680284661 +1680284661 +2023-03-31 17:44:21 +2023-03-31 17:44:21 +2023-03-31 17:44:21 diff --git a/ext/phar/tests/gh12532.zip b/ext/phar/tests/gh12532.zip new file mode 100644 index 0000000000000000000000000000000000000000..9b52086eb29ca9d78abb0c14737056ec0017ff82 GIT binary patch literal 213 zcmWIWW@Zs#-~hs|x%FWTP{0eMxfm1}iW74Sa#Hn5DoR2_cp2EgN~@;?$*8A*=+X*q z21b^zj0_Aw{R|ABJ= min_size) { + read = php_stream_read(fp, &h.time.flags, min_size); + if (read != min_size) { + return FAILURE; + } + if (h.time.flags & (1 << 0)) { + /* Modification time set */ + entry->timestamp = PHAR_GET_32(h.time.time); + } + + len -= header_size + 4; + + /* Consume remaining bytes */ + if (header_size != read) { + php_stream_seek(fp, header_size - read, SEEK_CUR); + } + continue; + } + /* Fallthrough to next if to skip header */ + } + if (h.header.tag[0] != 'n' || h.header.tag[1] != 'u') { /* skip to next header */ php_stream_seek(fp, PHAR_GET_16(h.header.size), SEEK_CUR);