aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-08-07 20:15:53 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-07 20:15:53 -0700
commit2fe6a965af394db7228dce56ce29b629c650764c (patch)
tree9d0df164cd0ffcc2c1000a58aa498476abf0b3c6 /src
parenta32097aa9f53f7c8ea1331c61dc2658bc1b11208 (diff)
downloadbun-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.zig3
-rw-r--r--src/bun.js/webcore/body.zig2
-rw-r--r--src/bun.js/webcore/response.zig66
-rw-r--r--src/http.zig2
-rw-r--r--src/http/mime_type.zig34
-rw-r--r--src/js_ast.zig6
-rw-r--r--src/resolver/data_url.zig33
-rw-r--r--src/resolver/resolver.zig4
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}';"