@@ -85,16 +85,6 @@ pub const URL = struct {
85
85
return WebApiURL .init (allocator , self .uri );
86
86
}
87
87
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
-
98
88
/// Properly stitches two URL fragments together.
99
89
///
100
90
/// For URLs with a path, it will replace the last entry with the src.
@@ -106,25 +96,24 @@ pub const URL = struct {
106
96
comptime opts : StitchOpts ,
107
97
) ! StitchReturn (opts ) {
108
98
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 );
117
100
}
118
101
119
102
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
+ };
123
111
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 });
126
115
}
127
- return base ;
116
+ return std . fmt . allocPrint ( allocator , "{s}:{s}" , .{ protocol , path }) ;
128
117
}
129
118
130
119
// Quick hack because domains have to be at least 3 characters.
@@ -191,10 +180,6 @@ pub const URL = struct {
191
180
return out [0.. out_i ];
192
181
}
193
182
194
- fn StitchReturn (comptime opts : StitchOpts ) type {
195
- return if (opts .null_terminated ) [:0 ]const u8 else []const u8 ;
196
- }
197
-
198
183
pub fn concatQueryString (arena : Allocator , url : []const u8 , query_string : []const u8 ) ! []const u8 {
199
184
std .debug .assert (url .len != 0 );
200
185
@@ -221,11 +206,33 @@ pub const URL = struct {
221
206
}
222
207
};
223
208
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 );
227
226
}
228
227
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 {
229
236
if (url .len < 8 ) {
230
237
return false ;
231
238
}
@@ -243,8 +250,6 @@ fn isComleteHTTPUrl(url: []const u8) bool {
243
250
244
251
const testing = @import ("testing.zig" );
245
252
test "URL: isComleteHTTPUrl" {
246
- try testing .expectEqual (true , isComleteHTTPUrl ("://lightpanda.io" ));
247
- try testing .expectEqual (true , isComleteHTTPUrl ("://lightpanda.io/about" ));
248
253
try testing .expectEqual (true , isComleteHTTPUrl ("http://lightpanda.io/about" ));
249
254
try testing .expectEqual (true , isComleteHTTPUrl ("HttP://lightpanda.io/about" ));
250
255
try testing .expectEqual (true , isComleteHTTPUrl ("httpS://lightpanda.io/about" ));
@@ -253,6 +258,8 @@ test "URL: isComleteHTTPUrl" {
253
258
try testing .expectEqual (false , isComleteHTTPUrl ("/lightpanda.io" ));
254
259
try testing .expectEqual (false , isComleteHTTPUrl ("../../about" ));
255
260
try testing .expectEqual (false , isComleteHTTPUrl ("about" ));
261
+ try testing .expectEqual (false , isComleteHTTPUrl ("//lightpanda.io" ));
262
+ try testing .expectEqual (false , isComleteHTTPUrl ("//lightpanda.io/about" ));
256
263
}
257
264
258
265
test "URL: resolve size" {
@@ -280,99 +287,83 @@ test "URL: stitch" {
280
287
expected : []const u8 ,
281
288
};
282
289
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
+ } };
376
367
377
368
for (cases ) | case | {
378
369
const result = try stitch (testing .arena_allocator , case .path , case .base , .{});
0 commit comments