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];
diff --git a/src/browser/tests/net/request.html b/src/browser/tests/net/request.html
index 0bca84d45..0f78a259e 100644
--- a/src/browser/tests/net/request.html
+++ b/src/browser/tests/net/request.html
@@ -259,6 +259,104 @@
}
+
+
+
+