diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bundler.zig | 15 | ||||
-rw-r--r-- | src/css_scanner.zig | 10 | ||||
-rw-r--r-- | src/http.zig | 151 | ||||
-rw-r--r-- | src/javascript/jsc/api/router.zig | 3 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 48 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 2 | ||||
-rw-r--r-- | src/linker.zig | 46 | ||||
-rw-r--r-- | src/query_string_map.zig | 34 |
8 files changed, 246 insertions, 63 deletions
diff --git a/src/bundler.zig b/src/bundler.zig index b726996a1..068d8cd2b 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -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; diff --git a/src/css_scanner.zig b/src/css_scanner.zig index 10f065058..55b3298d2 100644 --- a/src/css_scanner.zig +++ b/src/css_scanner.zig @@ -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, }; diff --git a/src/http.zig b/src/http.zig index 0e0db02f3..bccfcce3e 100644 --- a/src/http.zig +++ b/src/http.zig @@ -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 {}; diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index 66aca8349..2e2588e5a 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -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); } } diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 844ffea92..043bc7aea 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -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; + var was_http = false; + if (strings.hasPrefix(slice, "https://")) { + slice = slice["https://".len..]; + was_http = true; + } - if (strings.startsWith(slice, VirtualMachine.vm.bundler.options.origin.host)) { - slice = slice[VirtualMachine.vm.bundler.options.origin.host.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.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 (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, ), }, diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 455c36e8a..cb87f2e98 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -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(); } diff --git a/src/linker.zig b/src/linker.zig index c652ec9b8..3255d770e 100644 --- a/src/linker.zig +++ b/src/linker.zig @@ -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; diff --git a/src/query_string_map.zig b/src/query_string_map.zig index 0f43e9671..16a3c83c7 100644 --- a/src/query_string_map.zig +++ b/src/query_string_map.zig @@ -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"); } |