| ... | ... | @@ -588,8 +588,9 @@ pub const pipeToFileSystem = extract; |
| 588 | 588 | /// Ingests tar file from `reader`, populating file contents within `dir`. If |
| 589 | 589 | /// any file would be extracted outside of `dir`, an error is return instead. |
| 590 | 590 | pub fn extract(io: Io, dir: Io.Dir, reader: *Io.Reader, options: ExtractOptions) !void { |
| 591 | | var file_name_buffer: [std.fs.max_path_bytes]u8 = undefined; |
| 592 | | var link_name_buffer: [std.fs.max_path_bytes]u8 = undefined; |
| 591 | var file_name_buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 592 | var link_name_buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 593 | var sanitize_buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 593 | 594 | var file_contents_buffer: [1024]u8 = undefined; |
| 594 | 595 | var it: Iterator = .init(reader, .{ |
| 595 | 596 | .file_name_buffer = &file_name_buffer, |
| ... | ... | @@ -598,14 +599,15 @@ pub fn extract(io: Io, dir: Io.Dir, reader: *Io.Reader, options: ExtractOptions) |
| 598 | 599 | }); |
| 599 | 600 | |
| 600 | 601 | while (try it.next()) |file| { |
| 601 | | const file_name = stripComponents(file.name, options.strip_components); |
| 602 | | if (file_name.len == 0 and file.kind != .directory) { |
| 602 | const n = sanitizePath(&sanitize_buffer, file.name, options.strip_components) catch 0; |
| 603 | if (n == 0 and file.kind != .directory) { |
| 603 | 604 | const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix; |
| 604 | 605 | try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{ |
| 605 | 606 | .file_name = try d.allocator.dupe(u8, file.name), |
| 606 | 607 | } }); |
| 607 | 608 | continue; |
| 608 | 609 | } |
| 610 | const file_name = sanitize_buffer[0..n]; |
| 609 | 611 | if (options.diagnostics) |d| { |
| 610 | 612 | try d.findRoot(file.kind, file_name); |
| 611 | 613 | } |
| ... | ... | @@ -671,27 +673,59 @@ fn createDirAndSymlink(io: Io, dir: Io.Dir, link_name: []const u8, file_name: [] |
| 671 | 673 | }; |
| 672 | 674 | } |
| 673 | 675 | |
| 674 | | fn stripComponents(path: []const u8, count: u32) []const u8 { |
| 676 | fn sanitizePath(buffer: []u8, path: []const u8, strip_components: u32) error{Invalid}!usize { |
| 677 | if (path.len == 0 or path[0] == '/') return error.Invalid; |
| 675 | 678 | var i: usize = 0; |
| 676 | | var c = count; |
| 677 | | while (c > 0) : (c -= 1) { |
| 678 | | if (std.mem.findScalarPos(u8, path, i, '/')) |pos| { |
| 679 | | i = pos + 1; |
| 680 | | } else { |
| 681 | | i = path.len; |
| 682 | | break; |
| 679 | var c = strip_components; |
| 680 | var it = std.mem.tokenizeScalar(u8, path, '/'); |
| 681 | while (it.next()) |component| { |
| 682 | if (std.mem.eql(u8, component, ".")) continue; |
| 683 | if (std.mem.eql(u8, component, "..")) { |
| 684 | if (i == 0) return error.Invalid; |
| 685 | while (true) { |
| 686 | const ends_with_slash = buffer[i - 1] == '/'; |
| 687 | i -= 1; |
| 688 | if (ends_with_slash or i == 0) break; |
| 689 | } |
| 690 | continue; |
| 691 | } |
| 692 | if (c > 0) { |
| 693 | c -= 1; |
| 694 | continue; |
| 695 | } |
| 696 | if (i > 0) { |
| 697 | buffer[i] = '/'; |
| 698 | i += 1; |
| 683 | 699 | } |
| 700 | @memcpy(buffer[i..][0..component.len], component); |
| 701 | i += component.len; |
| 684 | 702 | } |
| 685 | | return path[i..]; |
| 703 | if (c > 0) return error.Invalid; |
| 704 | return i; |
| 705 | } |
| 706 | |
| 707 | fn testSanitizePath(expected: []const u8, input: []const u8, strip: u32) !void { |
| 708 | var buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 709 | const result = buffer[0..try sanitizePath(&buffer, input, strip)]; |
| 710 | try testing.expectEqualStrings(expected, result); |
| 711 | } |
| 712 | |
| 713 | fn testSanitizePathError(expected: anyerror, input: []const u8, strip: u32) !void { |
| 714 | var buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 715 | try testing.expectError(expected, sanitizePath(&buffer, input, strip)); |
| 686 | 716 | } |
| 687 | 717 | |
| 688 | | test stripComponents { |
| 689 | | const expectEqualStrings = testing.expectEqualStrings; |
| 690 | | try expectEqualStrings("a/b/c", stripComponents("a/b/c", 0)); |
| 691 | | try expectEqualStrings("b/c", stripComponents("a/b/c", 1)); |
| 692 | | try expectEqualStrings("c", stripComponents("a/b/c", 2)); |
| 693 | | try expectEqualStrings("", stripComponents("a/b/c", 3)); |
| 694 | | try expectEqualStrings("", stripComponents("a/b/c", 4)); |
| 718 | test sanitizePath { |
| 719 | try testSanitizePath("a/b/c", "a/b/c", 0); |
| 720 | try testSanitizePath("a/b/c", "a/x/y/../../b/c", 0); |
| 721 | try testSanitizePath("b/c", "a/b/c", 1); |
| 722 | try testSanitizePath("c", "a/b/c", 2); |
| 723 | try testSanitizePath("", "a/b/c", 3); |
| 724 | try testSanitizePath("", "a/b/c/../../..", 0); |
| 725 | try testSanitizePathError(error.Invalid, "a/b/c", 4); |
| 726 | try testSanitizePathError(error.Invalid, "..", 0); |
| 727 | try testSanitizePathError(error.Invalid, "a/b/../../..", 0); |
| 728 | try testSanitizePathError(error.Invalid, "a/b/../..", 1); |
| 695 | 729 | } |
| 696 | 730 | |
| 697 | 731 | test PaxIterator { |