From 68ed6e3e4124f263f11aca6dff2146f952e1d49b Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 22 Feb 2026 01:27:14 -0700 Subject: [PATCH] zig upgrade to 0.15.2 --- build.zig.zon | 6 +- src/StringPool.zig | 9 +- src/autoenv.zig | 18 +-- src/msvcup.zig | 304 ++++++++++++++++++++++++--------------------- src/zip.zig | 136 +++++++++++++++----- 5 files changed, 274 insertions(+), 199 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index bd0c9b9..855a369 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,11 +2,11 @@ .name = .msvcup, .fingerprint = 0xbd52518ef5381747, .version = "0.0.0", - .minimum_zig_version = "0.14.1", + .minimum_zig_version = "0.15.2", .dependencies = .{ .zipcmdline = .{ - .url = "git+https://github.com/marler8997/zipcmdline#3dfca786a489d117e4b72ea10ffb4bbd9fc2dd72", - .hash = "12201a08d7eff7619c8eb8284691a3ff959861b4bdd87216f180ed136672fb4ea26f", + .url = "git+https://github.com/marler8997/zipcmdline#a6de65875023c52d2f56f21a31c6487266f8105f", + .hash = "zipcmdline-0.0.0-DFii07hmAwCFNhtMpml64Pj_hr8kN4WesClygZErQHsk", }, .iobackport = .{ .url = "git+https://github.com/marler8997/zig-io-backport#2ce586882e0257f45f48a4d67e6b76defa0e45f1", diff --git a/src/StringPool.zig b/src/StringPool.zig index a8d3f99..364a958 100644 --- a/src/StringPool.zig +++ b/src/StringPool.zig @@ -10,14 +10,7 @@ pub const Val = struct { pub fn eql(self: Val, other: Val) bool { return self.slice.ptr == other.slice.ptr; } - pub fn format( - self: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = fmt; - _ = options; + pub fn format(self: @This(), writer: *std.Io.Writer) error{WriteFailed}!void { return writer.writeAll(self.slice); } }; diff --git a/src/autoenv.zig b/src/autoenv.zig index 20c47e9..e70ea80 100644 --- a/src/autoenv.zig +++ b/src/autoenv.zig @@ -20,7 +20,7 @@ pub fn main() !u8 { const self_exe_file = getImagePathName() orelse @panic("no image path name"); const self_exe_paths = splitDirBasename(self_exe_file) orelse errExit( - "self exe path '{}' has no parent directory", + "self exe path '{f}' has no parent directory", .{std.unicode.fmtUtf16Le(self_exe_file)}, ); std.debug.assert(self_exe_paths.basename.len > 0); @@ -71,8 +71,9 @@ pub fn main() !u8 { // fatal error C1090: PDB API call failed, error code '23': (0x000006BA) const use_job = !std.mem.eql(u16, self_exe_paths.basename, L("cl.exe")); - const CREATE_SUSPENDED = 0x00000004; - const create_process_flags: u32 = if (use_job) CREATE_SUSPENDED else 0; + const create_process_flags: win32.CreateProcessFlags = .{ + .create_suspended = use_job, + }; if (0 == win32.kernel32.CreateProcessW( // L(exe_basename), exe, @@ -322,7 +323,7 @@ fn findExe(allocator: std.mem.Allocator, exe_basename: []const u16) !?[:0]u16 { free_candidate = false; return candidate_slice; } else |err| switch (err) { - error.FileNotFound, error.PermissionDenied => {}, + error.FileNotFound, error.AccessDenied => {}, error.Unexpected => |e| return e, } } @@ -379,14 +380,7 @@ pub fn fmtError(error_code: win32.Win32Error) FormatError(300) { pub fn FormatError(comptime max_len: usize) type { return struct { error_code: win32.Win32Error, - pub fn format( - self: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { - _ = fmt; - _ = options; + pub fn format(self: @This(), writer: *std.Io.Writer) error{WriteFailed}!void { try writer.print("{} (", .{@intFromEnum(self.error_code)}); var buf: [max_len]u8 = undefined; const len = FormatMessageA( diff --git a/src/msvcup.zig b/src/msvcup.zig index 1872c80..e0f3f0b 100644 --- a/src/msvcup.zig +++ b/src/msvcup.zig @@ -1050,8 +1050,8 @@ fn installFromLockFile( if (version_ref.*) |v| { if (!v.eql(payload_msvcup_pkg.version)) { errExit( - "lock file contains multiple {s} package versions ('{f}' and '{f}')", - .{ @tagName(payload_msvcup_pkg.kind), v, payload_msvcup_pkg.version }, + "lock file contains multiple {t} package versions ('{f}' and '{f}')", + .{ payload_msvcup_pkg.kind, v, payload_msvcup_pkg.version }, ); } } else { @@ -1602,93 +1602,98 @@ fn installPayloadZip( var payload_file = try std.fs.cwd().openFile(cache_path, .{}); defer payload_file.close(); - var last_root_dir: ?[]const u8 = null; + var read_buffer: [4096]u8 = undefined; + var reader = payload_file.reader(&read_buffer); - { - var zip_it = try zip.Iterator.init(payload_file.seekableStream()); - while (try zip_it.next()) |entry| { - var filename_buf: [std.fs.max_path_bytes]u8 = undefined; - const filename = filename_buf[0..entry.filename_len]; - try payload_file.seekableStream().seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); - const len = try payload_file.reader().readAll(filename); - if (len != filename.len) - return error.ZipBadFileOffset; - const other_sep = switch (std.fs.path.sep) { - '/' => '\\', - '\\' => '/', - else => @compileError("todo"), - }; - for (filename) |*c| { - if (c.* == other_sep) c.* = std.fs.path.sep; - } - if (filename.len == 0) return error.ZipEmptyFilename; - if (filename[0] == std.fs.path.sep) return error.ZipAbsoluteFilename; - { - var it = std.mem.splitScalar(u8, filename, std.fs.path.sep); - while (it.next()) |part| { - if (std.mem.eql(u8, part, ".")) return error.ZipFilenameContainsDot; - if (std.mem.eql(u8, part, "..")) return error.ZipFilenameContainsDots; - } - } + var last_root_dir: ?[]const u8 = null; - const prefix: []const u8 = switch (kind) { - .vsix => "Contents" ++ std.fs.path.sep_str, - .zip => "", - }; - if (!std.ascii.startsWithIgnoreCase(filename, prefix)) { - // log.info("ignore '{s}'", .{filename}); - continue; - } - if (filename[filename.len - 1] == std.fs.path.sep) { - // log.info("ignore directory '{s}'", .{filename}); - continue; + var zip_it = try std.zip.Iterator.init(&reader); + while (try zip_it.next()) |entry| { + var filename_buf: [std.fs.max_path_bytes]u8 = undefined; + const filename = filename_buf[0..entry.filename_len]; + try reader.seekTo(entry.header_zip_offset + @sizeOf(std.zip.CentralDirectoryFileHeader)); + try reader.interface.readSliceAll(filename); + const other_sep = switch (std.fs.path.sep) { + '/' => '\\', + '\\' => '/', + else => @compileError("todo"), + }; + for (filename) |*c| { + if (c.* == other_sep) c.* = std.fs.path.sep; + } + if (filename.len == 0) return error.ZipEmptyFilename; + if (filename[0] == std.fs.path.sep) return error.ZipAbsoluteFilename; + { + var it = std.mem.splitScalar(u8, filename, std.fs.path.sep); + while (it.next()) |part| { + if (std.mem.eql(u8, part, ".")) return error.ZipFilenameContainsDot; + if (std.mem.eql(u8, part, "..")) return error.ZipFilenameContainsDots; } + } - // for some reason, the VSIX filenames can be URL percent encoded?!? - const sub_path_encoded = filename[prefix.len..]; - const sub_path_decoded = allocUrlPercentDecoded(scratch, sub_path_encoded) catch |e| oom(e); - defer scratch.free(sub_path_decoded); - - const sub_path = blk: { - if (strip_root_dir) { - const root_dir_end = std.mem.indexOfScalar( - u8, - sub_path_decoded, - std.fs.path.sep, - ) orelse std.debug.panic( - "no root dir to strip from '{s}'", - .{sub_path_decoded}, - ); - const root_dir = sub_path_decoded[0..root_dir_end]; - if (last_root_dir) |last| if (!std.mem.eql(u8, last, root_dir)) std.debug.panic( - "root dir has changed from '{s}' to '{s}', cannot strip", - .{ last, root_dir }, - ); - last_root_dir = root_dir; - std.debug.assert(sub_path_decoded[root_dir.len] == std.fs.path.sep); - const sub_path = sub_path_decoded[root_dir.len + 1 ..]; - std.debug.assert(sub_path.len > 0); - break :blk sub_path; - } - break :blk sub_path_decoded; - }; + const prefix: []const u8 = switch (kind) { + .vsix => "Contents" ++ std.fs.path.sep_str, + .zip => "", + }; + if (!std.ascii.startsWithIgnoreCase(filename, prefix)) { + // log.info("ignore '{s}'", .{filename}); + continue; + } + if (filename[filename.len - 1] == std.fs.path.sep) { + // log.info("ignore directory '{s}'", .{filename}); + continue; + } - switch (try updateInstallingManifest(install_dir, installing_manifest, sub_path)) { - .already_installed => { - // TODO: for zip, we could probably just take a CRC of the current file and compre - // it to our expected CRC - std.debug.panic("'{s}' already installed (TODO: check if it's the same)", .{sub_path}); - }, - .ready => { - const file = try install_dir.createFile(sub_path, .{}); - defer file.close(); - const crc = try zip.extract(entry, payload_file.seekableStream(), file.writer()); - if (crc != entry.crc32) std.debug.panic( - "file '{s}' expected CRC32 0x{x} but got 0x{x}", - .{ sub_path, entry.crc32, crc }, - ); - }, + // for some reason, the VSIX filenames can be URL percent encoded?!? + const sub_path_encoded = filename[prefix.len..]; + const sub_path_decoded = allocUrlPercentDecoded(scratch, sub_path_encoded) catch |e| oom(e); + defer scratch.free(sub_path_decoded); + + const sub_path = blk: { + if (strip_root_dir) { + const root_dir_end = std.mem.indexOfScalar( + u8, + sub_path_decoded, + std.fs.path.sep, + ) orelse std.debug.panic( + "no root dir to strip from '{s}'", + .{sub_path_decoded}, + ); + const root_dir = sub_path_decoded[0..root_dir_end]; + if (last_root_dir) |last| if (!std.mem.eql(u8, last, root_dir)) std.debug.panic( + "root dir has changed from '{s}' to '{s}', cannot strip", + .{ last, root_dir }, + ); + last_root_dir = root_dir; + std.debug.assert(sub_path_decoded[root_dir.len] == std.fs.path.sep); + const sub_path = sub_path_decoded[root_dir.len + 1 ..]; + std.debug.assert(sub_path.len > 0); + break :blk sub_path; } + break :blk sub_path_decoded; + }; + + switch (try updateInstallingManifest(install_dir, installing_manifest, sub_path)) { + .already_installed => { + // TODO: for zip, we could probably just take a CRC of the current file and compre + // it to our expected CRC + std.debug.panic("'{s}' already installed (TODO: check if it's the same)", .{sub_path}); + }, + .ready => { + const file = try install_dir.createFile(sub_path, .{}); + defer file.close(); + var write_buffer: [1024]u8 = undefined; + var file_writer = file.writer(&write_buffer); + const crc = zip.extract(entry, zip_it.input, &file_writer.interface) catch |err| switch (err) { + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // error.WriteFailed => return file_writer.err.?, + else => |e| return e, + }; + if (crc != entry.crc32) std.debug.panic( + "file '{s}' expected CRC32 0x{x} but got 0x{x}", + .{ sub_path, entry.crc32, crc }, + ); + }, } } } @@ -3142,42 +3147,44 @@ fn resolveChannelManifestUrlToFile( error.UnexpectedCharacter, error.InvalidFormat, error.InvalidPort, - error.HttpProxyMissingHost, + error.UriMissingHost, + error.UriHostTooLong, => |e| errExit("init proxy failed with {s}", .{@errorName(e)}), }; - var header_buffer: [8196]u8 = undefined; - - var request = try client.open(.GET, uri, .{ - .server_header_buffer = &header_buffer, - .keep_alive = false, - .redirect_behavior = .not_allowed, + var req = try client.request(.GET, uri, .{ + .redirect_behavior = .unhandled, }); - defer request.deinit(); - try request.send(); - request.wait() catch |err| switch (err) { - error.TooManyHttpRedirects => { - if (request.response.location) |redirect_url| { - _ = std.Uri.parse(redirect_url) catch |e| errExit( - "failed to parse the redirect url '{s}' with {s}", - .{ redirect_url, @errorName(e) }, - ); - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // TODO: don't download directly to this file, download to a temp file and rename - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - const out_file = try std.fs.cwd().createFile(out_path, .{}); - defer out_file.close(); - try out_file.writer().writeAll(redirect_url); - return; - } - }, - else => |e| return e, - }; - errExit("GET '{s}' HTTP status {} \"{s}\"", .{ + defer req.deinit(); + + try req.sendBodiless(); + var redirect_buffer: [8192]u8 = undefined; + var response = try req.receiveHead(&redirect_buffer); + + // Check if we got a redirect response + const status_code = @intFromEnum(response.head.status); + if (status_code >= 300 and status_code < 400) { + // Handle redirect - extract Location header and write to file + const location = response.head.location orelse + errExit("redirect response missing Location header", .{}); + _ = std.Uri.parse(location) catch |e| errExit( + "failed to parse the redirect url '{s}' with {s}", + .{ location, @errorName(e) }, + ); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TODO: don't download directly to this file, download to a temp file and rename + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + const out_file = try std.fs.cwd().createFile(out_path, .{}); + defer out_file.close(); + try out_file.writeAll(location); + return; + } + + errExit("GET '{f}' HTTP status {} \"{s}\"", .{ uri, - @intFromEnum(request.response.status), - request.response.status.phrase() orelse "", + @intFromEnum(response.head.status), + response.head.status.phrase() orelse "", }); } @@ -3273,27 +3280,28 @@ fn fetch( error.UnexpectedCharacter, error.InvalidFormat, error.InvalidPort, - error.HttpProxyMissingHost, + error.UriMissingHost, + error.UriHostTooLong, => |e| errExit("init proxy failed with {s}", .{@errorName(e)}), }; - var header_buffer: [8196]u8 = undefined; - var request = try client.open(.GET, uri, .{ - .server_header_buffer = &header_buffer, - .keep_alive = false, - }); - defer request.deinit(); - try request.send(); - try request.wait(); - if (request.response.status != .ok) return errExit( - "fetch '{}': HTTP response {} \"{?s}\"", - .{ uri, @intFromEnum(request.response.status), request.response.status.phrase() }, + var req = try client.request(.GET, uri, .{}); + defer req.deinit(); + + try req.sendBodiless(); + var redirect_buffer: [8192]u8 = undefined; + var response = try req.receiveHead(&redirect_buffer); + + if (response.head.status != .ok) return errExit( + "fetch '{f}': HTTP response {} \"{?s}\"", + .{ uri, @intFromEnum(response.head.status), response.head.status.phrase() }, ); const file = try std.fs.cwd().createFile(out_path, .{}); defer file.close(); + const maybe_expected_size = blk: { - if (request.response.content_length) |content_length| { + if (response.head.content_length) |content_length| { if (opt.size) |size| { if (size != content_length) errExit( "fetch '{f}': Content-Length {} != expected size {}", @@ -3306,33 +3314,41 @@ fn fetch( }; if (maybe_expected_size) |size| try file.setEndPos(size); + var transfer_buffer: [8192]u8 = undefined; + const body_reader = response.request.reader.bodyReader( + &transfer_buffer, + response.head.transfer_encoding, + response.head.content_length, + ); + var hasher: std.crypto.hash.sha2.Sha256 = .init(.{}); var total_received: u64 = 0; + // Read and write body in chunks while (true) { - var buf: [@max(std.heap.page_size_min, 4096)]u8 = undefined; - const len = request.reader().read(&buf) catch |e| std.debug.panic( - "fetch '{}': read failed with {s}", - .{ uri, @errorName(e) }, - ); - if (len == 0) break; - total_received += len; - if (request.response.content_length) |content_length| { + // Fill the body_reader buffer + body_reader.fillMore() catch |err| switch (err) { + error.ReadFailed => break, + else => |e| return e, + }; + + const buffered = body_reader.buffered(); + if (buffered.len == 0) break; + + total_received += buffered.len; + if (response.head.content_length) |content_length| { if (total_received > content_length) errExit( "fetch '{f}': read more than Content-Length ({})", .{ uri, content_length }, ); } - hasher.update(buf[0..len]); - // NOTE: not going through a buffered writer since we're writing - // large chunks - file.writer().writeAll(buf[0..len]) catch |err| std.debug.panic( - "fetch '{}': write {} bytes of HTTP response failed with {s}", - .{ uri, len, @errorName(err) }, - ); + + hasher.update(buffered); + try file.writeAll(buffered); + body_reader.toss(buffered.len); } - if (request.response.content_length) |content_length| { + if (response.head.content_length) |content_length| { if (total_received != content_length) errExit( "fetch '{f}': Content-Length is {} but only read {}", .{ uri, content_length, total_received }, diff --git a/src/zip.zig b/src/zip.zig index 50af080..cc40d08 100644 --- a/src/zip.zig +++ b/src/zip.zig @@ -1,29 +1,29 @@ -pub const Iterator = std.zip.Iterator(std.fs.File.SeekableStream); +// pub const Iterator = std.zip.Iterator(std.fs.File.SeekableStream); // This is mostly copied from std.zip. std.zip should be refactored // so we don't have to copy this. pub fn extract( - entry: Iterator.Entry, - stream: std.fs.File.SeekableStream, - file_writer: std.fs.File.Writer, + self: std.zip.Iterator.Entry, + stream: *std.fs.File.Reader, + writer: *std.Io.Writer, ) !u32 { const local_data_header_offset: u64 = local_data_header_offset: { const local_header = blk: { - try stream.seekTo(entry.file_offset); - break :blk try stream.context.reader().readStructEndian(std.zip.LocalFileHeader, .little); + try stream.seekTo(self.file_offset); + break :blk try stream.interface.takeStruct(LocalFileHeader, .little); }; - if (!std.mem.eql(u8, &local_header.signature, &std.zip.local_file_header_sig)) + if (!std.mem.eql(u8, &local_header.signature, &local_file_header_sig)) return error.ZipBadFileOffset; - if (local_header.version_needed_to_extract != entry.version_needed_to_extract) + if (local_header.version_needed_to_extract != self.version_needed_to_extract) return error.ZipMismatchVersionNeeded; - if (local_header.last_modification_time != entry.last_modification_time) + if (local_header.last_modification_time != self.last_modification_time) return error.ZipMismatchModTime; - if (local_header.last_modification_date != entry.last_modification_date) + if (local_header.last_modification_date != self.last_modification_date) return error.ZipMismatchModDate; - if (@as(u16, @bitCast(local_header.flags)) != @as(u16, @bitCast(entry.flags))) + if (@as(u16, @bitCast(local_header.flags)) != @as(u16, @bitCast(self.flags))) return error.ZipMismatchFlags; - if (local_header.crc32 != 0 and local_header.crc32 != entry.crc32) + if (local_header.crc32 != 0 and local_header.crc32 != self.crc32) return error.ZipMismatchCrc32; var extents: FileExtents = .{ .uncompressed_size = local_header.uncompressed_size, @@ -35,10 +35,8 @@ pub fn extract( const extra = extra_buf[0..local_header.extra_len]; { - try stream.seekTo(entry.file_offset + @sizeOf(std.zip.LocalFileHeader) + local_header.filename_len); - const len = try stream.context.reader().readAll(extra); - if (len != extra.len) - return error.ZipTruncated; + try stream.seekTo(self.file_offset + @sizeOf(LocalFileHeader) + local_header.filename_len); + try stream.interface.readSliceAll(extra); } var extra_offset: usize = 0; @@ -49,8 +47,8 @@ pub fn extract( if (end > local_header.extra_len) return error.ZipBadExtraFieldSize; const data = extra[extra_offset + 4 .. end]; - switch (@as(std.zip.ExtraHeader, @enumFromInt(header_id))) { - .zip64_info => try readZip64FileExtents(std.zip.LocalFileHeader, local_header, &extents, data), + switch (@as(ExtraHeader, @enumFromInt(header_id))) { + .zip64_info => try readZip64FileExtents(LocalFileHeader, local_header, &extents, data), else => {}, // ignore } extra_offset = end; @@ -58,13 +56,13 @@ pub fn extract( } if (extents.compressed_size != 0 and - extents.compressed_size != entry.compressed_size) + extents.compressed_size != self.compressed_size) return error.ZipMismatchCompLen; if (extents.uncompressed_size != 0 and - extents.uncompressed_size != entry.uncompressed_size) + extents.uncompressed_size != self.uncompressed_size) return error.ZipMismatchUncompLen; - if (local_header.filename_len != entry.filename_len) + if (local_header.filename_len != self.filename_len) return error.ZipMismatchFilenameLen; break :local_data_header_offset @as(u64, local_header.filename_len) + @@ -72,22 +70,91 @@ pub fn extract( }; const local_data_file_offset: u64 = - @as(u64, entry.file_offset) + - @as(u64, @sizeOf(std.zip.LocalFileHeader)) + + @as(u64, self.file_offset) + + @as(u64, @sizeOf(LocalFileHeader)) + local_data_header_offset; try stream.seekTo(local_data_file_offset); - var limited_reader = std.io.limitedReader(stream.context.reader(), entry.compressed_size); - const crc = try std.zip.decompress( - entry.compression_method, - entry.uncompressed_size, - limited_reader.reader(), - file_writer, + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TODO: make sure we're supposed to not pass a buffer here + // var limited_reader = stream.interface.limited(.limited64(self.compressed_size), &.{}); + std.log.info( + "--- decompress compressed_size={} uncompressed_size={}", + .{ self.compressed_size, self.uncompressed_size }, + ); + const crc = try decompress( + self.compression_method, + self.uncompressed_size, + // &limited_reader.interface, + &stream.interface, + writer, ); - if (limited_reader.bytes_left != 0) - return error.ZipDecompressTruncated; + + const total_read = stream.logicalPos() - local_data_file_offset; + std.log.info("total read : {}", .{total_read}); + std.log.info("compressed size : {}", .{self.compressed_size}); + // this assert is faililng for some reason? + // std.debug.assert(total_read >= self.compressed_size); + // std.log.info("read {} extra bytes", .{total_read - self.compressed_size}); + + // if (limited_reader.remaining != .nothing) + // return error.ZipDecompressTruncated; return crc; } +/// Decompresses the given data from `reader` into `writer`. Stops early if more +/// than `uncompressed_size` bytes are processed and verifies that exactly that +/// number of bytes are decompressed. Returns the CRC-32 of the uncompressed data. +/// `writer` can be anything with a `writeAll(self: *Self, chunk: []const u8) anyerror!void` method. +pub fn decompress( + method: std.zip.CompressionMethod, + uncompressed_size: u64, + reader: *std.Io.Reader, + writer: *std.Io.Writer, +) !u32 { + var hash = std.hash.Crc32.init(); + switch (method) { + .store => { + @panic("todo"); + // var buf: [4096]u8 = undefined; + // while (true) { + // const len = try reader.read(&buf); + // if (len == 0) break; + // try writer.writeAll(buf[0..len]); + // hash.update(buf[0..len]); + // total_uncompressed += @intCast(len); + // } + }, + .deflate => { + var flate_buffer: [flate.max_window_len]u8 = undefined; + var decompressor: flate.Decompress = .init(reader, .raw, &flate_buffer); + var uncompressed_remaining: u64 = uncompressed_size; + while (uncompressed_remaining > 0) { + const buffer = try decompressor.reader.peekGreedy(1); + const chunk = buffer[0..@min(buffer.len, uncompressed_remaining)]; + std.log.info("decompress chunk {}", .{chunk.len}); + try writer.writeAll(chunk); + hash.update(chunk); + decompressor.reader.toss(chunk.len); + uncompressed_remaining -= chunk.len; + // var buf: [4096]u8 = undefined; + // const max_read: usize = @min(buf.len, std.math.cast(usize, uncompressed_remaining) orelse buf.len); + // std.log.info("decompress chunk max_read={}", .{max_read}); + // const len = try decompressor.reader.readSliceShort(buf[0..max_read]); + // if (len == 0) return error.ZipDeflateTruncated; + // std.log.info("decompress chunk {}", .{len}); + // try writer.writeAll(buf[0..len]); + // hash.update(buf[0..len]); + // uncompressed_remaining -= len; + } + // if (br.end != br.start) + // return error.ZipDeflateTruncated; + }, + _ => return error.UnsupportedCompressionMethod, + } + return hash.final(); +} + const FileExtents = struct { uncompressed_size: u64, compressed_size: u64, @@ -97,7 +164,8 @@ const FileExtents = struct { fn isMaxInt(uint: anytype) bool { return uint == std.math.maxInt(@TypeOf(uint)); } -// this function is copied from std.zip as it is unfortunately private :( + +// // this function is copied from std.zip as it is unfortunately private :( fn readZip64FileExtents(comptime T: type, header: T, extents: *FileExtents, data: []u8) !void { var data_offset: usize = 0; if (isMaxInt(header.uncompressed_size)) { @@ -137,3 +205,7 @@ fn readZip64FileExtents(comptime T: type, header: T, extents: *FileExtents, data } const std = @import("std"); +const flate = std.compress.flate; +const ExtraHeader = std.zip.ExtraHeader; +const LocalFileHeader = std.zip.LocalFileHeader; +const local_file_header_sig = std.zip.local_file_header_sig;