Commit 63801c4b05

Andrew Kelley <andrew@ziglang.org>
2025-10-10 00:24:11
std.crypto.Certificate.Bundle: remove use of File.readAll
1 parent fcac861
Changed files (2)
lib
std
crypto
Certificate
http
lib/std/crypto/Certificate/Bundle.zig
@@ -4,6 +4,20 @@
 //! concatenated together in the `bytes` array. The `map` field contains an
 //! index from the DER-encoded subject name to the index of the containing
 //! certificate within `bytes`.
+const Bundle = @This();
+const builtin = @import("builtin");
+
+const std = @import("../../std.zig");
+const Io = std.Io;
+const assert = std.debug.assert;
+const fs = std.fs;
+const mem = std.mem;
+const crypto = std.crypto;
+const Allocator = std.mem.Allocator;
+const Certificate = std.crypto.Certificate;
+const der = Certificate.der;
+
+const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
 
 /// The key is the contents slice of the subject.
 map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,
@@ -56,17 +70,17 @@ pub const RescanError = RescanLinuxError || RescanMacError || RescanWithPathErro
 /// 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) RescanError!void {
+pub fn rescan(cb: *Bundle, gpa: Allocator, io: Io) RescanError!void {
     switch (builtin.os.tag) {
-        .linux => return rescanLinux(cb, gpa),
+        .linux => return rescanLinux(cb, gpa, io),
         .macos => return rescanMac(cb, gpa),
-        .freebsd, .openbsd => return rescanWithPath(cb, gpa, "/etc/ssl/cert.pem"),
-        .netbsd => return rescanWithPath(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),
-        .dragonfly => return rescanWithPath(cb, gpa, "/usr/local/etc/ssl/cert.pem"),
-        .illumos => return rescanWithPath(cb, gpa, "/etc/ssl/cacert.pem"),
-        .haiku => return rescanWithPath(cb, gpa, "/boot/system/data/ssl/CARootCertificates.pem"),
+        .freebsd, .openbsd => return rescanWithPath(cb, gpa, io, "/etc/ssl/cert.pem"),
+        .netbsd => return rescanWithPath(cb, gpa, io, "/etc/openssl/certs/ca-certificates.crt"),
+        .dragonfly => return rescanWithPath(cb, gpa, io, "/usr/local/etc/ssl/cert.pem"),
+        .illumos => return rescanWithPath(cb, gpa, io, "/etc/ssl/cacert.pem"),
+        .haiku => return rescanWithPath(cb, gpa, io, "/boot/system/data/ssl/CARootCertificates.pem"),
         // https://github.com/SerenityOS/serenity/blob/222acc9d389bc6b490d4c39539761b043a4bfcb0/Ports/ca-certificates/package.sh#L19
-        .serenity => return rescanWithPath(cb, gpa, "/etc/ssl/certs/ca-certificates.crt"),
+        .serenity => return rescanWithPath(cb, gpa, io, "/etc/ssl/certs/ca-certificates.crt"),
         .windows => return rescanWindows(cb, gpa),
         else => {},
     }
@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;
 
 const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;
 
-fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
+fn rescanLinux(cb: *Bundle, gpa: Allocator, io: Io) 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.
@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
 
     scan: {
         for (cert_file_paths) |cert_file_path| {
-            if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| {
+            if (addCertsFromFilePathAbsolute(cb, gpa, io, cert_file_path)) |_| {
                 break :scan;
             } else |err| switch (err) {
                 error.FileNotFound => continue,
@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
         }
 
         for (cert_dir_paths) |cert_dir_path| {
-            addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) {
+            addCertsFromDirPathAbsolute(cb, gpa, io, cert_dir_path) catch |err| switch (err) {
                 error.FileNotFound => continue,
                 else => |e| return e,
             };
@@ -121,10 +135,10 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
 
 const RescanWithPathError = AddCertsFromFilePathError;
 
-fn rescanWithPath(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanWithPathError!void {
+fn rescanWithPath(cb: *Bundle, gpa: Allocator, io: Io, cert_file_path: []const u8) RescanWithPathError!void {
     cb.bytes.clearRetainingCapacity();
     cb.map.clearRetainingCapacity();
-    try addCertsFromFilePathAbsolute(cb, gpa, cert_file_path);
+    try addCertsFromFilePathAbsolute(cb, gpa, io, cert_file_path);
     cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
 }
 
@@ -160,28 +174,30 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;
 pub fn addCertsFromDirPath(
     cb: *Bundle,
     gpa: Allocator,
+    io: Io,
     dir: fs.Dir,
     sub_dir_path: []const u8,
 ) AddCertsFromDirPathError!void {
     var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
     defer iterable_dir.close();
-    return addCertsFromDir(cb, gpa, iterable_dir);
+    return addCertsFromDir(cb, gpa, io, iterable_dir);
 }
 
 pub fn addCertsFromDirPathAbsolute(
     cb: *Bundle,
     gpa: Allocator,
+    io: Io,
     abs_dir_path: []const u8,
 ) AddCertsFromDirPathError!void {
     assert(fs.path.isAbsolute(abs_dir_path));
     var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
     defer iterable_dir.close();
-    return addCertsFromDir(cb, gpa, iterable_dir);
+    return addCertsFromDir(cb, gpa, io, iterable_dir);
 }
 
 pub const AddCertsFromDirError = AddCertsFromFilePathError;
 
-pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {
+pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, iterable_dir: fs.Dir) AddCertsFromDirError!void {
     var it = iterable_dir.iterate();
     while (try it.next()) |entry| {
         switch (entry.kind) {
@@ -189,32 +205,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer
             else => continue,
         }
 
-        try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);
+        try addCertsFromFilePath(cb, gpa, io, iterable_dir.adaptToNewApi(), entry.name);
     }
 }
 
-pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError;
+pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
 
 pub fn addCertsFromFilePathAbsolute(
     cb: *Bundle,
     gpa: Allocator,
+    io: Io,
     abs_file_path: []const u8,
 ) AddCertsFromFilePathError!void {
-    assert(fs.path.isAbsolute(abs_file_path));
+    const now = try Io.Clock.real.now(io);
     var file = try fs.openFileAbsolute(abs_file_path, .{});
     defer file.close();
-    return addCertsFromFile(cb, gpa, file);
+    var file_reader = file.reader(io, &.{});
+    return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
 }
 
 pub fn addCertsFromFilePath(
     cb: *Bundle,
     gpa: Allocator,
-    dir: fs.Dir,
+    io: Io,
+    dir: Io.Dir,
     sub_file_path: []const u8,
 ) AddCertsFromFilePathError!void {
-    var file = try dir.openFile(sub_file_path, .{});
-    defer file.close();
-    return addCertsFromFile(cb, gpa, file);
+    const now = try Io.Clock.real.now(io);
+    var file = try dir.openFile(io, sub_file_path, .{});
+    defer file.close(io);
+    var file_reader = file.reader(io, &.{});
+    return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
 }
 
 pub const AddCertsFromFileError = Allocator.Error ||
@@ -222,10 +243,10 @@ pub const AddCertsFromFileError = Allocator.Error ||
     fs.File.ReadError ||
     ParseCertError ||
     std.base64.Error ||
-    error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };
+    error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };
 
-pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {
-    const size = try file.getEndPos();
+pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file_reader: *Io.File.Reader, now_sec: i64) AddCertsFromFileError!void {
+    const size = try file_reader.getSize();
 
     // We borrow `bytes` as a temporary buffer for the base64-encoded data.
     // This is possible by computing the decoded length and reserving the space
@@ -236,14 +257,14 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
     try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
     const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
     const buffer = cb.bytes.allocatedSlice()[end_reserved..];
-    const end_index = try file.readAll(buffer);
+    const end_index = file_reader.interface.readSliceShort(buffer) catch |err| switch (err) {
+        error.ReadFailed => return file_reader.err.?,
+    };
     const encoded_bytes = buffer[0..end_index];
 
     const begin_marker = "-----BEGIN CERTIFICATE-----";
     const end_marker = "-----END CERTIFICATE-----";
 
-    const now_sec = std.time.timestamp();
-
     var start_index: usize = 0;
     while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
         const cert_start = begin_marker_start + begin_marker.len;
@@ -288,19 +309,6 @@ pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64)
     }
 }
 
-const builtin = @import("builtin");
-const std = @import("../../std.zig");
-const assert = std.debug.assert;
-const fs = std.fs;
-const mem = std.mem;
-const crypto = std.crypto;
-const Allocator = std.mem.Allocator;
-const Certificate = std.crypto.Certificate;
-const der = Certificate.der;
-const Bundle = @This();
-
-const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
-
 const MapContext = struct {
     cb: *const Bundle,
 
@@ -321,8 +329,11 @@ const MapContext = struct {
 test "scan for OS-provided certificates" {
     if (builtin.os.tag == .wasi) return error.SkipZigTest;
 
+    const io = std.testing.io;
+    const gpa = std.testing.allocator;
+
     var bundle: Bundle = .{};
-    defer bundle.deinit(std.testing.allocator);
+    defer bundle.deinit(gpa);
 
-    try bundle.rescan(std.testing.allocator);
+    try bundle.rescan(gpa, io);
 }
lib/std/http/Client.zig
@@ -1666,6 +1666,8 @@ pub fn request(
     uri: Uri,
     options: RequestOptions,
 ) RequestError!Request {
+    const io = client.io;
+
     if (std.debug.runtime_safety) {
         for (options.extra_headers) |header| {
             assert(header.name.len != 0);
@@ -1689,7 +1691,7 @@ pub fn request(
             defer client.ca_bundle_mutex.unlock();
 
             if (client.next_https_rescan_certs) {
-                client.ca_bundle.rescan(client.allocator) catch
+                client.ca_bundle.rescan(client.allocator, io) catch
                     return error.CertificateBundleLoadFailure;
                 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
             }