authorgravatar for truemedian@gmail.comNameless <truemedian@gmail.com> 2023-12-18 14:08:38-06:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-01-13 18:50:29-08:00
log832f6d8f7f7f7b10b86b109a8b26bb5eacc8d13e
tree7883621eb36c839ddc07706a77d92ce045ac2406
parentc5d359e4cdcc6090f0ff1803adb929247027e7cb

std.Uri: fix implementation of resolve with trailing slashes


1 files changed, 93 insertions(+), 35 deletions(-)

lib/std/Uri.zig+93-35
...@@ -358,53 +358,111 @@ pub fn parse(text: []const u8) ParseError!Uri {...@@ -358,53 +358,111 @@ pub fn parse(text: []const u8) ParseError!Uri {
358 return uri;358 return uri;
359}359}
360360
361/// Implementation of RFC 3986, Section 5.2.4. Removes dot segments from a URI path.
362///
363/// `std.fs.path.resolvePosix` is not sufficient here because it may return relative paths and does not preserve trailing slashes.
364fn removeDotSegments(allocator: std.mem.Allocator, paths: []const []const u8) std.mem.Allocator.Error![]const u8 {
365 var result = std.ArrayList(u8).init(allocator);
366 defer result.deinit();
367
368 for (paths) |p| {
369 var it = std.mem.tokenizeScalar(u8, p, '/');
370 while (it.next()) |component| {
371 if (std.mem.eql(u8, component, ".")) {
372 continue;
373 } else if (std.mem.eql(u8, component, "..")) {
374 if (result.items.len == 0)
375 continue;
376
377 while (true) {
378 const ends_with_slash = result.items[result.items.len - 1] == '/';
379 result.items.len -= 1;
380 if (ends_with_slash or result.items.len == 0) break;
381 }
382 } else {
383 try result.ensureUnusedCapacity(1 + component.len);
384 result.appendAssumeCapacity('/');
385 result.appendSliceAssumeCapacity(component);
386 }
387 }
388 }
389
390 // ensure a trailing slash is kept
391 const last_path = paths[paths.len - 1];
392 if (last_path.len > 0 and last_path[last_path.len - 1] == '/') {
393 try result.append('/');
394 }
395
396 return result.toOwnedSlice();
397}
398
361/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.399/// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.
362/// arena owns any memory allocated by this function.400///
363pub fn resolve(Base: Uri, R: Uri, strict: bool, arena: std.mem.Allocator) !Uri {401/// Assumes `arena` owns all memory in `base` and `ref`. `arena` will own all memory in the returned URI.
364 var T: Uri = undefined;402pub fn resolve(base: Uri, ref: Uri, strict: bool, arena: std.mem.Allocator) std.mem.Allocator.Error!Uri {
365403 var target: Uri = Uri{
366 if (R.scheme.len > 0 and !((!strict) and (std.mem.eql(u8, R.scheme, Base.scheme)))) {404 .scheme = "",
367 T.scheme = R.scheme;405 .user = null,
368 T.user = R.user;406 .password = null,
369 T.host = R.host;407 .host = null,
370 T.port = R.port;408 .port = null,
371 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });409 .path = "",
372 T.query = R.query;410 .query = null,
411 .fragment = null,
412 };
413
414 if (ref.scheme.len > 0 and (strict or !std.mem.eql(u8, ref.scheme, base.scheme))) {
415 target.scheme = ref.scheme;
416 target.user = ref.user;
417 target.host = ref.host;
418 target.port = ref.port;
419 target.path = try removeDotSegments(arena, &.{ref.path});
420 target.query = ref.query;
373 } else {421 } else {
374 if (R.host) |host| {422 target.scheme = base.scheme;
375 T.user = R.user;423 if (ref.host) |host| {
376 T.host = host;424 target.user = ref.user;
377 T.port = R.port;425 target.host = host;
378 T.path = R.path;426 target.port = ref.port;
379 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });427 target.path = ref.path;
380 T.query = R.query;428 target.path = try removeDotSegments(arena, &.{ref.path});
429 target.query = ref.query;
381 } else {430 } else {
382 if (R.path.len == 0) {431 if (ref.path.len == 0) {
383 T.path = Base.path;432 target.path = base.path;
384 if (R.query) |query| {433 target.query = ref.query orelse base.query;
385 T.query = query;
386 } else {
387 T.query = Base.query;
388 }
389 } else {434 } else {
390 if (R.path[0] == '/') {435 if (ref.path[0] == '/') {
391 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path });436 target.path = try removeDotSegments(arena, &.{ref.path});
392 } else {437 } else {
393 T.path = try std.fs.path.resolvePosix(arena, &.{ "/", Base.path, R.path });438 target.path = try removeDotSegments(arena, &.{ std.fs.path.dirnamePosix(base.path) orelse "", ref.path });
394 }439 }
395 T.query = R.query;440 target.query = ref.query;
396 }441 }
397442
398 T.user = Base.user;443 target.user = base.user;
399 T.host = Base.host;444 target.host = base.host;
400 T.port = Base.port;445 target.port = base.port;
401 }446 }
402 T.scheme = Base.scheme;
403 }447 }
404448
405 T.fragment = R.fragment;449 target.fragment = ref.fragment;
450
451 return target;
452}
453
454test resolve {
455 const base = try parse("http://a/b/c/d;p?q");
456
457 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
458 defer arena.deinit();
406459
407 return T;460 try std.testing.expectEqualDeep(try parse("http://a/b/c/blog/"), try base.resolve(try parseWithoutScheme("blog/"), true, arena.allocator()));
461 try std.testing.expectEqualDeep(try parse("http://a/b/c/blog/?k"), try base.resolve(try parseWithoutScheme("blog/?k"), true, arena.allocator()));
462 try std.testing.expectEqualDeep(try parse("http://a/b/blog/"), try base.resolve(try parseWithoutScheme("../blog/"), true, arena.allocator()));
463 try std.testing.expectEqualDeep(try parse("http://a/b/blog"), try base.resolve(try parseWithoutScheme("../blog"), true, arena.allocator()));
464 try std.testing.expectEqualDeep(try parse("http://e"), try base.resolve(try parseWithoutScheme("//e"), true, arena.allocator()));
465 try std.testing.expectEqualDeep(try parse("https://a:1/"), try base.resolve(try parse("https://a:1/"), true, arena.allocator()));
408}466}
409467
410const SliceReader = struct {468const SliceReader = struct {