From af45e00a9444e73a275abb9fa95c9416bec22b5d Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 28 Jul 2026 14:01:49 +0300 Subject: [PATCH] `Mime`: move `ContentTypeIterator` to `Mime` --- src/browser/Mime.zig | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/browser/Mime.zig b/src/browser/Mime.zig index 72268c7fb..6b5d85068 100644 --- a/src/browser/Mime.zig +++ b/src/browser/Mime.zig @@ -90,6 +90,70 @@ pub fn contentTypeString(mime: *const Mime) []const u8 { }; } +/// Parses `essence` and parameters of `Content-Type` header value. +pub const ContentTypeIterator = struct { + /// Header value. + rest: []const u8, + /// Always the first. + essence: []const u8, + + /// Initializes a `ContentTypeIterator`. + pub fn init(content_type: []const u8) ContentTypeIterator { + // Skip whitespace. + const trimmed = std.mem.trimStart(u8, content_type, &.{ ' ', '\t' }); + // Find semicolon delimiter; or just use the end position. + const end = std.mem.indexOfScalar(u8, trimmed, ';') orelse trimmed.len; + const essence = std.mem.trimEnd(u8, trimmed[0..end], &.{ ' ', '\t' }); + + // Rest of the parameters. + const rest = trimmed[end..]; + return .{ .rest = rest, .essence = essence }; + } + + pub const Parameter = struct { + key: []const u8, + /// `value` can be an empty string (""). + value: []const u8, + }; + + /// Returns the next parameter or null if there aren't any parameters. + pub fn next(self: *ContentTypeIterator) ?Parameter { + while (self.rest.len > 0) { + // `rest` always sits at the `;` that introduced this parameter. + var param = self.rest[1..]; + const end = std.mem.indexOfScalar(u8, param, ';') orelse param.len; + self.rest = param[end..]; + param = std.mem.trim(u8, param[0..end], " \t"); + + // Parameters without `=` are malformed; skip them. + const eq = std.mem.indexOfScalar(u8, param, '=') orelse continue; + const key = std.mem.trimEnd(u8, param[0..eq], " \t"); + if (key.len == 0) { + continue; + } + + var value = std.mem.trimStart(u8, param[eq + 1 ..], " \t"); + if (value.len >= 2 and value[0] == '"' and value[value.len - 1] == '"') { + value = value[1 .. value.len - 1]; + } + return .{ .key = key, .value = value }; + } + + return null; + } + + /// Returns boundary string; consuming other parameters along the way. + /// Boundary can be an empty string. + pub fn findBoundary(self: *ContentTypeIterator) []const u8 { + while (self.next()) |p| { + if (std.ascii.eqlIgnoreCase(p.key, "boundary")) { + return p.value; + } + } + return ""; + } +}; + /// Returns the null-terminated charset value. pub fn charsetStringZ(mime: *const Mime) [:0]const u8 { return mime.charset[0..mime.charset_len :0];