| ... | ... | @@ -358,53 +358,111 @@ pub fn parse(text: []const u8) ParseError!Uri { |
| 358 | 358 | return uri; |
| 359 | 359 | } |
| 360 | 360 | |
| 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. |
| 364 | fn 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 | 399 | /// Resolves a URI against a base URI, conforming to RFC 3986, Section 5. |
| 362 | | /// arena owns any memory allocated by this function. |
| 363 | | pub fn resolve(Base: Uri, R: Uri, strict: bool, arena: std.mem.Allocator) !Uri { |
| 364 | | var T: Uri = undefined; |
| 365 | | |
| 366 | | if (R.scheme.len > 0 and !((!strict) and (std.mem.eql(u8, R.scheme, Base.scheme)))) { |
| 367 | | T.scheme = R.scheme; |
| 368 | | T.user = R.user; |
| 369 | | T.host = R.host; |
| 370 | | T.port = R.port; |
| 371 | | T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); |
| 372 | | T.query = R.query; |
| 400 | /// |
| 401 | /// Assumes `arena` owns all memory in `base` and `ref`. `arena` will own all memory in the returned URI. |
| 402 | pub fn resolve(base: Uri, ref: Uri, strict: bool, arena: std.mem.Allocator) std.mem.Allocator.Error!Uri { |
| 403 | var target: Uri = Uri{ |
| 404 | .scheme = "", |
| 405 | .user = null, |
| 406 | .password = null, |
| 407 | .host = null, |
| 408 | .port = null, |
| 409 | .path = "", |
| 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 | 421 | } else { |
| 374 | | if (R.host) |host| { |
| 375 | | T.user = R.user; |
| 376 | | T.host = host; |
| 377 | | T.port = R.port; |
| 378 | | T.path = R.path; |
| 379 | | T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); |
| 380 | | T.query = R.query; |
| 422 | target.scheme = base.scheme; |
| 423 | if (ref.host) |host| { |
| 424 | target.user = ref.user; |
| 425 | target.host = host; |
| 426 | target.port = ref.port; |
| 427 | target.path = ref.path; |
| 428 | target.path = try removeDotSegments(arena, &.{ref.path}); |
| 429 | target.query = ref.query; |
| 381 | 430 | } else { |
| 382 | | if (R.path.len == 0) { |
| 383 | | T.path = Base.path; |
| 384 | | if (R.query) |query| { |
| 385 | | T.query = query; |
| 386 | | } else { |
| 387 | | T.query = Base.query; |
| 388 | | } |
| 431 | if (ref.path.len == 0) { |
| 432 | target.path = base.path; |
| 433 | target.query = ref.query orelse base.query; |
| 389 | 434 | } else { |
| 390 | | if (R.path[0] == '/') { |
| 391 | | T.path = try std.fs.path.resolvePosix(arena, &.{ "/", R.path }); |
| 435 | if (ref.path[0] == '/') { |
| 436 | target.path = try removeDotSegments(arena, &.{ref.path}); |
| 392 | 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 | } |
| 397 | 442 | |
| 398 | | T.user = Base.user; |
| 399 | | T.host = Base.host; |
| 400 | | T.port = Base.port; |
| 443 | target.user = base.user; |
| 444 | target.host = base.host; |
| 445 | target.port = base.port; |
| 401 | 446 | } |
| 402 | | T.scheme = Base.scheme; |
| 403 | 447 | } |
| 404 | 448 | |
| 405 | | T.fragment = R.fragment; |
| 449 | target.fragment = ref.fragment; |
| 450 | |
| 451 | return target; |
| 452 | } |
| 453 | |
| 454 | test 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(); |
| 406 | 459 | |
| 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 | } |
| 409 | 467 | |
| 410 | 468 | const SliceReader = struct { |