diff --git a/src/browser/DataURI.zig b/src/browser/DataURI.zig new file mode 100644 index 00000000..00d3792f --- /dev/null +++ b/src/browser/DataURI.zig @@ -0,0 +1,52 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// Parses data:[][;base64], +pub fn parse(allocator: Allocator, src: []const u8) !?[]const u8 { + if (!std.mem.startsWith(u8, src, "data:")) { + return null; + } + + const uri = src[5..]; + const data_starts = std.mem.indexOfScalar(u8, uri, ',') orelse return null; + + var data = uri[data_starts + 1 ..]; + + // Extract the encoding. + const metadata = uri[0..data_starts]; + if (std.mem.endsWith(u8, metadata, ";base64")) { + const decoder = std.base64.standard.Decoder; + const decoded_size = try decoder.calcSizeForSlice(data); + + const buffer = try allocator.alloc(u8, decoded_size); + errdefer allocator.free(buffer); + + try decoder.decode(buffer, data); + data = buffer; + } + + return data; +} + +const testing = @import("../testing.zig"); +test "DataURI: parse valid" { + try test_valid("data:text/javascript; charset=utf-8;base64,Zm9v", "foo"); + try test_valid("data:text/javascript; charset=utf-8;,foo", "foo"); + try test_valid("data:,foo", "foo"); +} + +test "DataURI: parse invalid" { + try test_cannot_parse("atad:,foo"); + try test_cannot_parse("data:foo"); + try test_cannot_parse("data:"); +} + +fn test_valid(uri: []const u8, expected: []const u8) !void { + defer testing.reset(); + const data_uri = try parse(testing.arena_allocator, uri) orelse return error.TestFailed; + try testing.expectEqual(expected, data_uri); +} + +fn test_cannot_parse(uri: []const u8) !void { + try testing.expectEqual(null, parse(undefined, uri)); +} diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 47dfb354..e5108dbd 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -23,6 +23,7 @@ const parser = @import("netsurf.zig"); const Env = @import("env.zig").Env; const Page = @import("page.zig").Page; +const DataURI = @import("DataURI.zig"); const Browser = @import("browser.zig").Browser; const HttpClient = @import("../http/Client.zig"); const URL = @import("../url.zig").URL; @@ -168,6 +169,9 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void { var source: Script.Source = undefined; var remote_url: ?[:0]const u8 = null; if (try parser.elementGetAttribute(element, "src")) |src| { + if (try DataURI.parse(page.arena, src)) |data_uri| { + source = .{ .@"inline" = data_uri }; + } remote_url = try URL.stitch(page.arena, src, page.url.raw, .{ .null_terminated = true }); source = .{ .remote = .{} }; } else { diff --git a/src/browser/datauri.zig b/src/browser/datauri.zig deleted file mode 100644 index d600a255..00000000 --- a/src/browser/datauri.zig +++ /dev/null @@ -1,79 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -// Represents https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data -pub const DataURI = struct { - was_base64_encoded: bool, - // The contents in the uri. It will be base64 decoded but not prepared in - // any way for mime.charset. - data: []const u8, - - // Parses data:[][;base64], - pub fn parse(allocator: Allocator, src: []const u8) !?DataURI { - if (!std.mem.startsWith(u8, src, "data:")) { - return null; - } - - const uri = src[5..]; - const data_starts = std.mem.indexOfScalar(u8, uri, ',') orelse return null; - - // Extract the encoding. - var metadata = uri[0..data_starts]; - var base64_encoded = false; - if (std.mem.endsWith(u8, metadata, ";base64")) { - base64_encoded = true; - metadata = metadata[0 .. metadata.len - 7]; - } - - // TODO: Extract mime type. This not trivial because Mime.parse requires - // a []u8 and might mutate the src. And, the DataURI.parse references atm - // do not have deinit calls. - - // Prepare the data. - var data = uri[data_starts + 1 ..]; - if (base64_encoded) { - const decoder = std.base64.standard.Decoder; - const decoded_size = try decoder.calcSizeForSlice(data); - - const buffer = try allocator.alloc(u8, decoded_size); - errdefer allocator.free(buffer); - - try decoder.decode(buffer, data); - data = buffer; - } - - return .{ - .was_base64_encoded = base64_encoded, - .data = data, - }; - } - - pub fn deinit(self: *const DataURI, allocator: Allocator) void { - if (self.was_base64_encoded) { - allocator.free(self.data); - } - } -}; - -const testing = std.testing; -test "DataURI: parse valid" { - try test_valid("data:text/javascript; charset=utf-8;base64,Zm9v", "foo"); - try test_valid("data:text/javascript; charset=utf-8;,foo", "foo"); - try test_valid("data:,foo", "foo"); -} - -test "DataURI: parse invalid" { - try test_cannot_parse("atad:,foo"); - try test_cannot_parse("data:foo"); - try test_cannot_parse("data:"); -} - -fn test_valid(uri: []const u8, expected: []const u8) !void { - const data_uri = try DataURI.parse(std.testing.allocator, uri) orelse return error.TestFailed; - defer data_uri.deinit(testing.allocator); - try testing.expectEqualStrings(expected, data_uri.data); -} - -fn test_cannot_parse(uri: []const u8) !void { - try testing.expectEqual(null, DataURI.parse(std.testing.allocator, uri)); -} diff --git a/src/browser/page.zig b/src/browser/page.zig index 384ea1f6..30c959d4 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -25,7 +25,6 @@ const Dump = @import("dump.zig"); const State = @import("State.zig"); const Env = @import("env.zig").Env; const Mime = @import("mime.zig").Mime; -const DataURI = @import("datauri.zig").DataURI; const Session = @import("session.zig").Session; const Renderer = @import("renderer.zig").Renderer; const Window = @import("html/window.zig").Window;