/src/serenity/Userland/Libraries/LibArchive/TarStream.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org> |
3 | | * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #pragma once |
9 | | |
10 | | #include <AK/MaybeOwned.h> |
11 | | #include <AK/Span.h> |
12 | | #include <AK/Stream.h> |
13 | | #include <LibArchive/Tar.h> |
14 | | |
15 | | namespace Archive { |
16 | | |
17 | | class TarInputStream; |
18 | | |
19 | | class TarFileStream : public Stream { |
20 | | public: |
21 | | virtual ErrorOr<Bytes> read_some(Bytes) override; |
22 | | virtual ErrorOr<size_t> write_some(ReadonlyBytes) override; |
23 | | virtual bool is_eof() const override; |
24 | 0 | virtual bool is_open() const override { return true; } |
25 | 0 | virtual void close() override {}; |
26 | | |
27 | | private: |
28 | | TarFileStream(TarInputStream& stream); |
29 | | TarInputStream& m_tar_stream; |
30 | | int m_generation; |
31 | | |
32 | | friend class TarInputStream; |
33 | | }; |
34 | | |
35 | | class TarInputStream { |
36 | | public: |
37 | | static ErrorOr<NonnullOwnPtr<TarInputStream>> construct(NonnullOwnPtr<Stream>); |
38 | | ErrorOr<void> advance(); |
39 | 21.8k | bool finished() const { return m_found_end_of_archive || m_stream->is_eof(); } |
40 | | ErrorOr<bool> valid() const; |
41 | 67.1k | TarFileHeader const& header() const { return m_header; } |
42 | | TarFileStream file_contents(); |
43 | | |
44 | | template<VoidFunction<StringView, StringView> F> |
45 | | ErrorOr<void> for_each_extended_header(F func); |
46 | | |
47 | | private: |
48 | | TarInputStream(NonnullOwnPtr<Stream>); |
49 | | ErrorOr<void> load_next_header(); |
50 | | |
51 | | TarFileHeader m_header; |
52 | | NonnullOwnPtr<Stream> m_stream; |
53 | | unsigned long m_file_offset { 0 }; |
54 | | int m_generation { 0 }; |
55 | | bool m_found_end_of_archive { false }; |
56 | | |
57 | | friend class TarFileStream; |
58 | | }; |
59 | | |
60 | | class TarOutputStream { |
61 | | public: |
62 | | TarOutputStream(MaybeOwned<Stream>); |
63 | | ErrorOr<void> add_file(StringView path, mode_t, ReadonlyBytes); |
64 | | ErrorOr<void> add_link(StringView path, mode_t, StringView); |
65 | | ErrorOr<void> add_directory(StringView path, mode_t); |
66 | | ErrorOr<void> finish(); |
67 | | |
68 | | private: |
69 | | MaybeOwned<Stream> m_stream; |
70 | | bool m_finished { false }; |
71 | | |
72 | | friend class TarFileStream; |
73 | | }; |
74 | | |
75 | | template<VoidFunction<StringView, StringView> F> |
76 | | inline ErrorOr<void> TarInputStream::for_each_extended_header(F func) |
77 | 5.45k | { |
78 | 5.45k | VERIFY(header().content_is_like_extended_header()); |
79 | | |
80 | 5.45k | Archive::TarFileStream file_stream = file_contents(); |
81 | | |
82 | 5.45k | auto header_size = TRY(header().size()); |
83 | 5.45k | ByteBuffer file_contents_buffer = TRY(ByteBuffer::create_zeroed(header_size)); |
84 | 5.45k | TRY(file_stream.read_until_filled(file_contents_buffer)); |
85 | | |
86 | 0 | StringView file_contents { file_contents_buffer }; |
87 | | |
88 | 9.98k | while (!file_contents.is_empty()) { |
89 | | // Split off the length (until the first space). |
90 | 4.95k | Optional<size_t> length_end_index = file_contents.find(' '); |
91 | 4.95k | if (!length_end_index.has_value()) |
92 | 51 | return Error::from_string_literal("Malformed extended header: No length found."); |
93 | 4.90k | Optional<unsigned> length = file_contents.substring_view(0, length_end_index.value()).to_number<unsigned>(); |
94 | 4.90k | if (!length.has_value()) |
95 | 132 | return Error::from_string_literal("Malformed extended header: Could not parse length."); |
96 | | |
97 | 4.77k | if (length_end_index.value() >= length.value()) |
98 | 25 | return Error::from_string_literal("Malformed extended header: Header length too short."); |
99 | | |
100 | 4.74k | unsigned int remaining_length = length.value(); |
101 | | |
102 | 4.74k | remaining_length -= length_end_index.value() + 1; |
103 | 4.74k | file_contents = file_contents.substring_view(length_end_index.value() + 1); |
104 | | |
105 | 4.74k | if (file_contents.length() < remaining_length - 1) |
106 | 66 | return Error::from_string_literal("Malformed extended header: Header length too large."); |
107 | | |
108 | | // Extract the header. |
109 | 4.68k | StringView header = file_contents.substring_view(0, remaining_length - 1); |
110 | 4.68k | file_contents = file_contents.substring_view(remaining_length - 1); |
111 | | |
112 | | // Ensure that the header ends at the expected location. |
113 | 4.68k | if (file_contents.length() < 1 || !file_contents.starts_with('\n')) |
114 | 65 | return Error::from_string_literal("Malformed extended header: Header does not end at expected location."); |
115 | 4.61k | file_contents = file_contents.substring_view(1); |
116 | | |
117 | | // Find the delimiting '='. |
118 | 4.61k | Optional<size_t> header_delimiter_index = header.find('='); |
119 | 4.61k | if (!header_delimiter_index.has_value()) |
120 | 10 | return Error::from_string_literal("Malformed extended header: Header does not have a delimiter."); |
121 | 4.60k | StringView key = header.substring_view(0, header_delimiter_index.value()); |
122 | 4.60k | StringView value = header.substring_view(header_delimiter_index.value() + 1); |
123 | | |
124 | 4.60k | func(key, value); |
125 | 4.60k | } |
126 | | |
127 | 5.02k | return {}; |
128 | 5.37k | } |
129 | | |
130 | | } |