From 038ed32cffbb40d87d8634470e29df31b7699359 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 12 Apr 2023 22:48:03 -0500 Subject: [PATCH] add explicit error union for Bundle.rescan and associated functions --- lib/std/crypto/Certificate.zig | 32 ++++++++++++------ lib/std/crypto/Certificate/Bundle.zig | 37 +++++++++++++++------ lib/std/crypto/Certificate/Bundle/macos.zig | 4 ++- lib/std/http/Client.zig | 24 ++----------- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index 22513f9efe..0caffba363 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -371,7 +371,9 @@ test "Parsed.checkHostName" { try expectEqual(false, Parsed.checkHostName("lang.org", "zig*.org")); } -pub fn parse(cert: Certificate) !Parsed { +pub const ParseError = der.Element.ParseElementError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError; + +pub fn parse(cert: Certificate) ParseError!Parsed { const cert_bytes = cert.buffer; const certificate = try der.Element.parse(cert_bytes, cert.index); const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start); @@ -514,14 +516,18 @@ pub fn contents(cert: Certificate, elem: der.Element) []const u8 { return cert.buffer[elem.slice.start..elem.slice.end]; } +pub const ParseBitStringError = error{ CertificateFieldHasWrongDataType, CertificateHasInvalidBitString }; + pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice { if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType; if (cert.buffer[elem.slice.start] != 0) return error.CertificateHasInvalidBitString; return .{ .start = elem.slice.start + 1, .end = elem.slice.end }; } +pub const ParseTimeError = error{ CertificateTimeInvalid, CertificateFieldHasWrongDataType }; + /// Returns number of seconds since epoch. -pub fn parseTime(cert: Certificate, elem: der.Element) !u64 { +pub fn parseTime(cert: Certificate, elem: der.Element) ParseTimeError!u64 { const bytes = cert.contents(elem); switch (elem.identifier.tag) { .utc_time => { @@ -647,34 +653,38 @@ test parseYear4 { try expectError(error.CertificateTimeInvalid, parseYear4("crap")); } -pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm { +pub fn parseAlgorithm(bytes: []const u8, element: der.Element) ParseEnumError!Algorithm { return parseEnum(Algorithm, bytes, element); } -pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory { +pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) ParseEnumError!AlgorithmCategory { return parseEnum(AlgorithmCategory, bytes, element); } -pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute { +pub fn parseAttribute(bytes: []const u8, element: der.Element) ParseEnumError!Attribute { return parseEnum(Attribute, bytes, element); } -pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve { +pub fn parseNamedCurve(bytes: []const u8, element: der.Element) ParseEnumError!NamedCurve { return parseEnum(NamedCurve, bytes, element); } -pub fn parseExtensionId(bytes: []const u8, element: der.Element) !ExtensionId { +pub fn parseExtensionId(bytes: []const u8, element: der.Element) ParseEnumError!ExtensionId { return parseEnum(ExtensionId, bytes, element); } -fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E { +pub const ParseEnumError = error{ CertificateFieldHasWrongDataType, CertificateHasUnrecognizedObjectId }; + +fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) ParseEnumError!E { if (element.identifier.tag != .object_identifier) return error.CertificateFieldHasWrongDataType; const oid_bytes = bytes[element.slice.start..element.slice.end]; return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId; } -pub fn parseVersion(bytes: []const u8, version_elem: der.Element) !Version { +pub const ParseVersionError = error{ UnsupportedCertificateVersion, CertificateFieldHasInvalidLength }; + +pub fn parseVersion(bytes: []const u8, version_elem: der.Element) ParseVersionError!Version { if (@bitCast(u8, version_elem.identifier) != 0xa0) return .v1; @@ -861,9 +871,9 @@ pub const der = struct { pub const empty: Slice = .{ .start = 0, .end = 0 }; }; - pub const ParseError = error{CertificateFieldHasInvalidLength}; + pub const ParseElementError = error{CertificateFieldHasInvalidLength}; - pub fn parse(bytes: []const u8, index: u32) ParseError!Element { + pub fn parse(bytes: []const u8, index: u32) ParseElementError!Element { var i = index; const identifier = @bitCast(Identifier, bytes[i]); i += 1; diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 1a5a45ae63..b3b5409d27 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -50,11 +50,13 @@ pub fn deinit(cb: *Bundle, gpa: Allocator) void { cb.* = undefined; } +pub const RescanError = RescanLinuxError || RescanMacError || RescanWindowsError; + /// Clears the set of certificates and then scans the host operating system /// file system standard locations for certificates. /// For operating systems that do not have standard CA installations to be /// found, this function clears the set of certificates. -pub fn rescan(cb: *Bundle, gpa: Allocator) !void { +pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void { switch (builtin.os.tag) { .linux => return rescanLinux(cb, gpa), .macos => return rescanMac(cb, gpa), @@ -64,8 +66,11 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void { } pub const rescanMac = @import("Bundle/macos.zig").rescanMac; +pub const RescanMacError = @import("Bundle/macos.zig").RescanMacError; -pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void { +pub const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError; + +pub fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void { // Possible certificate files; stop after finding one. const cert_file_paths = [_][]const u8{ "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. @@ -107,7 +112,9 @@ pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void { cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); } -pub fn rescanWindows(cb: *Bundle, gpa: Allocator) !void { +pub const RescanWindowsError = Allocator.Error || ParseCertError || std.os.UnexpectedError || error{FileNotFound}; + +pub fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void { cb.bytes.clearRetainingCapacity(); cb.map.clearRetainingCapacity(); @@ -132,12 +139,14 @@ pub fn rescanWindows(cb: *Bundle, gpa: Allocator) !void { cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); } +pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError; + pub fn addCertsFromDirPath( cb: *Bundle, gpa: Allocator, dir: fs.Dir, sub_dir_path: []const u8, -) !void { +) AddCertsFromDirPathError!void { var iterable_dir = try dir.openIterableDir(sub_dir_path, .{}); defer iterable_dir.close(); return addCertsFromDir(cb, gpa, iterable_dir); @@ -147,14 +156,16 @@ pub fn addCertsFromDirPathAbsolute( cb: *Bundle, gpa: Allocator, abs_dir_path: []const u8, -) !void { +) AddCertsFromDirPathError!void { assert(fs.path.isAbsolute(abs_dir_path)); var iterable_dir = try fs.openIterableDirAbsolute(abs_dir_path, .{}); defer iterable_dir.close(); return addCertsFromDir(cb, gpa, iterable_dir); } -pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir) !void { +pub const AddCertsFromDirError = AddCertsFromFilePathError; + +pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir) AddCertsFromDirError!void { var it = iterable_dir.iterate(); while (try it.next()) |entry| { switch (entry.kind) { @@ -166,11 +177,13 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir } } +pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError; + pub fn addCertsFromFilePathAbsolute( cb: *Bundle, gpa: Allocator, abs_file_path: []const u8, -) !void { +) AddCertsFromFilePathError!void { assert(fs.path.isAbsolute(abs_file_path)); var file = try fs.openFileAbsolute(abs_file_path, .{}); defer file.close(); @@ -182,13 +195,15 @@ pub fn addCertsFromFilePath( gpa: Allocator, dir: fs.Dir, sub_file_path: []const u8, -) !void { +) AddCertsFromFilePathError!void { var file = try dir.openFile(sub_file_path, .{}); defer file.close(); return addCertsFromFile(cb, gpa, file); } -pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void { +pub const AddCertsFromFileError = Allocator.Error || fs.File.GetSeekPosError || fs.File.ReadError || ParseCertError || std.base64.Error || error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker }; + +pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void { const size = try file.getEndPos(); // We borrow `bytes` as a temporary buffer for the base64-encoded data. @@ -222,7 +237,9 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void { } } -pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) !void { +pub const ParseCertError = Allocator.Error || Certificate.ParseError; + +pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) ParseCertError!void { // Even though we could only partially parse the certificate to find // the subject name, we pre-parse all of them to make sure and only // include in the bundle ones that we know will parse. This way we can diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig index 5260aa61a6..dba0520795 100644 --- a/lib/std/crypto/Certificate/Bundle/macos.zig +++ b/lib/std/crypto/Certificate/Bundle/macos.zig @@ -5,7 +5,9 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const Bundle = @import("../Bundle.zig"); -pub fn rescanMac(cb: *Bundle, gpa: Allocator) !void { +pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream}; + +pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void { cb.bytes.clearRetainingCapacity(); cb.map.clearRetainingCapacity(); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 41f199a094..59df27c454 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -29,34 +29,16 @@ connection_pool: ConnectionPool = .{}, last_error: ?ExtraError = null, pub const ExtraError = union(enum) { - fn impliedErrorSet(comptime f: anytype) type { - const set = @typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?).ErrorUnion.error_set; - if (@typeName(set)[0] != '@') @compileError(@typeName(f) ++ " doesn't have an implied error set any more."); - return set; - } - - // There's apparently a dependency loop with using Client.DeflateDecompressor. - const FakeTransferError = proto.HeadersParser.ReadError || error{ReadFailed}; - const FakeTransferReader = std.io.Reader(void, FakeTransferError, fakeRead); - fn fakeRead(ctx: void, buf: []u8) FakeTransferError!usize { - _ = .{ buf, ctx }; - return 0; - } - - const FakeDeflateDecompressor = std.compress.zlib.ZlibStream(FakeTransferReader); - const FakeGzipDecompressor = std.compress.gzip.Decompress(FakeTransferReader); - const FakeZstdDecompressor = std.compress.zstd.DecompressStream(FakeTransferReader, .{}); - pub const TcpConnectError = std.net.TcpConnectToHostError; pub const TlsError = std.crypto.tls.Client.InitError(net.Stream); pub const WriteError = BufferedConnection.WriteError; pub const ReadError = BufferedConnection.ReadError || error{HttpChunkInvalid}; - pub const CaBundleError = impliedErrorSet(std.crypto.Certificate.Bundle.rescan); + pub const CaBundleError = std.crypto.Certificate.Bundle.RescanError; pub const ZlibInitError = error{ BadHeader, InvalidCompression, InvalidWindowSize, Unsupported, EndOfStream, OutOfMemory } || Request.TransferReadError; pub const GzipInitError = error{ BadHeader, InvalidCompression, OutOfMemory, WrongChecksum, EndOfStream, StreamTooLong } || Request.TransferReadError; - // pub const DecompressError = Client.DeflateDecompressor.Error || Client.GzipDecompressor.Error || Client.ZstdDecompressor.Error; - pub const DecompressError = FakeDeflateDecompressor.Error || FakeGzipDecompressor.Error || FakeZstdDecompressor.Error; + // pub const DecompressError = Compression.DeflateDecompressor.Error || Compression.GzipDecompressor.Error || Compression.ZstdDecompressor.Error; + pub const DecompressError = anyerror; // FIXME: the above line causes a false positive dependency loop zlib_init: ZlibInitError, // error.CompressionInitializationFailed gzip_init: GzipInitError, // error.CompressionInitializationFailed