Skip to content

Commit a470c4a

Browse files
committed
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. Closes GH-12548.
1 parent 1c8943b commit a470c4a

File tree

6 files changed

+66
-0
lines changed

6 files changed

+66
-0
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ PDO_PGSQL:
2424
PGSQL:
2525
. Added the possibility to have no conditions for pg_select. (OmarEmaraDev)
2626

27+
Phar:
28+
. Fixed bug GH-12532 (PharData created from zip has incorrect timestamp).
29+
(nielsdos)
30+
2731
SimpleXML:
2832
. Fixed bug GH-12192 (SimpleXML infinite loop when getName() is called
2933
within foreach). (nielsdos)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ PHP 8.4 UPGRADE NOTES
7979
. Added constant DOMNode::DOCUMENT_POSITION_CONTAINED_BY.
8080
. Added constant DOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
8181

82+
- Phar:
83+
. Added support for the unix timestamp extension for zip archives.
84+
8285
- SOAP:
8386
. Added support for clark notation for namespaces in class map.
8487
It is now possible to specify entries in a class map with clark notation

ext/phar/pharzip.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ typedef struct _phar_zip_unix3 {
146146
/* (var.) variable symbolic link filename */
147147
} phar_zip_unix3;
148148

149+
/* See https://libzip.org/specifications/extrafld.txt */
150+
typedef struct _phar_zip_unix_time {
151+
phar_zip_extra_field_header header;
152+
char flags; /* flags 1 byte */
153+
char time[4]; /* time in standard Unix format 4 bytes */
154+
} phar_zip_unix_time;
155+
149156
typedef struct _phar_zip_central_dir_file {
150157
char signature[4]; /* central file header signature 4 bytes (0x02014b50) */
151158
char madeby[2]; /* version made by 2 bytes */

ext/phar/tests/gh12532.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-12532 (PharData created from zip has incorrect timestamp)
3+
--EXTENSIONS--
4+
phar
5+
--FILE--
6+
<?php
7+
8+
date_default_timezone_set('UTC');
9+
$phar = new PharData(__DIR__ . '/gh12532.zip');
10+
echo $phar->getMTime(), "\n";
11+
echo $phar->getFileInfo()->getMTime(), "\n";
12+
echo date('Y-m-d H:i:s', $phar->getMTime()), "\n";
13+
echo date('Y-m-d H:i:s', $phar->getCTime()), "\n";
14+
echo date('Y-m-d H:i:s', $phar->getATime()), "\n";
15+
16+
?>
17+
--EXPECT--
18+
1680284661
19+
1680284661
20+
2023-03-31 17:44:21
21+
2023-03-31 17:44:21
22+
2023-03-31 17:44:21

ext/phar/tests/gh12532.zip

213 Bytes
Binary file not shown.

ext/phar/zip.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, uint16
4444
union {
4545
phar_zip_extra_field_header header;
4646
phar_zip_unix3 unix3;
47+
phar_zip_unix_time time;
4748
} h;
4849
size_t read;
4950

@@ -52,6 +53,35 @@ static int phar_zip_process_extra(php_stream *fp, phar_entry_info *entry, uint16
5253
return FAILURE;
5354
}
5455

56+
if (h.header.tag[0] == 'U' && h.header.tag[1] == 'T') {
57+
/* Unix timestamp header found.
58+
* The flags field indicates which timestamp fields are present.
59+
* The size with a timestamp is at least 5 (== 9 - tag size) bytes, but may be larger.
60+
* We only store the modification time in the entry, so only read that.
61+
*/
62+
const size_t min_size = 5;
63+
uint16_t header_size = PHAR_GET_16(h.header.size);
64+
if (header_size >= min_size) {
65+
read = php_stream_read(fp, &h.time.flags, min_size);
66+
if (read != min_size) {
67+
return FAILURE;
68+
}
69+
if (h.time.flags & (1 << 0)) {
70+
/* Modification time set */
71+
entry->timestamp = PHAR_GET_32(h.time.time);
72+
}
73+
74+
len -= header_size + 4;
75+
76+
/* Consume remaining bytes */
77+
if (header_size != read) {
78+
php_stream_seek(fp, header_size - read, SEEK_CUR);
79+
}
80+
continue;
81+
}
82+
/* Fallthrough to next if to skip header */
83+
}
84+
5585
if (h.header.tag[0] != 'n' || h.header.tag[1] != 'u') {
5686
/* skip to next header */
5787
php_stream_seek(fp, PHAR_GET_16(h.header.size), SEEK_CUR);

0 commit comments

Comments
 (0)