| ... | @@ -588,8 +588,9 @@ pub const pipeToFileSystem = extract; | ... | @@ -588,8 +588,9 @@ pub const pipeToFileSystem = extract; |
| 588 | /// Ingests tar file from `reader`, populating file contents within `dir`. If | 588 | /// Ingests tar file from `reader`, populating file contents within `dir`. If |
| 589 | /// any file would be extracted outside of `dir`, an error is return instead. | 589 | /// any file would be extracted outside of `dir`, an error is return instead. |
| 590 | pub fn extract(io: Io, dir: Io.Dir, reader: *Io.Reader, options: ExtractOptions) !void { | 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; | 591 | var file_name_buffer: [Io.Dir.max_path_bytes]u8 = undefined; |
| 592 | var link_name_buffer: [std.fs.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 | var file_contents_buffer: [1024]u8 = undefined; | 594 | var file_contents_buffer: [1024]u8 = undefined; |
| 594 | var it: Iterator = .init(reader, .{ | 595 | var it: Iterator = .init(reader, .{ |
| 595 | .file_name_buffer = &file_name_buffer, | 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,14 +599,15 @@ pub fn extract(io: Io, dir: Io.Dir, reader: *Io.Reader, options: ExtractOptions) |
| 598 | }); | 599 | }); |
| 599 | | 600 | |
| 600 | while (try it.next()) |file| { | 601 | while (try it.next()) |file| { |
| 601 | const file_name = stripComponents(file.name, options.strip_components); | 602 | const n = sanitizePath(&sanitize_buffer, file.name, options.strip_components) catch 0; |
| 602 | if (file_name.len == 0 and file.kind != .directory) { | 603 | if (n == 0 and file.kind != .directory) { |
| 603 | const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix; | 604 | const d = options.diagnostics orelse return error.TarComponentsOutsideStrippedPrefix; |
| 604 | try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{ | 605 | try d.errors.append(d.allocator, .{ .components_outside_stripped_prefix = .{ |
| 605 | .file_name = try d.allocator.dupe(u8, file.name), | 606 | .file_name = try d.allocator.dupe(u8, file.name), |
| 606 | } }); | 607 | } }); |
| 607 | continue; | 608 | continue; |
| 608 | } | 609 | } |
| | 610 | const file_name = sanitize_buffer[0..n]; |
| 609 | if (options.diagnostics) |d| { | 611 | if (options.diagnostics) |d| { |
| 610 | try d.findRoot(file.kind, file_name); | 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,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 | var i: usize = 0; | 678 | var i: usize = 0; |
| 676 | var c = count; | 679 | var c = strip_components; |
| 677 | while (c > 0) : (c -= 1) { | 680 | var it = std.mem.tokenizeScalar(u8, path, '/'); |
| 678 | if (std.mem.findScalarPos(u8, path, i, '/')) |pos| { | 681 | while (it.next()) |component| { |
| 679 | i = pos + 1; | 682 | if (std.mem.eql(u8, component, ".")) continue; |
| 680 | } else { | 683 | if (std.mem.eql(u8, component, "..")) { |
| 681 | i = path.len; | 684 | if (i == 0) return error.Invalid; |
| 682 | break; | 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 { | 718 | test sanitizePath { |
| 689 | const expectEqualStrings = testing.expectEqualStrings; | 719 | try testSanitizePath("a/b/c", "a/b/c", 0); |
| 690 | try expectEqualStrings("a/b/c", stripComponents("a/b/c", 0)); | 720 | try testSanitizePath("a/b/c", "a/x/y/../../b/c", 0); |
| 691 | try expectEqualStrings("b/c", stripComponents("a/b/c", 1)); | 721 | try testSanitizePath("b/c", "a/b/c", 1); |
| 692 | try expectEqualStrings("c", stripComponents("a/b/c", 2)); | 722 | try testSanitizePath("c", "a/b/c", 2); |
| 693 | try expectEqualStrings("", stripComponents("a/b/c", 3)); | 723 | try testSanitizePath("", "a/b/c", 3); |
| 694 | try expectEqualStrings("", stripComponents("a/b/c", 4)); | 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 | test PaxIterator { | 731 | test PaxIterator { |