[bun dev] Automatically set origin - improve support for proxying Bun

Previously, when running Bun behind a reverse proxy, you had to pass an explicit `--origin` arg and it could only run behind one proxy at a time.

Now, Bun automatically determines the origin from the request if possible. It reads `Forwarded`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `Origin`, and lastly `Host`. If none are available, it falls back to the `--origin` CLI arg.

This change is important for usecases like Replit which shows multiple iframes in different origins.
This commit is contained in:
Jarred Sumner
2022-01-01 18:12:57 -08:00
parent e4693b8aaf
commit 6a28cfd2ba
8 changed files with 247 additions and 64 deletions

View File

@@ -10,7 +10,6 @@ const default_allocator = _global.default_allocator;
const StoredFileDescriptorType = _global.StoredFileDescriptorType;
const FeatureFlags = _global.FeatureFlags;
const C = _global.C;
const std = @import("std");
const lex = @import("js_lexer.zig");
const logger = @import("logger.zig");
@@ -49,6 +48,7 @@ const NewBunQueue = @import("./bun_queue.zig").NewBunQueue;
const NodeFallbackModules = @import("./node_fallbacks.zig");
const CacheEntry = @import("./cache.zig").FsCacheEntry;
const Analytics = @import("./analytics/analytics_thread.zig");
const URL = @import("./query_string_map.zig").URL;
const Linker = linker.Linker;
const Resolver = _resolver.Resolver;
@@ -2166,6 +2166,7 @@ pub const Bundler = struct {
comptime WatcherType: type,
watcher: *WatcherType,
client_entry_point: ?*ClientEntryPoint,
origin: URL,
) !BuildResolveResultPair {
if (resolve_result.is_external) {
return BuildResolveResultPair{
@@ -2230,6 +2231,7 @@ pub const Bundler = struct {
allocator,
bundler.log,
&bundler.linker,
origin,
)).written;
} else {
break :brk (try CSSBundler.bundle(
@@ -2243,6 +2245,7 @@ pub const Bundler = struct {
allocator,
bundler.log,
&bundler.linker,
origin,
)).written;
}
},
@@ -2274,7 +2277,7 @@ pub const Bundler = struct {
return BuildResolveResultPair{ .written = 0, .input_fd = result.input_fd, .empty = true };
}
try bundler.linker.link(file_path, &result, import_path_format, false);
try bundler.linker.link(file_path, &result, origin, import_path_format, false);
if (bundler.options.platform.isBun()) {
return BuildResolveResultPair{
@@ -2382,6 +2385,7 @@ pub const Bundler = struct {
try bundler.linker.link(
file_path,
&result,
bundler.options.origin,
import_path_format,
false,
);
@@ -2411,11 +2415,15 @@ pub const Bundler = struct {
output_file.value = .{ .move = file_op };
},
.css => {
const CSSBuildContext = struct {
origin: URL,
};
var build_ctx = CSSBuildContext{ .origin = bundler.options.origin };
const CSSWriter = Css.NewWriter(
std.fs.File,
@TypeOf(&bundler.linker),
import_path_format,
void,
CSSBuildContext,
);
const entry = bundler.resolver.caches.fs.readFile(
bundler.fs,
@@ -2434,6 +2442,7 @@ pub const Bundler = struct {
&bundler.linker,
bundler.log,
);
css_writer.buildCtx = build_ctx;
var did_warn = false;
try css_writer.run(bundler.log, bundler.allocator, &did_warn);
output_file.size = css_writer.written;

View File

@@ -19,7 +19,7 @@ const logger = @import("./logger.zig");
const Options = options;
const resolver = @import("./resolver/resolver.zig");
const _linker = @import("./linker.zig");
const URL = @import("./query_string_map.zig").URL;
const replacementCharacter: CodePoint = 0xFFFD;
pub const Chunk = struct {
@@ -1020,6 +1020,7 @@ pub fn NewWriter(
import.text.utf8,
chunk.range,
import_record.ImportKind.at,
writer.buildCtx.origin,
Options.BundleOptions.ImportPathFormat.absolute_path,
true,
) catch |err| {
@@ -1066,6 +1067,7 @@ pub fn NewWriter(
url.utf8,
chunk.range,
import_record.ImportKind.url,
writer.buildCtx.origin,
import_path_format,
true,
);
@@ -1078,6 +1080,7 @@ pub fn NewWriter(
import.text.utf8,
chunk.range,
import_record.ImportKind.at,
writer.buildCtx.origin,
import_path_format,
false,
);
@@ -1155,6 +1158,8 @@ pub fn NewBundler(
fs_reader: FileReader,
fs: FSType,
allocator: std.mem.Allocator,
origin: URL = URL{},
pub fn bundle(
absolute_path: string,
fs: FSType,
@@ -1166,6 +1171,7 @@ pub fn NewBundler(
allocator: std.mem.Allocator,
log: *logger.Log,
linker: Linker,
origin: URL,
) !CodeCount {
if (!has_set_global_queue) {
global_queued = QueuedList.init(default_allocator);
@@ -1186,7 +1192,7 @@ pub fn NewBundler(
.writer = writer,
.fs_reader = fs_reader,
.fs = fs,
.origin = origin,
.allocator = allocator,
.watcher = watcher,
};

View File

@@ -94,6 +94,7 @@ pub const RequestContext = struct {
watcher: *Watcher,
timer: std.time.Timer,
matched_route: ?Router.Match = null,
origin: ZigURL,
full_url: [:0]const u8 = "",
res_headers_count: usize = 0,
@@ -101,10 +102,92 @@ pub const RequestContext = struct {
/// --disable-bun.js propagates here
pub var fallback_only = false;
fn parseOrigin(this: *RequestContext) void {
var protocol: ?string = null;
var host: ?string = null;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
if (this.header("Forwarded")) |forwarded| {
if (strings.indexOf(forwarded, "host=")) |host_start| {
const host_i = host_start + "host=".len;
const host_ = forwarded[host_i..][0 .. strings.indexOfChar(forwarded[host_i..], ';') orelse forwarded[host_i..].len];
if (host_.len > 0) {
host = host_;
}
}
if (strings.indexOf(forwarded, "proto=")) |protocol_start| {
const protocol_i = protocol_start + "proto=".len;
if (strings.eqlComptime(forwarded[protocol_i..][0 .. strings.indexOfChar(forwarded[protocol_i..], ';') orelse forwarded[protocol_i..].len], "https")) {
protocol = "https";
} else {
protocol = "http";
}
}
}
if (protocol == null) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
determine_protocol: {
if (this.header("X-Forwarded-Proto")) |proto| {
if (strings.eqlComptime(proto, "https")) {
protocol = "https";
break :determine_protocol;
}
}
// Microsoft IIS
if (this.header("Front-End-Https")) |proto| {
if (strings.eqlComptime(proto, "on")) {
protocol = "https";
break :determine_protocol;
}
}
}
}
if (host == null) {
determine_host: {
if (this.header("X-Forwarded-Host")) |_host| {
host = _host;
break :determine_host;
}
}
if (this.header("Origin")) |origin| {
this.origin = ZigURL.parse(origin);
return;
}
}
if (host != null or protocol != null) {
// Proxies like Caddy might only send X-Forwarded-Proto if the host matches
// In that case,
const display_protocol = protocol orelse @as(string, "http");
var display_host = host orelse
(if (protocol != null) this.header("Host") else null) orelse
@as(string, this.origin.host);
var display_port = if (this.origin.port.len > 0) this.origin.port else @as(string, "3000");
if (strings.indexOfChar(display_host, ':')) |colon| {
display_port = display_host[colon + 1 .. display_host.len];
display_host = display_host[0..colon];
} else if (this.bundler.options.origin.port_was_automatically_set and protocol != null) {
if (strings.eqlComptime(display_protocol, "https")) {
display_port = "443";
} else {
display_port = "80";
}
}
this.origin = ZigURL.parse(std.fmt.allocPrint(this.allocator, "{s}://{s}:{s}/", .{ display_protocol, display_host, display_port }) catch unreachable);
}
}
pub fn getFullURL(this: *RequestContext) [:0]const u8 {
if (this.full_url.len == 0) {
if (this.bundler.options.origin.isAbsolute()) {
this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.bundler.options.origin.origin, this.request.path }) catch unreachable;
if (this.origin.isAbsolute()) {
this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.origin.origin, this.request.path }) catch unreachable;
} else {
this.full_url = this.allocator.dupeZ(u8, this.request.path) catch unreachable;
}
@@ -120,7 +203,11 @@ pub const RequestContext = struct {
try this.flushHeaders();
}
pub fn header(ctx: *RequestContext, comptime name: anytype) ?Header {
pub fn header(ctx: *RequestContext, comptime name: anytype) ?[]const u8 {
return (ctx.headerEntry(name) orelse return null).value;
}
pub fn headerEntry(ctx: *RequestContext, comptime name: anytype) ?Header {
for (ctx.request.headers) |head| {
if (strings.eqlCaseInsensitiveASCII(head.name, name, true)) {
return head;
@@ -130,6 +217,18 @@ pub const RequestContext = struct {
return null;
}
pub fn headerEntryFirst(ctx: *RequestContext, comptime name: []const string) ?Header {
for (ctx.request.headers) |head| {
inline for (name) |match| {
if (strings.eqlCaseInsensitiveASCII(head.name, match, true)) {
return head;
}
}
}
return null;
}
pub fn renderFallback(
this: *RequestContext,
allocator: std.mem.Allocator,
@@ -175,7 +274,7 @@ pub const RequestContext = struct {
bundler_parse_options,
@as(?*bundler.FallbackEntryPoint, &fallback_entry_point),
)) |*result| {
try bundler_.linker.link(fallback_entry_point.source.path, result, .absolute_url, false);
try bundler_.linker.link(fallback_entry_point.source.path, result, this.origin, .absolute_url, false);
var buffer_writer = try js_printer.BufferWriter.init(default_allocator);
var writer = js_printer.BufferPrinter.init(buffer_writer);
_ = try bundler_.print(
@@ -535,6 +634,7 @@ pub const RequestContext = struct {
.method = Method.which(req.method) orelse return error.InvalidMethod,
.watcher = watcher_,
.timer = timer,
.origin = bundler_.options.origin,
};
return ctx;
@@ -542,7 +642,7 @@ pub const RequestContext = struct {
pub inline fn isBrowserNavigation(req: *RequestContext) bool {
if (req.header("Sec-Fetch-Mode")) |mode| {
return strings.eqlComptime(mode.value, "navigate");
return strings.eqlComptime(mode, "navigate");
}
return false;
@@ -621,7 +721,7 @@ pub const RequestContext = struct {
ctx.appendHeader("Cache-Control", "immutable, max-age=99999");
if (ctx.header("If-None-Match")) |etag_header| {
if (std.mem.eql(u8, node_modules_bundle.bundle.etag, etag_header.value)) {
if (strings.eqlLong(node_modules_bundle.bundle.etag, etag_header, true)) {
try ctx.sendNotModified();
return;
}
@@ -680,6 +780,7 @@ pub const RequestContext = struct {
printer: js_printer.BufferPrinter,
timer: std.time.Timer,
count: usize = 0,
origin: ZigURL,
pub const WatchBuildResult = struct {
value: Value,
id: u32,
@@ -779,6 +880,7 @@ pub const RequestContext = struct {
this.bundler.linker.link(
Fs.Path.init(file_path_str),
&parse_result,
this.origin,
.absolute_url,
false,
) catch return WatchBuildResult{
@@ -865,6 +967,7 @@ pub const RequestContext = struct {
this.allocator,
&log,
&this.bundler.linker,
this.origin,
);
} else {
break :brk CSSBundler.bundle(
@@ -878,6 +981,7 @@ pub const RequestContext = struct {
this.allocator,
&log,
&this.bundler.linker,
this.origin,
);
}
} catch {
@@ -1275,9 +1379,10 @@ pub const RequestContext = struct {
var handler: *JavaScriptHandler = try channel.readItem();
JavaScript.VirtualMachine.vm.tick();
JavaScript.VirtualMachine.vm.preflush();
const original_origin = vm.origin;
vm.origin = handler.ctx.origin;
defer vm.origin = original_origin;
JavaScript.EventListenerMixin.emitFetchEvent(
vm,
&handler.ctx,
@@ -1388,6 +1493,7 @@ pub const RequestContext = struct {
clone.message_buffer = try MutableString.init(server.allocator, 0);
clone.ctx.conn = &clone.conn;
clone.ctx.log = logger.Log.init(server.allocator);
clone.ctx.origin = ZigURL.parse(server.allocator.dupe(u8, ctx.origin.href) catch unreachable);
var printer_writer = try js_printer.BufferWriter.init(server.allocator);
clone.builder = WatchBuilder{
@@ -1396,6 +1502,7 @@ pub const RequestContext = struct {
.printer = js_printer.BufferPrinter.init(printer_writer),
.timer = ctx.timer,
.watcher = ctx.watcher,
.origin = clone.ctx.origin,
};
clone.websocket = Websocket.Websocket.create(&clone.conn, SOCKET_FLAGS);
@@ -1763,14 +1870,14 @@ pub const RequestContext = struct {
var request: *RequestContext = &self.ctx;
const upgrade_header = request.header("Upgrade") orelse return error.BadRequest;
if (!strings.eqlComptime(upgrade_header.value, "websocket")) {
if (!strings.eqlComptime(upgrade_header, "websocket")) {
return error.BadRequest; // Can only upgrade to websocket
}
// Some proxies/load balancers will mess with the connection header
// and browsers also send multiple values here
const connection_header = request.header("Connection") orelse return error.BadRequest;
var it = std.mem.split(u8, connection_header.value, ",");
var it = std.mem.split(u8, connection_header, ",");
while (it.next()) |part| {
const conn = std.mem.trim(u8, part, " ");
if (strings.eqlCaseInsensitiveASCII(conn, "upgrade", true)) {
@@ -1788,8 +1895,8 @@ pub const RequestContext = struct {
Output.prettyErrorln("HMR WebSocket error: missing Sec-WebSocket-Version header", .{});
return error.BadRequest;
};
return std.fmt.parseInt(u8, v.value, 10) catch {
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Version is invalid {s}", .{v.value});
return std.fmt.parseInt(u8, v, 10) catch {
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Version is invalid {s}", .{v});
return error.BadRequest;
};
}
@@ -1798,7 +1905,7 @@ pub const RequestContext = struct {
self: *WebsocketHandler,
) ![]const u8 {
var request: *RequestContext = &self.ctx;
const key = (request.header("Sec-WebSocket-Key") orelse return error.BadRequest).value;
const key = (request.header("Sec-WebSocket-Key") orelse return error.BadRequest);
if (key.len < 8) {
Output.prettyErrorln("HMR WebSocket error: Sec-WebSocket-Key is less than 8 characters long: {s}", .{key});
return error.BadRequest;
@@ -1822,7 +1929,7 @@ pub const RequestContext = struct {
this.appendHeader("ETag", etag_content_slice);
if (this.header("If-None-Match")) |etag_header| {
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
if (strings.eqlLong(etag_content_slice, etag_header, true)) {
try this.sendNotModified();
return true;
}
@@ -1951,7 +2058,7 @@ pub const RequestContext = struct {
chunky.rctx.appendHeader("ETag", etag_content_slice);
if (chunky.rctx.header("If-None-Match")) |etag_header| {
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
if (std.mem.eql(u8, etag_content_slice, etag_header)) {
try chunky.rctx.sendNotModified();
return;
}
@@ -2012,6 +2119,7 @@ pub const RequestContext = struct {
Watcher,
ctx.watcher,
client_entry_point_,
ctx.origin,
) catch |err| {
ctx.sendInternalError(err) catch {};
return;
@@ -2052,7 +2160,7 @@ pub const RequestContext = struct {
ctx.appendHeader("ETag", etag_content_slice);
if (ctx.header("If-None-Match")) |etag_header| {
if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
if (std.mem.eql(u8, etag_content_slice, etag_header)) {
try ctx.sendNotModified();
return;
}
@@ -2120,7 +2228,7 @@ pub const RequestContext = struct {
ctx.appendHeader("ETag", complete_weak_etag);
if (ctx.header("If-None-Match")) |etag_header| {
if (strings.eql(complete_weak_etag, etag_header.value)) {
if (strings.eql(complete_weak_etag, etag_header)) {
try ctx.sendNotModified();
return;
}
@@ -2227,7 +2335,7 @@ pub const RequestContext = struct {
if (strings.eqlComptime(path, "_api.hmr")) {
if (ctx.header("Upgrade")) |upgrade| {
if (strings.eqlCaseInsensitiveASCII(upgrade.value, "websocket", true)) {
if (strings.eqlCaseInsensitiveASCII(upgrade, "websocket", true)) {
try ctx.handleWebsocket(server);
return;
}
@@ -2296,8 +2404,8 @@ pub const RequestContext = struct {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Dest
pub fn isScriptOrStyleRequest(ctx: *RequestContext) bool {
const header_ = ctx.header("Sec-Fetch-Dest") orelse return false;
return strings.eqlComptime(header_.value, "script") or
strings.eqlComptime(header_.value, "style");
return strings.eqlComptime(header_, "script") or
strings.eqlComptime(header_, "style");
}
fn handleSrcURL(ctx: *RequestContext, _: *Server) !void {
@@ -2944,6 +3052,7 @@ pub const Server = struct {
const is_navigation_request = req_ctx_.isBrowserNavigation();
defer if (is_navigation_request) Analytics.enqueue(Analytics.EventName.http_build);
req_ctx.parseOrigin();
if (req_ctx.url.needs_redirect) {
req_ctx.handleRedirect(req_ctx.url.path) catch |err| {
@@ -3028,7 +3137,7 @@ pub const Server = struct {
if (comptime FeatureFlags.keep_alive) {
if (req_ctx.header("Connection")) |connection| {
req_ctx.keep_alive = strings.eqlInsensitive(connection.value, "keep-alive");
req_ctx.keep_alive = strings.eqlInsensitive(connection, "keep-alive");
}
conn.client.setKeepAlive(req_ctx.keep_alive) catch {};

View File

@@ -390,11 +390,12 @@ pub fn getScriptSrcString(
&entry_point_tempbuf,
Fs.PathName.init(file_path),
),
VirtualMachine.vm.origin,
ScriptSrcStream.Writer,
writer,
);
} else {
JavaScript.Bun.getPublicPath(file_path, ScriptSrcStream.Writer, writer);
JavaScript.Bun.getPublicPath(file_path, VirtualMachine.vm.origin, ScriptSrcStream.Writer, writer);
}
}

View File

@@ -75,7 +75,7 @@ const ErrorableZigString = @import("javascript_core").ErrorableZigString;
const ZigGlobalObject = @import("javascript_core").ZigGlobalObject;
const VM = @import("javascript_core").VM;
const Config = @import("./config.zig");
const URL = @import("../../query_string_map.zig").URL;
pub const GlobalClasses = [_]type{
Request.Class,
Response.Class,
@@ -105,7 +105,7 @@ pub const Bun = struct {
pub fn onImportCSS(
resolve_result: *const Resolver.Result,
import_record: *ImportRecord,
_: string,
origin: URL,
) void {
if (!css_imports_buf_loaded) {
css_imports_buf = std.ArrayList(u8).initCapacity(
@@ -121,7 +121,7 @@ pub const Bun = struct {
.offset = @truncate(u32, offset),
.length = 0,
};
getPublicPath(resolve_result.path_pair.primary.text, @TypeOf(writer), writer);
getPublicPath(resolve_result.path_pair.primary.text, origin, @TypeOf(writer), writer);
const length = css_imports_buf.items.len - offset;
css_imports_list[css_imports_list_tail].length = @truncate(u32, length);
css_imports_list_tail += 1;
@@ -198,7 +198,7 @@ pub const Bun = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
return ZigString.init(VirtualMachine.vm.bundler.options.origin.origin).toValue(VirtualMachine.vm.global).asRef();
return ZigString.init(VirtualMachine.vm.origin.origin).toValue(VirtualMachine.vm.global).asRef();
}
pub fn enableANSIColors(
@@ -509,10 +509,10 @@ pub const Bun = struct {
return result;
}
pub fn getPublicPath(to: string, comptime Writer: type, writer: Writer) void {
pub fn getPublicPath(to: string, origin: URL, comptime Writer: type, writer: Writer) void {
const relative_path = VirtualMachine.vm.bundler.fs.relativeTo(to);
if (VirtualMachine.vm.bundler.options.origin.isAbsolute()) {
VirtualMachine.vm.bundler.options.origin.joinWrite(
if (origin.isAbsolute()) {
origin.joinWrite(
Writer,
writer,
VirtualMachine.vm.bundler.options.routes.asset_prefix_path,
@@ -558,7 +558,7 @@ pub const Bun = struct {
var stream = std.io.fixedBufferStream(&public_path_temp_str);
var writer = stream.writer();
getPublicPath(to, @TypeOf(&writer), &writer);
getPublicPath(to, VirtualMachine.vm.origin, @TypeOf(&writer), &writer);
return ZigString.init(stream.buffer[0..stream.pos]).toValueGC(VirtualMachine.vm.global).asRef();
}
@@ -820,6 +820,7 @@ pub const VirtualMachine = struct {
blobs: *Blob.Group = undefined,
flush_list: std.ArrayList(string),
entry_point: ServerEntryPoint = undefined,
origin: URL = URL{},
arena: *std.heap.ArenaAllocator = undefined,
has_loaded: bool = false,
@@ -941,6 +942,7 @@ pub const VirtualMachine = struct {
.log = log,
.flush_list = std.ArrayList(string).init(allocator),
.blobs = try Blob.Group.init(allocator),
.origin = bundler.options.origin,
.macros = MacroMap.init(allocator),
.macro_entry_points = @TypeOf(VirtualMachine.vm.macro_entry_points).init(allocator),
@@ -1078,6 +1080,7 @@ pub const VirtualMachine = struct {
try bundler.linker.link(
file_path,
&parse_result,
vm.origin,
.absolute_path,
false,
);
@@ -1205,6 +1208,7 @@ pub const VirtualMachine = struct {
try vm.bundler.linker.link(
path,
&parse_result,
vm.origin,
.absolute_path,
false,
);
@@ -1384,19 +1388,33 @@ pub const VirtualMachine = struct {
pub fn normalizeSpecifier(slice_: string) string {
var slice = slice_;
if (slice.len == 0) return slice;
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.origin.host)) {
slice = slice[VirtualMachine.vm.bundler.options.origin.host.len..];
var was_http = false;
if (strings.hasPrefix(slice, "https://")) {
slice = slice["https://".len..];
was_http = true;
}
if (VirtualMachine.vm.bundler.options.origin.path.len > 1) {
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.origin.path)) {
slice = slice[VirtualMachine.vm.bundler.options.origin.path.len..];
if (strings.hasPrefix(slice, "http://")) {
slice = slice["http://".len..];
was_http = true;
}
if (strings.hasPrefix(slice, VirtualMachine.vm.origin.host)) {
slice = slice[VirtualMachine.vm.origin.host.len..];
} else if (was_http) {
if (strings.indexOfChar(slice, '/')) |i| {
slice = slice[i..];
}
}
if (VirtualMachine.vm.origin.path.len > 1) {
if (strings.hasPrefix(slice, VirtualMachine.vm.origin.path)) {
slice = slice[VirtualMachine.vm.origin.path.len..];
}
}
if (VirtualMachine.vm.bundler.options.routes.asset_prefix_path.len > 0) {
if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.routes.asset_prefix_path)) {
if (strings.hasPrefix(slice, VirtualMachine.vm.bundler.options.routes.asset_prefix_path)) {
slice = slice[VirtualMachine.vm.bundler.options.routes.asset_prefix_path.len..];
}
}
@@ -1742,7 +1760,7 @@ pub const VirtualMachine = struct {
),
frame.sourceURLFormatter(
vm.bundler.fs.top_level_dir,
&vm.bundler.options.origin,
&vm.origin,
allow_ansi_colors,
),
},

View File

@@ -1663,7 +1663,7 @@ pub const Request = struct {
_: js.ExceptionRef,
) js.JSValueRef {
if (this.request_context.header("Referrer")) |referrer| {
return ZigString.init(referrer.value).toValueGC(VirtualMachine.vm.global).asRef();
return ZigString.init(referrer).toValueGC(VirtualMachine.vm.global).asRef();
} else {
return ZigString.init("").toValueGC(VirtualMachine.vm.global).asRef();
}

View File

@@ -35,10 +35,10 @@ const Bundler = _bundler.Bundler;
const ResolveQueue = _bundler.ResolveQueue;
const ResolverType = Resolver.Resolver;
const Runtime = @import("./runtime.zig").Runtime;
const URL = @import("query_string_map.zig").URL;
pub const CSSResolveError = error{ResolveError};
pub const OnImportCallback = fn (resolve_result: *const Resolver.Result, import_record: *ImportRecord, source_dir: string) void;
pub const OnImportCallback = fn (resolve_result: *const Resolver.Result, import_record: *ImportRecord, origin: URL) void;
pub const Linker = struct {
const HashedFileNameMap = std.AutoHashMap(u64, string);
@@ -117,6 +117,7 @@ pub const Linker = struct {
url: string,
range: logger.Range,
kind: ImportKind,
origin: URL,
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
comptime resolve_only: bool,
) !string {
@@ -133,7 +134,7 @@ pub const Linker = struct {
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
return import_record.path.text;
},
.at_conditional => {
@@ -145,7 +146,7 @@ pub const Linker = struct {
var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind };
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
return import_record.path.text;
},
.url => {
@@ -157,7 +158,7 @@ pub const Linker = struct {
var import_record = ImportRecord{ .range = range, .path = resolve_result.path_pair.primary, .kind = kind };
const loader = this.options.loaders.get(resolve_result.path_pair.primary.name.ext) orelse .file;
this.processImportRecord(loader, dir, &resolve_result, &import_record, import_path_format) catch unreachable;
this.processImportRecord(loader, dir, &resolve_result, &import_record, origin, import_path_format) catch unreachable;
return import_record.path.text;
},
else => unreachable,
@@ -165,13 +166,10 @@ pub const Linker = struct {
unreachable;
}
pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker) string {
pub inline fn nodeModuleBundleImportPath(this: *const ThisLinker, origin: URL) string {
if (this.options.platform.isBun()) return "/node_modules.server.bun";
return if (this.options.node_modules_bundle_url.len > 0)
this.options.node_modules_bundle_url
else
this.options.node_modules_bundle.?.bundle.import_from_name;
return std.fmt.allocPrint(this.allocator, "{s}://{}{s}", .{ origin.displayProtocol(), origin.displayHost(), this.options.node_modules_bundle.?.bundle.import_from_name }) catch unreachable;
}
// pub const Scratch = struct {
@@ -193,6 +191,7 @@ pub const Linker = struct {
linker: *ThisLinker,
file_path: Fs.Path,
result: *_bundler.ParseResult,
origin: URL,
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
comptime ignore_runtime: bool,
) !void {
@@ -201,6 +200,7 @@ pub const Linker = struct {
var needs_bundle = false;
var had_resolve_errors = false;
var needs_require = false;
var node_module_bundle_import_path: ?string = null;
// Step 1. Resolve imports & requires
switch (result.loader) {
@@ -213,15 +213,17 @@ pub const Linker = struct {
if (strings.eqlComptime(import_record.path.text, Runtime.Imports.Name)) {
// runtime is included in the bundle, so we don't need to dynamically import it
if (linker.options.node_modules_bundle != null) {
import_record.path.text = linker.nodeModuleBundleImportPath();
node_module_bundle_import_path = node_module_bundle_import_path orelse
linker.nodeModuleBundleImportPath(origin);
import_record.path.text = node_module_bundle_import_path.?;
result.ast.runtime_import_record_id = record_index;
} else {
import_record.path = try linker.generateImportPath(
source_dir,
linker.runtime_source_path,
Runtime.version(),
false,
"bun",
origin,
import_path_format,
);
result.ast.runtime_import_record_id = record_index;
@@ -287,7 +289,9 @@ pub const Linker = struct {
}
import_record.is_bundled = true;
import_record.path.text = linker.nodeModuleBundleImportPath();
node_module_bundle_import_path = node_module_bundle_import_path orelse
linker.nodeModuleBundleImportPath(origin);
import_record.path.text = node_module_bundle_import_path.?;
import_record.module_id = found_module.id;
needs_bundle = true;
continue;
@@ -303,6 +307,7 @@ pub const Linker = struct {
source_dir,
resolved_import,
import_record,
origin,
import_path_format,
) catch continue;
@@ -407,14 +412,14 @@ pub const Linker = struct {
import_records[import_records.len - 1] = ImportRecord{
.kind = .stmt,
.path = if (linker.options.node_modules_bundle != null)
Fs.Path.init(linker.nodeModuleBundleImportPath())
Fs.Path.init(node_module_bundle_import_path orelse linker.nodeModuleBundleImportPath(origin))
else
try linker.generateImportPath(
source_dir,
linker.runtime_source_path,
Runtime.version(),
false,
"bun",
origin,
import_path_format,
),
.range = logger.Range{ .loc = logger.Loc{ .start = 0 }, .len = 0 },
@@ -460,9 +465,9 @@ pub const Linker = struct {
linker: *ThisLinker,
source_dir: string,
source_path: string,
_: ?string,
use_hashed_name: bool,
namespace: string,
origin: URL,
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
) !Fs.Path {
switch (import_path_format) {
@@ -532,7 +537,7 @@ pub const Linker = struct {
// assumption: already starts with "node:"
"{s}/{s}",
.{
linker.options.origin.origin,
origin,
source_path,
},
));
@@ -559,7 +564,7 @@ pub const Linker = struct {
basename = try linker.getHashedFilename(basepath, null);
}
return Fs.Path.init(try linker.options.origin.joinAlloc(
return Fs.Path.init(try origin.joinAlloc(
linker.allocator,
linker.options.routes.asset_prefix_path,
dirname,
@@ -580,6 +585,7 @@ pub const Linker = struct {
source_dir: string,
resolve_result: *const Resolver.Result,
import_record: *ImportRecord,
origin: URL,
comptime import_path_format: Options.BundleOptions.ImportPathFormat,
) !void {
linker.import_counter += 1;
@@ -594,16 +600,16 @@ pub const Linker = struct {
import_record.path = try linker.generateImportPath(
source_dir,
if (path.is_symlink and import_path_format == .absolute_url and linker.options.platform.isNotBun()) path.pretty else path.text,
if (resolve_result.package_json) |package_json| package_json.version else "",
Bundler.isCacheEnabled and loader == .file,
path.namespace,
origin,
import_path_format,
);
switch (loader) {
.css => {
if (linker.onImportCSS) |callback| {
callback(resolve_result, import_record, source_dir);
callback(resolve_result, import_record, origin);
}
// This saves us a less reliable string check
import_record.print_mode = .css;

View File

@@ -15,7 +15,11 @@ const C = _global.C;
// This is close to WHATWG URL, but we don't want the validation errors
pub const URL = struct {
hash: string = "",
/// hostname, but with a port
/// `localhost:3000`
host: string = "",
/// hostname does not have a port
/// `localhost`
hostname: string = "",
href: string = "",
origin: string = "",
@@ -86,6 +90,36 @@ pub const URL = struct {
return "localhost";
}
pub const HostFormatter = struct {
host: string,
port: string,
is_https: bool = false,
pub fn format(formatter: HostFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (strings.indexOfChar(formatter.host, ':') != null) {
try writer.writeAll(formatter.host);
return;
}
try writer.writeAll(formatter.host);
const is_port_optional = (formatter.is_https and (formatter.port.len == 0 or strings.eqlComptime(formatter.port, "443"))) or
(!formatter.is_https and (formatter.port.len == 0 or strings.eqlComptime(formatter.port, "80")));
if (!is_port_optional) {
try writer.writeAll(":");
try writer.writeAll(formatter.port);
return;
}
}
};
pub fn displayHost(this: *const URL) HostFormatter {
return HostFormatter{
.host = if (this.host.len > 0) this.host else this.displayHostname(),
.port = this.port,
.is_https = this.isHTTPS(),
};
}
pub fn hasHTTPLikeProtocol(this: *const URL) bool {
return strings.eqlComptime(this.protocol, "http") or strings.eqlComptime(this.protocol, "https");
}