Coverage Report

Created: 2025-03-04 07:22

/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
}