diff options
| author | Jens Goldberg <jens.goldberg@gmail.com> | 2022-01-01 12:47:08 +0000 |
|---|---|---|
| committer | Jens Goldberg <jens.goldberg@gmail.com> | 2022-01-01 12:47:08 +0000 |
| commit | a54788ba7af64e59d3fbaf8951e3164f1347a2f4 (patch) | |
| tree | ce2b8278d77299afe44d10d18e6dd9f6a79cb310 /lib/std/tz.zig | |
| parent | cb06f6f9846a4705a4d8ae1bcaa508e992bafee8 (diff) | |
| download | zig-a54788ba7af64e59d3fbaf8951e3164f1347a2f4.tar.gz zig-a54788ba7af64e59d3fbaf8951e3164f1347a2f4.zip | |
Support legacy TZ format, expose header struct to a potential writer
Diffstat (limited to 'lib/std/tz.zig')
| -rw-r--r-- | lib/std/tz.zig | 137 |
1 files changed, 78 insertions, 59 deletions
diff --git a/lib/std/tz.zig b/lib/std/tz.zig index f1d19601ef..d505a790b7 100644 --- a/lib/std/tz.zig +++ b/lib/std/tz.zig @@ -38,70 +38,71 @@ pub const Tz = struct { transitions: []const Transition, timetypes: []const Timetype, leapseconds: []const Leapsecond, - footer: []const u8, + footer: ?[]const u8, - pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz { - const Header = extern struct { - magic: [4]u8, - version: u8, - reserved: [15]u8, - }; - - const Counts = extern struct { + const Header = extern struct { + magic: [4]u8, + version: u8, + reserved: [15]u8, + counts: extern struct { isutcnt: u32, isstdcnt: u32, leapcnt: u32, timecnt: u32, typecnt: u32, charcnt: u32, - }; + }, + }; - // Parse and skip the legacy header and data - { - const header = try reader.readStruct(Header); - if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader; - if (header.version == 0) return error.UnsupportedLegacyFormat; - if (header.version != '2' and header.version != '3') return error.BadVersion; + pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Tz { + var legacy_header = try reader.readStruct(Header); + if (!std.mem.eql(u8, &legacy_header.magic, "TZif")) return error.BadHeader; + if (legacy_header.version != 0 and legacy_header.version != '2' and legacy_header.version != '3') return error.BadVersion; - var counts = try reader.readStruct(Counts); - if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) { - std.mem.bswapAllFields(Counts, &counts); - } + if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) { + std.mem.bswapAllFields(@TypeOf(legacy_header.counts), &legacy_header.counts); + } - const skipv = counts.timecnt * 5 + counts.typecnt * 6 + counts.charcnt + counts.leapcnt * 8 + counts.isstdcnt + counts.isutcnt; + if (legacy_header.version == 0) { + return parseBlock(allocator, reader, legacy_header, true); + } else { + // If the format is modern, just skip over the legacy data + const skipv = legacy_header.counts.timecnt * 5 + legacy_header.counts.typecnt * 6 + legacy_header.counts.charcnt + legacy_header.counts.leapcnt * 8 + legacy_header.counts.isstdcnt + legacy_header.counts.isutcnt; try reader.skipBytes(skipv, .{}); - } - const header = try reader.readStruct(Header); - if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader; - if (header.version != '2' and header.version != '3') return error.BadVersion; + var header = try reader.readStruct(Header); + if (!std.mem.eql(u8, &header.magic, "TZif")) return error.BadHeader; + if (header.version != '2' and header.version != '3') return error.BadVersion; + if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) { + std.mem.bswapAllFields(@TypeOf(header.counts), &header.counts); + } - var counts = try reader.readStruct(Counts); - if (builtin.target.cpu.arch.endian() != std.builtin.Endian.Big) { - std.mem.bswapAllFields(Counts, &counts); + return parseBlock(allocator, reader, header, false); } + } - if (counts.isstdcnt != 0 and counts.isstdcnt != counts.typecnt) return error.Malformed; // rfc8536: isstdcnt [...] MUST either be zero or equal to "typecnt" - if (counts.isutcnt != 0 and counts.isutcnt != counts.typecnt) return error.Malformed; // rfc8536: isutcnt [...] MUST either be zero or equal to "typecnt" - if (counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero - if (counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero - if (counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical + fn parseBlock(allocator: std.mem.Allocator, reader: anytype, header: Header, legacy: bool) !Tz { + if (header.counts.isstdcnt != 0 and header.counts.isstdcnt != header.counts.typecnt) return error.Malformed; // rfc8536: isstdcnt [...] MUST either be zero or equal to "typecnt" + if (header.counts.isutcnt != 0 and header.counts.isutcnt != header.counts.typecnt) return error.Malformed; // rfc8536: isutcnt [...] MUST either be zero or equal to "typecnt" + if (header.counts.typecnt == 0) return error.Malformed; // rfc8536: typecnt [...] MUST NOT be zero + if (header.counts.charcnt == 0) return error.Malformed; // rfc8536: charcnt [...] MUST NOT be zero + if (header.counts.charcnt > 256 + 6) return error.Malformed; // Not explicitly banned by rfc8536 but nonsensical - var leapseconds = try allocator.alloc(Leapsecond, counts.leapcnt); + var leapseconds = try allocator.alloc(Leapsecond, header.counts.leapcnt); errdefer allocator.free(leapseconds); - var transitions = try allocator.alloc(Transition, counts.timecnt); + var transitions = try allocator.alloc(Transition, header.counts.timecnt); errdefer allocator.free(transitions); - var timetypes = try allocator.alloc(Timetype, counts.typecnt); + var timetypes = try allocator.alloc(Timetype, header.counts.typecnt); errdefer allocator.free(timetypes); // Parse transition types var i: usize = 0; - while (i < counts.timecnt) : (i += 1) { - transitions[i].ts = try reader.readIntBig(i64); + while (i < header.counts.timecnt) : (i += 1) { + transitions[i].ts = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64); } i = 0; - while (i < counts.timecnt) : (i += 1) { + while (i < header.counts.timecnt) : (i += 1) { const tt = try reader.readByte(); if (tt >= timetypes.len) return error.Malformed; // rfc8536: Each type index MUST be in the range [0, "typecnt" - 1] transitions[i].timetype = &timetypes[tt]; @@ -109,13 +110,13 @@ pub const Tz = struct { // Parse time types i = 0; - while (i < counts.typecnt) : (i += 1) { + while (i < header.counts.typecnt) : (i += 1) { const offset = try reader.readIntBig(i32); if (offset < -2147483648) return error.Malformed; // rfc8536: utoff [...] MUST NOT be -2**31 const dst = try reader.readByte(); if (dst != 0 and dst != 1) return error.Malformed; // rfc8536: (is)dst [...] The value MUST be 0 or 1. const idx = try reader.readByte(); - if (idx > counts.charcnt - 1) return error.Malformed; // rfc8536: (desig)idx [...] Each index MUST be in the range [0, "charcnt" - 1] + if (idx > header.counts.charcnt - 1) return error.Malformed; // rfc8536: (desig)idx [...] Each index MUST be in the range [0, "charcnt" - 1] timetypes[i] = .{ .offset = offset, .flags = dst, @@ -127,8 +128,8 @@ pub const Tz = struct { } var designators_data: [256 + 6]u8 = undefined; - try reader.readNoEof(designators_data[0..counts.charcnt]); - const designators = designators_data[0..counts.charcnt]; + try reader.readNoEof(designators_data[0..header.counts.charcnt]); + const designators = designators_data[0..header.counts.charcnt]; if (designators[designators.len - 1] != 0) return error.Malformed; // rfc8536: charcnt [...] includes the trailing NUL (0x00) octet // Iterate through the timetypes again, setting the designator names @@ -142,8 +143,8 @@ pub const Tz = struct { // Parse leap seconds i = 0; - while (i < counts.leapcnt) : (i += 1) { - const occur = try reader.readIntBig(i64); + while (i < header.counts.leapcnt) : (i += 1) { + const occur: i64 = if (legacy) try reader.readIntBig(i32) else try reader.readIntBig(i64); if (occur < 0) return error.Malformed; // rfc8536: occur [...] MUST be nonnegative if (i > 0 and leapseconds[i - 1].occurrence + 2419199 > occur) return error.Malformed; // rfc8536: occur [...] each later value MUST be at least 2419199 greater than the previous value if (occur > std.math.maxInt(i48)) return error.Malformed; // Unreasonably far into the future @@ -161,7 +162,7 @@ pub const Tz = struct { // Parse standard/wall indicators i = 0; - while (i < counts.isstdcnt) : (i += 1) { + while (i < header.counts.isstdcnt) : (i += 1) { const stdtime = try reader.readByte(); if (stdtime == 1) { timetypes[i].flags |= 0x02; @@ -170,7 +171,7 @@ pub const Tz = struct { // Parse UT/local indicators i = 0; - while (i < counts.isutcnt) : (i += 1) { + while (i < header.counts.isutcnt) : (i += 1) { const ut = try reader.readByte(); if (ut == 1) { timetypes[i].flags |= 0x04; @@ -178,29 +179,34 @@ pub const Tz = struct { } } - if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline - // Footer - var footerdata_buf: [128]u8 = undefined; - const footer = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) { - error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string - else => return err, - }; - - const footer_dup = try allocator.dupe(u8, footer); - errdefer allocator.free(footer_dup); + var footer: ?[]u8 = null; + if (!legacy) { + if ((try reader.readByte()) != '\n') return error.Malformed; // An rfc8536 footer must start with a newline + var footerdata_buf: [128]u8 = undefined; + const footer_mem = reader.readUntilDelimiter(&footerdata_buf, '\n') catch |err| switch (err) { + error.StreamTooLong => return error.OverlargeFooter, // Read more than 128 bytes, much larger than any reasonable POSIX TZ string + else => return err, + }; + if (footer_mem.len != 0) { + footer = try allocator.dupe(u8, footer_mem); + } + } + errdefer if (footer) |ft| allocator.free(ft); return Tz{ .allocator = allocator, .transitions = transitions, .timetypes = timetypes, .leapseconds = leapseconds, - .footer = footer_dup, + .footer = footer, }; } pub fn deinit(self: *Tz) void { - self.allocator.free(self.footer); + if (self.footer) |footer| { + self.allocator.free(footer); + } self.allocator.free(self.leapseconds); self.allocator.free(self.transitions); self.allocator.free(self.timetypes); @@ -231,3 +237,16 @@ test "fat" { try std.testing.expect(std.mem.eql(u8, tz.transitions[3].timetype.name(), "+05")); try std.testing.expectEqual(tz.transitions[4].ts, 1268251224); // 2010-03-10 20:00:00 UTC } + +test "legacy" { + // Taken from Slackware 8.0, from 2001 + const data = @embedFile("tz/europe_vatican.tzif"); + var in_stream = std.io.fixedBufferStream(data); + + var tz = try std.Tz.parse(std.testing.allocator, in_stream.reader()); + defer tz.deinit(); + + try std.testing.expectEqual(tz.transitions.len, 170); + try std.testing.expect(std.mem.eql(u8, tz.transitions[69].timetype.name(), "CET")); + try std.testing.expectEqual(tz.transitions[123].ts, 1414285200); // 2014-10-26 01:00:00 UTC +} |
