@@ -85,16 +85,6 @@ pub const URL = struct {
8585 return WebApiURL .init (allocator , self .uri );
8686 }
8787
88- const StitchOpts = struct {
89- alloc : AllocWhen = .always ,
90- null_terminated : bool = false ,
91-
92- const AllocWhen = enum {
93- always ,
94- if_needed ,
95- };
96- };
97-
9888 /// Properly stitches two URL fragments together.
9989 ///
10090 /// For URLs with a path, it will replace the last entry with the src.
@@ -106,25 +96,24 @@ pub const URL = struct {
10696 comptime opts : StitchOpts ,
10797 ) ! StitchReturn (opts ) {
10898 if (base .len == 0 or isComleteHTTPUrl (path )) {
109- if (comptime opts .null_terminated ) {
110- return allocator .dupeZ (u8 , path );
111- }
112-
113- if (opts .alloc == .always ) {
114- return allocator .dupe (u8 , path );
115- }
116- return path ;
99+ return simpleStitch (allocator , path , opts );
117100 }
118101
119102 if (path .len == 0 ) {
120- if (comptime opts .null_terminated ) {
121- return allocator .dupeZ (u8 , base );
122- }
103+ return simpleStitch (allocator , base , opts );
104+ }
105+
106+ if (std .mem .startsWith (u8 , path , "//" )) {
107+ // network-path reference
108+ const index = std .mem .indexOfScalar (u8 , base , ':' ) orelse {
109+ return simpleStitch (allocator , path , opts );
110+ };
123111
124- if (opts .alloc == .always ) {
125- return allocator .dupe (u8 , base );
112+ const protocol = base [0.. index ];
113+ if (comptime opts .null_terminated ) {
114+ return std .fmt .allocPrintZ (allocator , "{s}:{s}" , .{ protocol , path });
126115 }
127- return base ;
116+ return std . fmt . allocPrint ( allocator , "{s}:{s}" , .{ protocol , path }) ;
128117 }
129118
130119 // Quick hack because domains have to be at least 3 characters.
@@ -191,10 +180,6 @@ pub const URL = struct {
191180 return out [0.. out_i ];
192181 }
193182
194- fn StitchReturn (comptime opts : StitchOpts ) type {
195- return if (opts .null_terminated ) [:0 ]const u8 else []const u8 ;
196- }
197-
198183 pub fn concatQueryString (arena : Allocator , url : []const u8 , query_string : []const u8 ) ! []const u8 {
199184 std .debug .assert (url .len != 0 );
200185
@@ -221,11 +206,33 @@ pub const URL = struct {
221206 }
222207};
223208
224- fn isComleteHTTPUrl (url : []const u8 ) bool {
225- if (std .mem .startsWith (u8 , url , "://" )) {
226- return true ;
209+ const StitchOpts = struct {
210+ alloc : AllocWhen = .always ,
211+ null_terminated : bool = false ,
212+
213+ const AllocWhen = enum {
214+ always ,
215+ if_needed ,
216+ };
217+ };
218+
219+ fn StitchReturn (comptime opts : StitchOpts ) type {
220+ return if (opts .null_terminated ) [:0 ]const u8 else []const u8 ;
221+ }
222+
223+ fn simpleStitch (allocator : Allocator , url : []const u8 , comptime opts : StitchOpts ) ! StitchReturn (opts ) {
224+ if (comptime opts .null_terminated ) {
225+ return allocator .dupeZ (u8 , url );
227226 }
228227
228+ if (comptime opts .alloc == .always ) {
229+ return allocator .dupe (u8 , url );
230+ }
231+
232+ return url ;
233+ }
234+
235+ fn isComleteHTTPUrl (url : []const u8 ) bool {
229236 if (url .len < 8 ) {
230237 return false ;
231238 }
@@ -243,8 +250,6 @@ fn isComleteHTTPUrl(url: []const u8) bool {
243250
244251const testing = @import ("testing.zig" );
245252test "URL: isComleteHTTPUrl" {
246- try testing .expectEqual (true , isComleteHTTPUrl ("://lightpanda.io" ));
247- try testing .expectEqual (true , isComleteHTTPUrl ("://lightpanda.io/about" ));
248253 try testing .expectEqual (true , isComleteHTTPUrl ("http://lightpanda.io/about" ));
249254 try testing .expectEqual (true , isComleteHTTPUrl ("HttP://lightpanda.io/about" ));
250255 try testing .expectEqual (true , isComleteHTTPUrl ("httpS://lightpanda.io/about" ));
@@ -253,6 +258,8 @@ test "URL: isComleteHTTPUrl" {
253258 try testing .expectEqual (false , isComleteHTTPUrl ("/lightpanda.io" ));
254259 try testing .expectEqual (false , isComleteHTTPUrl ("../../about" ));
255260 try testing .expectEqual (false , isComleteHTTPUrl ("about" ));
261+ try testing .expectEqual (false , isComleteHTTPUrl ("//lightpanda.io" ));
262+ try testing .expectEqual (false , isComleteHTTPUrl ("//lightpanda.io/about" ));
256263}
257264
258265test "URL: resolve size" {
@@ -280,99 +287,83 @@ test "URL: stitch" {
280287 expected : []const u8 ,
281288 };
282289
283- const cases = [_ ]Case {
284- .{
285- .base = "https://lightpanda.io/xyz/abc/123" ,
286- .path = "something.js" ,
287- .expected = "https://lightpanda.io/xyz/abc/something.js" ,
288- },
289- .{
290- .base = "https://lightpanda.io/xyz/abc/123" ,
291- .path = "/something.js" ,
292- .expected = "https://lightpanda.io/something.js" ,
293- },
294- .{
295- .base = "https://lightpanda.io/" ,
296- .path = "something.js" ,
297- .expected = "https://lightpanda.io/something.js" ,
298- },
299- .{
300- .base = "https://lightpanda.io/" ,
301- .path = "/something.js" ,
302- .expected = "https://lightpanda.io/something.js" ,
303- },
304- .{
305- .base = "https://lightpanda.io" ,
306- .path = "something.js" ,
307- .expected = "https://lightpanda.io/something.js" ,
308- },
309- .{
310- .base = "https://lightpanda.io" ,
311- .path = "abc/something.js" ,
312- .expected = "https://lightpanda.io/abc/something.js" ,
313- },
314- .{
315- .base = "https://lightpanda.io/nested" ,
316- .path = "abc/something.js" ,
317- .expected = "https://lightpanda.io/abc/something.js" ,
318- },
319- .{
320- .base = "https://lightpanda.io/nested/" ,
321- .path = "abc/something.js" ,
322- .expected = "https://lightpanda.io/nested/abc/something.js" ,
323- },
324- .{
325- .base = "https://lightpanda.io/nested/" ,
326- .path = "/abc/something.js" ,
327- .expected = "https://lightpanda.io/abc/something.js" ,
328- },
329- .{
330- .base = "https://lightpanda.io/nested/" ,
331- .path = "http://www.github.com/lightpanda-io/" ,
332- .expected = "http://www.github.com/lightpanda-io/" ,
333- },
334- .{
335- .base = "https://lightpanda.io/nested/" ,
336- .path = "" ,
337- .expected = "https://lightpanda.io/nested/" ,
338- },
339- .{
340- .base = "https://lightpanda.io/abc/aaa" ,
341- .path = "./hello/./world" ,
342- .expected = "https://lightpanda.io/abc/hello/world" ,
343- },
344- .{
345- .base = "https://lightpanda.io/abc/aaa/" ,
346- .path = "../hello" ,
347- .expected = "https://lightpanda.io/abc/hello" ,
348- },
349- .{
350- .base = "https://lightpanda.io/abc/aaa" ,
351- .path = "../hello" ,
352- .expected = "https://lightpanda.io/hello" ,
353- },
354- .{
355- .base = "https://lightpanda.io/abc/aaa/" ,
356- .path = "./.././.././hello" ,
357- .expected = "https://lightpanda.io/hello" ,
358- },
359- .{
360- .base = "some/page" ,
361- .path = "hello" ,
362- .expected = "some/hello" ,
363- },
364- .{
365- .base = "some/page/" ,
366- .path = "hello" ,
367- .expected = "some/page/hello" ,
368- },
369-
370- .{
371- .base = "some/page/other" ,
372- .path = ".././hello" ,
373- .expected = "some/hello" ,
374- },
375- };
290+ const cases = [_ ]Case { .{
291+ .base = "https://lightpanda.io/xyz/abc/123" ,
292+ .path = "something.js" ,
293+ .expected = "https://lightpanda.io/xyz/abc/something.js" ,
294+ }, .{
295+ .base = "https://lightpanda.io/xyz/abc/123" ,
296+ .path = "/something.js" ,
297+ .expected = "https://lightpanda.io/something.js" ,
298+ }, .{
299+ .base = "https://lightpanda.io/" ,
300+ .path = "something.js" ,
301+ .expected = "https://lightpanda.io/something.js" ,
302+ }, .{
303+ .base = "https://lightpanda.io/" ,
304+ .path = "/something.js" ,
305+ .expected = "https://lightpanda.io/something.js" ,
306+ }, .{
307+ .base = "https://lightpanda.io" ,
308+ .path = "something.js" ,
309+ .expected = "https://lightpanda.io/something.js" ,
310+ }, .{
311+ .base = "https://lightpanda.io" ,
312+ .path = "abc/something.js" ,
313+ .expected = "https://lightpanda.io/abc/something.js" ,
314+ }, .{
315+ .base = "https://lightpanda.io/nested" ,
316+ .path = "abc/something.js" ,
317+ .expected = "https://lightpanda.io/abc/something.js" ,
318+ }, .{
319+ .base = "https://lightpanda.io/nested/" ,
320+ .path = "abc/something.js" ,
321+ .expected = "https://lightpanda.io/nested/abc/something.js" ,
322+ }, .{
323+ .base = "https://lightpanda.io/nested/" ,
324+ .path = "/abc/something.js" ,
325+ .expected = "https://lightpanda.io/abc/something.js" ,
326+ }, .{
327+ .base = "https://lightpanda.io/nested/" ,
328+ .path = "http://www.github.com/lightpanda-io/" ,
329+ .expected = "http://www.github.com/lightpanda-io/" ,
330+ }, .{
331+ .base = "https://lightpanda.io/nested/" ,
332+ .path = "" ,
333+ .expected = "https://lightpanda.io/nested/" ,
334+ }, .{
335+ .base = "https://lightpanda.io/abc/aaa" ,
336+ .path = "./hello/./world" ,
337+ .expected = "https://lightpanda.io/abc/hello/world" ,
338+ }, .{
339+ .base = "https://lightpanda.io/abc/aaa/" ,
340+ .path = "../hello" ,
341+ .expected = "https://lightpanda.io/abc/hello" ,
342+ }, .{
343+ .base = "https://lightpanda.io/abc/aaa" ,
344+ .path = "../hello" ,
345+ .expected = "https://lightpanda.io/hello" ,
346+ }, .{
347+ .base = "https://lightpanda.io/abc/aaa/" ,
348+ .path = "./.././.././hello" ,
349+ .expected = "https://lightpanda.io/hello" ,
350+ }, .{
351+ .base = "some/page" ,
352+ .path = "hello" ,
353+ .expected = "some/hello" ,
354+ }, .{
355+ .base = "some/page/" ,
356+ .path = "hello" ,
357+ .expected = "some/page/hello" ,
358+ }, .{
359+ .base = "some/page/other" ,
360+ .path = ".././hello" ,
361+ .expected = "some/hello" ,
362+ }, .{
363+ .path = "//static.lightpanda.io/hello.js" ,
364+ .base = "https://lightpanda.io/about/" ,
365+ .expected = "https://static.lightpanda.io/hello.js" ,
366+ } };
376367
377368 for (cases ) | case | {
378369 const result = try stitch (testing .arena_allocator , case .path , case .base , .{});
0 commit comments