|
38 | 38 | import inspect |
39 | 39 | import tempfile |
40 | 40 | import configparser |
| 41 | +from zoneinfo import ZoneInfo |
41 | 42 | from io import open, StringIO, BytesIO |
42 | 43 | __enable_pywwwget__ = True |
43 | 44 | pywwwget = False |
@@ -1081,30 +1082,71 @@ def format_ns_local(ts_ns, fmt='%Y-%m-%d %H:%M:%S'): |
1081 | 1082 | ns_str = "%09d" % ns |
1082 | 1083 | return base + "." + ns_str |
1083 | 1084 |
|
1084 | | -def get_unix_timestamp_zip(member): |
| 1085 | +WINDOWS_EPOCH_DELTA = 11644473600 # seconds between 1601-01-01 and 1970-01-01 |
| 1086 | + |
| 1087 | +def _filetime_to_unix_seconds(filetime: int) -> int: |
| 1088 | + # FILETIME is 100-ns intervals since 1601-01-01 UTC |
| 1089 | + return int(filetime // 10_000_000 - WINDOWS_EPOCH_DELTA) |
| 1090 | + |
| 1091 | +def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None: |
| 1092 | + # Layout: [flags:1][mtime?:4][atime?:4][ctime?:4] (only if flags bits set) |
| 1093 | + if len(extra_data) < 1: |
| 1094 | + return None |
| 1095 | + flags = extra_data[0] |
| 1096 | + off = 1 |
| 1097 | + if flags & 0x01: |
| 1098 | + if off + 4 <= len(extra_data): |
| 1099 | + (mtime,) = struct.unpack_from("<I", extra_data, off) |
| 1100 | + return int(mtime) |
| 1101 | + return None |
| 1102 | + |
| 1103 | +def _parse_ntfs_0x000a(extra_data: bytes) -> int | None: |
| 1104 | + # Layout: [reserved:4] then attributes: |
| 1105 | + # attr_tag(2), attr_size(2), attr_data(attr_size) |
| 1106 | + if len(extra_data) < 4: |
| 1107 | + return None |
| 1108 | + off = 4 |
| 1109 | + while off + 4 <= len(extra_data): |
| 1110 | + attr_tag, attr_size = struct.unpack_from("<HH", extra_data, off) |
| 1111 | + off += 4 |
| 1112 | + if off + attr_size > len(extra_data): |
| 1113 | + break |
| 1114 | + attr = extra_data[off:off + attr_size] |
| 1115 | + off += attr_size |
| 1116 | + |
| 1117 | + # 0x0001 attribute contains 3 FILETIMEs: mtime, atime, ctime (each 8 bytes) |
| 1118 | + if attr_tag == 0x0001 and len(attr) >= 24: |
| 1119 | + (mtime_filetime,) = struct.unpack_from("<Q", attr, 0) |
| 1120 | + return _filetime_to_unix_seconds(mtime_filetime) |
| 1121 | + return None |
| 1122 | + |
| 1123 | +def get_unix_timestamp_zip(member, fallback_tz: str = "America/Chicago") -> int: |
1085 | 1124 | extra = member.extra |
1086 | 1125 | i = 0 |
1087 | | - |
1088 | | - # 1. Try to find UTC Extra Fields |
1089 | | - while i + 4 <= len(extra): |
1090 | | - tag, length = struct.unpack('<HH', extra[i:i+4]) |
1091 | | - data = extra[i+4 : i+4+length] |
1092 | | - |
1093 | | - # 0x5455: Info-ZIP (Unix) |
1094 | | - if tag == 0x5455 and len(data) >= 5: |
1095 | | - if data[0] & 1: |
1096 | | - return struct.unpack('<I', data[1:5])[0] |
1097 | | - |
1098 | | - # 0x000a: NTFS (Windows) |
1099 | | - elif tag == 0x000a and len(data) >= 24: |
1100 | | - ntfs_mtime = struct.unpack('<Q', data[8:16])[0] |
1101 | | - return int((ntfs_mtime / 1e7) - 11644473600) |
1102 | | - |
1103 | | - i += 4 + length |
1104 | 1126 |
|
1105 | | - # 2. Fallback: Convert MS-DOS date_time to Unix integer |
1106 | | - dt = datetime.datetime(*member.date_time) |
1107 | | - return int(dt.replace(tzinfo=datetime.timezone.utc).timestamp()) |
| 1127 | + # 1) Prefer UTC-capable extra fields |
| 1128 | + while i + 4 <= len(extra): |
| 1129 | + tag, length = struct.unpack_from("<HH", extra, i) |
| 1130 | + i += 4 |
| 1131 | + data = extra[i:i+length] |
| 1132 | + i += length |
| 1133 | + |
| 1134 | + if tag == 0x5455: |
| 1135 | + ts = _parse_ext_timestamp_0x5455(data) |
| 1136 | + if ts is not None: |
| 1137 | + return ts |
| 1138 | + |
| 1139 | + elif tag == 0x000A: |
| 1140 | + ts = _parse_ntfs_0x000a(data) |
| 1141 | + if ts is not None: |
| 1142 | + return ts |
| 1143 | + |
| 1144 | + # 2) Fallback: DOS local time -> interpret in fallback_tz -> UTC |
| 1145 | + # ZIP DOS timestamps are "local time" with no TZ info. |
| 1146 | + local_naive = datetime.datetime(*member.date_time) |
| 1147 | + local_dt = local_naive.replace(tzinfo=ZoneInfo(fallback_tz)) |
| 1148 | + utc_dt = local_dt.astimezone(datetime.timezone.utc) |
| 1149 | + return int(utc_dt.timestamp()) |
1108 | 1150 |
|
1109 | 1151 | def CheckSumSupport(checkfor, guaranteed=True): |
1110 | 1152 | if(guaranteed): |
|
0 commit comments