Skip to content

Commit b328392

Browse files
authored
Support Data URI in scripts tags (#596)
* Support text/javascript mime type * Support base64 encoded scripts Related to #412
1 parent d9f21e0 commit b328392

File tree

3 files changed

+106
-8
lines changed

3 files changed

+106
-8
lines changed

src/browser/browser.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
2424

2525
const Dump = @import("dump.zig");
2626
const Mime = @import("mime.zig").Mime;
27+
const DataURI = @import("datauri.zig").DataURI;
2728
const parser = @import("netsurf.zig");
2829

2930
const Window = @import("html/window.zig").Window;
@@ -585,6 +586,12 @@ pub const Page = struct {
585586
log.debug("starting fetch {s}", .{src});
586587

587588
const arena = self.arena;
589+
590+
// Handle data URIs.
591+
if (try DataURI.parse(arena, src)) |data_uri| {
592+
return data_uri.data;
593+
}
594+
588595
var res_src = src;
589596

590597
// if a base path is given, we resolve src using base.

src/browser/datauri.zig

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const std = @import("std");
2+
const Allocator = std.mem.Allocator;
3+
4+
// Represents https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data
5+
pub const DataURI = struct {
6+
was_base64_encoded: bool,
7+
// The contents in the uri. It will be base64 decoded but not prepared in
8+
// any way for mime.charset.
9+
data: []const u8,
10+
11+
// Parses data:[<media-type>][;base64],<data>
12+
pub fn parse(allocator: Allocator, src: []const u8) !?DataURI {
13+
if (!std.mem.startsWith(u8, src, "data:")) {
14+
return null;
15+
}
16+
17+
const uri = src[5..];
18+
const data_starts = std.mem.indexOfScalar(u8, uri, ',') orelse return null;
19+
20+
// Extract the encoding.
21+
var metadata = uri[0..data_starts];
22+
var base64_encoded = false;
23+
if (std.mem.endsWith(u8, metadata, ";base64")) {
24+
base64_encoded = true;
25+
metadata = metadata[0 .. metadata.len - 7];
26+
}
27+
28+
// TODO: Extract mime type. This not trivial because Mime.parse requires
29+
// a []u8 and might mutate the src. And, the DataURI.parse references atm
30+
// do not have deinit calls.
31+
32+
// Prepare the data.
33+
var data = uri[data_starts + 1 ..];
34+
if (base64_encoded) {
35+
const decoder = std.base64.standard.Decoder;
36+
const decoded_size = try decoder.calcSizeForSlice(data);
37+
38+
const buffer = try allocator.alloc(u8, decoded_size);
39+
errdefer allocator.free(buffer);
40+
41+
try decoder.decode(buffer, data);
42+
data = buffer;
43+
}
44+
45+
return .{
46+
.was_base64_encoded = base64_encoded,
47+
.data = data,
48+
};
49+
}
50+
51+
pub fn deinit(self: *const DataURI, allocator: Allocator) void {
52+
if (self.was_base64_encoded) {
53+
allocator.free(self.data);
54+
}
55+
}
56+
};
57+
58+
const testing = std.testing;
59+
test "DataURI: parse valid" {
60+
try test_valid("data:text/javascript; charset=utf-8;base64,Zm9v", "foo");
61+
try test_valid("data:text/javascript; charset=utf-8;,foo", "foo");
62+
try test_valid("data:,foo", "foo");
63+
}
64+
65+
test "DataURI: parse invalid" {
66+
try test_cannot_parse("atad:,foo");
67+
try test_cannot_parse("data:foo");
68+
try test_cannot_parse("data:");
69+
}
70+
71+
fn test_valid(uri: []const u8, expected: []const u8) !void {
72+
const data_uri = try DataURI.parse(std.testing.allocator, uri) orelse return error.TestFailed;
73+
defer data_uri.deinit(testing.allocator);
74+
try testing.expectEqualStrings(expected, data_uri.data);
75+
}
76+
77+
fn test_cannot_parse(uri: []const u8) !void {
78+
try testing.expectEqual(null, DataURI.parse(std.testing.allocator, uri));
79+
}

src/browser/mime.zig

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub const Mime = struct {
3333
pub const ContentTypeEnum = enum {
3434
text_xml,
3535
text_html,
36+
text_javascript,
3637
text_plain,
3738
unknown,
3839
other,
@@ -41,6 +42,7 @@ pub const Mime = struct {
4142
pub const ContentType = union(ContentTypeEnum) {
4243
text_xml: void,
4344
text_html: void,
45+
text_javascript: void,
4446
text_plain: void,
4547
unknown: void,
4648
other: struct { type: []const u8, sub_type: []const u8 },
@@ -172,11 +174,17 @@ pub const Mime = struct {
172174
if (std.meta.stringToEnum(enum {
173175
@"text/xml",
174176
@"text/html",
177+
178+
@"text/javascript",
179+
@"application/javascript",
180+
@"application/x-javascript",
181+
175182
@"text/plain",
176183
}, type_name)) |known_type| {
177184
const ct: ContentType = switch (known_type) {
178185
.@"text/xml" => .{ .text_xml = {} },
179186
.@"text/html" => .{ .text_html = {} },
187+
.@"text/javascript", .@"application/javascript", .@"application/x-javascript" => .{ .text_javascript = {} },
180188
.@"text/plain" => .{ .text_plain = {} },
181189
};
182190
return .{ ct, attribute_start };
@@ -337,22 +345,26 @@ test "Mime: parse common" {
337345
try expect(.{ .content_type = .{ .text_xml = {} } }, " TeXT/xml");
338346
try expect(.{ .content_type = .{ .text_html = {} } }, "teXt/HtML ;");
339347
try expect(.{ .content_type = .{ .text_plain = {} } }, "tExT/PlAiN;");
348+
349+
try expect(.{ .content_type = .{ .text_javascript = {} } }, "text/javascript");
350+
try expect(.{ .content_type = .{ .text_javascript = {} } }, "Application/JavaScript");
351+
try expect(.{ .content_type = .{ .text_javascript = {} } }, "application/x-javascript");
340352
}
341353

342354
test "Mime: parse uncommon" {
343355
defer testing.reset();
344356

345-
const text_javascript = Expectation{
346-
.content_type = .{ .other = .{ .type = "text", .sub_type = "javascript" } },
357+
const text_csv = Expectation{
358+
.content_type = .{ .other = .{ .type = "text", .sub_type = "csv" } },
347359
};
348-
try expect(text_javascript, "text/javascript");
349-
try expect(text_javascript, "text/javascript;");
350-
try expect(text_javascript, " text/javascript\t ");
351-
try expect(text_javascript, " text/javascript\t ;");
360+
try expect(text_csv, "text/csv");
361+
try expect(text_csv, "text/csv;");
362+
try expect(text_csv, " text/csv\t ");
363+
try expect(text_csv, " text/csv\t ;");
352364

353365
try expect(
354-
.{ .content_type = .{ .other = .{ .type = "text", .sub_type = "javascript" } } },
355-
"Text/Javascript",
366+
.{ .content_type = .{ .other = .{ .type = "text", .sub_type = "csv" } } },
367+
"Text/CSV",
356368
);
357369
}
358370

0 commit comments

Comments
 (0)