diff options
author | 2023-08-07 20:15:53 -0700 | |
---|---|---|
committer | 2023-08-07 20:15:53 -0700 | |
commit | 2fe6a965af394db7228dce56ce29b629c650764c (patch) | |
tree | 9d0df164cd0ffcc2c1000a58aa498476abf0b3c6 /src | |
parent | a32097aa9f53f7c8ea1331c61dc2658bc1b11208 (diff) | |
download | bun-2fe6a965af394db7228dce56ce29b629c650764c.tar.gz bun-2fe6a965af394db7228dce56ce29b629c650764c.tar.zst bun-2fe6a965af394db7228dce56ce29b629c650764c.zip |
implement fetching data urls (#4000)
* fetch data urls
* `byteSlice`
* deinit slice
* allocate `mime_type` string if needed
* `content_type_allocated` and uncomment tests
* `str_`
* createAtom and slice decode result
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/ResolveMessage.zig | 3 | ||||
-rw-r--r-- | src/bun.js/webcore/body.zig | 2 | ||||
-rw-r--r-- | src/bun.js/webcore/response.zig | 66 | ||||
-rw-r--r-- | src/http.zig | 2 | ||||
-rw-r--r-- | src/http/mime_type.zig | 34 | ||||
-rw-r--r-- | src/js_ast.zig | 6 | ||||
-rw-r--r-- | src/resolver/data_url.zig | 33 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 4 |
8 files changed, 129 insertions, 21 deletions
diff --git a/src/bun.js/ResolveMessage.zig b/src/bun.js/ResolveMessage.zig index b9b2d7b06..457a8ec4f 100644 --- a/src/bun.js/ResolveMessage.zig +++ b/src/bun.js/ResolveMessage.zig @@ -35,6 +35,9 @@ pub const ResolveMessage = struct { return try std.fmt.allocPrint(allocator, "Cannot find module \"{s}\" from \"{s}\"", .{ specifier, referrer }); } }, + error.InvalidDataURL => { + return try std.fmt.allocPrint(allocator, "Cannot resolve invalid data URL \"{s}\" from \"{s}\"", .{ specifier, referrer }); + }, else => { if (Resolver.isPackagePath(specifier)) { return try std.fmt.allocPrint(allocator, "{s} while resolving package \"{s}\" from \"{s}\"", .{ @errorName(err), specifier, referrer }); diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 2007063f3..0dea0579d 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -1222,7 +1222,7 @@ pub fn BodyMixin(comptime Type: type) type { if (blob.content_type.len == 0 and blob.store != null) { if (this.getFetchHeaders()) |fetch_headers| { if (fetch_headers.fastGet(.ContentType)) |content_type| { - blob.store.?.mime_type = MimeType.init(content_type.slice()); + blob.store.?.mime_type = MimeType.init(content_type.slice(), null, null); } } else { blob.store.?.mime_type = MimeType.text; diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 3341c6eaf..0d5691a3f 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -37,6 +37,7 @@ const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const DataURL = @import("../../resolver/data_url.zig").DataURL; const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; @@ -971,6 +972,45 @@ pub const Fetch = struct { } }; + fn dataURLResponse( + _data_url: DataURL, + globalThis: *JSGlobalObject, + allocator: std.mem.Allocator, + ) JSValue { + var data_url = _data_url; + + const data = data_url.decodeData(allocator) catch { + const err = JSC.createError(globalThis, "failed to fetch the data URL", .{}); + return JSPromise.rejectedPromiseValue(globalThis, err); + }; + var blob = Blob.init(data, allocator, globalThis); + + var allocated = false; + const mime_type = bun.HTTP.MimeType.init(data_url.mime_type, allocator, &allocated); + blob.content_type = mime_type.value; + if (allocated) { + blob.content_type_allocated = true; + } + + var response = allocator.create(Response) catch @panic("out of memory"); + + response.* = Response{ + .body = Body{ + .init = Body.Init{ + .status_code = 200, + }, + .value = .{ + .Blob = blob, + }, + }, + .allocator = allocator, + .status_text = bun.String.createAtom("OK"), + .url = data_url.url.dupeRef(), + }; + + return JSPromise.resolvedPromiseValue(globalThis, response.toJS(globalThis)); + } + pub export fn Bun__fetch( ctx: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, @@ -1038,6 +1078,19 @@ pub const Fetch = struct { return JSPromise.rejectedPromiseValue(globalThis, err); } + if (request.url.hasPrefixComptime("data:")) { + var url_slice = request.url.toUTF8WithoutRef(bun.default_allocator); + defer url_slice.deinit(); + + var data_url = DataURL.parseWithoutCheck(url_slice.slice()) catch { + const err = JSC.createError(globalThis, "failed to fetch the data URL", .{}); + return JSPromise.rejectedPromiseValue(globalThis, err); + }; + + data_url.url = request.url; + return dataURLResponse(data_url, globalThis, bun.default_allocator); + } + url = ZigURL.fromString(bun.default_allocator, request.url) catch { const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() URL is invalid", .{}, ctx); // clean hostname if any @@ -1187,6 +1240,19 @@ pub const Fetch = struct { return JSPromise.rejectedPromiseValue(globalThis, err); } + if (str.hasPrefixComptime("data:")) { + var url_slice = str.toUTF8WithoutRef(bun.default_allocator); + defer url_slice.deinit(); + + var data_url = DataURL.parseWithoutCheck(url_slice.slice()) catch { + const err = JSC.createError(globalThis, "failed to fetch the data URL", .{}); + return JSPromise.rejectedPromiseValue(globalThis, err); + }; + data_url.url = str; + + return dataURLResponse(data_url, globalThis, bun.default_allocator); + } + url = ZigURL.fromString(bun.default_allocator, str) catch { // clean hostname if any if (hostname) |host| { diff --git a/src/http.zig b/src/http.zig index c206fbf66..61cf99095 100644 --- a/src/http.zig +++ b/src/http.zig @@ -2223,7 +2223,7 @@ pub const RequestContext = struct { const accept: MimeType = brk: { if (ctx.header("Accept")) |accept| - break :brk MimeType.init(accept); + break :brk MimeType.init(accept, null, null); break :brk ctx.mime_type; }; diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig index 17f6821b3..fb4043482 100644 --- a/src/http/mime_type.zig +++ b/src/http/mime_type.zig @@ -110,7 +110,7 @@ fn initComptime(comptime str: string, t: Category) MimeType { }; } -pub fn init(str_: string) MimeType { +pub fn init(str_: string, allocator: ?std.mem.Allocator, allocated: ?*bool) MimeType { var str = str_; if (std.mem.indexOfScalar(u8, str, '/')) |slash| { const category_ = str[0..slash]; @@ -141,12 +141,17 @@ pub fn init(str_: string) MimeType { return wasm; } - return MimeType{ .value = str_, .category = .application }; + if (allocated != null and allocator != null) allocated.?.* = true; + return MimeType{ + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .category = .application, + }; }, "font".len => { if (strings.eqlComptimeIgnoreLen(category_, "font")) { + if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, .category = .font, }; } @@ -168,27 +173,34 @@ pub fn init(str_: string) MimeType { return all.@"text/plain"; } - return MimeType{ .value = str_, .category = .text }; + if (allocated != null and allocator != null) allocated.?.* = true; + return MimeType{ + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .category = .text, + }; } }, "image".len => { if (strings.eqlComptimeIgnoreLen(category_, "image")) { + if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, .category = .image, }; } if (strings.eqlComptimeIgnoreLen(category_, "audio")) { + if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, .category = .audio, }; } if (strings.eqlComptimeIgnoreLen(category_, "video")) { + if (allocated != null and allocator != null) allocated.?.* = true; return MimeType{ - .value = str_, + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, .category = .video, }; } @@ -197,7 +209,11 @@ pub fn init(str_: string) MimeType { } } - return MimeType{ .value = str_, .category = .other }; + if (allocated != null and allocator != null) allocated.?.* = true; + return MimeType{ + .value = if (allocator) |a| a.dupe(u8, str_) catch unreachable else str_, + .category = .other, + }; } // TODO: improve this @@ -2516,7 +2532,7 @@ pub const all = struct { // TODO: do a comptime static hash map for this // its too many branches to use ComptimeStringMap pub fn byName(name: []const u8) MimeType { - return MimeType.init(name); + return MimeType.init(name, null, null); } pub const extensions = ComptimeStringMap(MimeType, .{ diff --git a/src/js_ast.zig b/src/js_ast.zig index e7466554f..2682c7f2b 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -3076,7 +3076,7 @@ pub const Expr = struct { ) !Expr { var bytes = blob.sharedView(); - const mime_type = mime_type_ orelse HTTP.MimeType.init(blob.content_type); + const mime_type = mime_type_ orelse HTTP.MimeType.init(blob.content_type, null, null); if (mime_type.category == .json) { var source = logger.Source.initPathString("fetch.json", bytes); @@ -9794,10 +9794,10 @@ pub const Macro = struct { if (value.jsType() == .DOMWrapper) { if (value.as(JSC.WebCore.Response)) |resp| { - mime_type = HTTP.MimeType.init(resp.mimeType(null)); + mime_type = HTTP.MimeType.init(resp.mimeType(null), null, null); blob_ = resp.body.use(); } else if (value.as(JSC.WebCore.Request)) |resp| { - mime_type = HTTP.MimeType.init(resp.mimeType()); + mime_type = HTTP.MimeType.init(resp.mimeType(), null, null); blob_ = resp.body.value.use(); } else if (value.as(JSC.WebCore.Blob)) |resp| { blob_ = resp.*; diff --git a/src/resolver/data_url.zig b/src/resolver/data_url.zig index dd31605d9..f2a042c4f 100644 --- a/src/resolver/data_url.zig +++ b/src/resolver/data_url.zig @@ -34,7 +34,7 @@ pub const PercentEncoding = struct { if (comptime Environment.allow_assert) std.debug.assert(str.len > 0); return switch (str[0]) { 'a'...'z', 'A'...'Z', '0'...'9', '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@' => true, - '%' => str.len > 3 and isHex(str[1]) and isHex(str[2]), + '%' => str.len >= 3 and isHex(str[1]) and isHex(str[2]), else => false, }; } @@ -53,7 +53,7 @@ pub const PercentEncoding = struct { } if (ret == null) { ret = try allocator.alloc(u8, path.len); - bun.copy(u8, ret, path[0..i]); + bun.copy(u8, ret.?, path[0..i]); ret_index = i; } @@ -71,22 +71,28 @@ pub const PercentEncoding = struct { } } - if (ret) |some| return allocator.shrink(some, ret_index); + if (ret) |some| return some[0..ret_index]; return null; } }; pub const DataURL = struct { + url: bun.String = bun.String.empty, mime_type: string, data: string, is_base64: bool = false, - pub fn parse(url: string) ?DataURL { + pub fn parse(url: string) !?DataURL { if (!strings.startsWith(url, "data:")) { return null; } - const comma = strings.indexOfChar(url, ',') orelse return null; + var result = try parseWithoutCheck(url); + return result; + } + + pub fn parseWithoutCheck(url: string) !DataURL { + const comma = strings.indexOfChar(url, ',') orelse return error.InvalidDataURL; var parsed = DataURL{ .mime_type = url["data:".len..comma], @@ -102,6 +108,21 @@ pub const DataURL = struct { } pub fn decodeMimeType(d: DataURL) bun.HTTP.MimeType { - return bun.HTTP.MimeType.init(d.mime_type); + return bun.HTTP.MimeType.init(d.mime_type, null, null); + } + + pub fn decodeData(url: DataURL, allocator: std.mem.Allocator) ![]u8 { + const percent_decoded = PercentEncoding.decode(allocator, url.data) catch url.data orelse url.data; + if (url.is_base64) { + const len = bun.base64.decodeLen(percent_decoded); + var buf = try allocator.alloc(u8, len); + const result = bun.base64.decode(buf, percent_decoded); + if (result.fail or result.written != len) { + return error.Base64DecodeError; + } + return buf; + } + + return allocator.dupe(u8, percent_decoded); } }; diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index e4d7686a4..4aa4239dd 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -842,7 +842,9 @@ pub const Resolver = struct { } } - if (DataURL.parse(import_path)) |_data_url| { + if (DataURL.parse(import_path) catch { + return .{ .failure = error.InvalidDataURL }; + }) |_data_url| { const data_url: DataURL = _data_url; // "import 'data:text/javascript,console.log(123)';" // "@import 'data:text/css,body{background:white}';" |