Libraries: Remove LibArchive

This library isn't used by anything but the Android build which
currently doesn't work. We most likely won't be using a homegrown
implementation for archive formats in the future, regardless.
This commit is contained in:
rmg-x 2024-11-23 09:18:38 -06:00 committed by Andreas Kling
parent bdabc9b70d
commit 03b9e555c0
Notes: github-actions[bot] 2024-11-25 12:38:39 +00:00
14 changed files with 2 additions and 1267 deletions

View file

@ -1,8 +0,0 @@
set(SOURCES
Tar.cpp
TarStream.cpp
Zip.cpp
)
serenity_lib(LibArchive archive)
target_link_libraries(LibArchive PRIVATE LibCompress LibCore LibCrypto)

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Archive {
class Statistics {
public:
Statistics(size_t file_count, size_t directory_count, size_t total_uncompressed_bytes)
: m_file_count(file_count)
, m_directory_count(directory_count)
, m_total_uncompressed_bytes(total_uncompressed_bytes)
{
}
size_t file_count() const { return m_file_count; }
size_t directory_count() const { return m_directory_count; }
size_t member_count() const { return file_count() + directory_count(); }
size_t total_uncompressed_bytes() const { return m_total_uncompressed_bytes; }
private:
size_t m_file_count { 0 };
size_t m_directory_count { 0 };
size_t m_total_uncompressed_bytes { 0 };
};
}

View file

@ -1,71 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Tar.h"
namespace Archive {
unsigned TarFileHeader::expected_checksum() const
{
auto checksum = 0u;
u8 const* u8_this = reinterpret_cast<u8 const*>(this);
u8 const* u8_m_checksum = reinterpret_cast<u8 const*>(&m_checksum);
for (auto i = 0u; i < sizeof(TarFileHeader); ++i) {
if (u8_this + i >= u8_m_checksum && u8_this + i < u8_m_checksum + sizeof(m_checksum)) {
checksum += ' ';
} else {
checksum += u8_this[i];
}
}
return checksum;
}
ErrorOr<void> TarFileHeader::calculate_checksum()
{
memset(m_checksum, ' ', sizeof(m_checksum));
auto octal = TRY(String::formatted("{:06o}", expected_checksum()));
bool copy_successful = octal.bytes_as_string_view().copy_characters_to_buffer(m_checksum, sizeof(m_checksum));
VERIFY(copy_successful);
return {};
}
bool TarFileHeader::is_zero_block() const
{
u8 const* buffer = reinterpret_cast<u8 const*>(this);
for (size_t i = 0; i < sizeof(TarFileHeader); ++i) {
if (buffer[i] != 0)
return false;
}
return true;
}
bool TarFileHeader::content_is_like_extended_header() const
{
return type_flag() == TarFileType::ExtendedHeader || type_flag() == TarFileType::GlobalExtendedHeader;
}
void TarFileHeader::set_filename_and_prefix(StringView filename)
{
// FIXME: Add support for extended tar headers for longer filenames.
VERIFY(filename.length() <= sizeof(m_filename) + sizeof(m_prefix));
if (filename.length() <= sizeof(m_filename)) {
set_prefix(""sv);
set_filename(filename);
return;
}
Optional<size_t> slash = filename.find('/', filename.length() - sizeof(m_filename));
VERIFY(slash.has_value());
set_prefix(filename.substring_view(0, slash.value() + 1));
set_filename(filename.substring_view(slash.value() + 1));
}
}

View file

@ -1,163 +0,0 @@
/*
* Copyright (c) 2020-2022, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <AK/StringView.h>
#include <string.h>
#include <sys/types.h>
// glibc before 2.28 defines these from sys/types.h, but we don't want
// TarFileHeader::major() and TarFileHeader::minor() to use those macros
#ifdef minor
# undef minor
#endif
#ifdef major
# undef major
#endif
namespace Archive {
enum class TarFileType : char {
NormalFile = '0',
AlternateNormalFile = '\0',
HardLink = '1',
SymLink = '2',
CharacterSpecialFile = '3',
BlockSpecialFile = '4',
Directory = '5',
FIFO = '6',
ContiguousFile = '7',
GlobalExtendedHeader = 'g',
ExtendedHeader = 'x',
// GNU extensions
LongName = 'L',
};
constexpr size_t block_size = 512;
constexpr StringView gnu_magic = "ustar "sv; // gnu format magic
constexpr StringView gnu_version = " "sv; // gnu format version
constexpr StringView ustar_magic = "ustar"sv; // ustar format magic
constexpr StringView ustar_version = "00"sv; // ustar format version
constexpr StringView posix1_tar_magic = ""sv; // POSIX.1-1988 format magic
constexpr StringView posix1_tar_version = ""sv; // POSIX.1-1988 format version
template<size_t N>
static ErrorOr<size_t> get_field_as_integral(char const (&field)[N])
{
size_t value = 0;
for (size_t i = 0; i < N; ++i) {
if (field[i] == 0 || field[i] == ' ')
break;
if (field[i] < '0' || field[i] > '7')
return Error::from_string_literal("Passed a non-octal value");
value *= 8;
value += field[i] - '0';
}
return value;
}
template<size_t N>
static StringView get_field_as_string_view(char const (&field)[N])
{
return { field, min(__builtin_strlen(field), N) };
}
template<size_t N, class TSource>
static void set_field(char (&field)[N], TSource&& source)
{
VERIFY(N >= source.length());
memcpy(field, StringView(source).characters_without_null_termination(), source.length());
if (N > source.length())
field[source.length()] = 0;
}
template<class TSource, size_t N>
static ErrorOr<void> set_octal_field(char (&field)[N], TSource&& source)
{
auto octal = TRY(String::formatted("{:o}", forward<TSource>(source)));
set_field(field, octal.bytes_as_string_view());
return {};
}
class [[gnu::packed]] TarFileHeader {
public:
StringView filename() const { return get_field_as_string_view(m_filename); }
ErrorOr<mode_t> mode() const { return TRY(get_field_as_integral(m_mode)); }
ErrorOr<uid_t> uid() const { return TRY(get_field_as_integral(m_uid)); }
ErrorOr<gid_t> gid() const { return TRY(get_field_as_integral(m_gid)); }
// FIXME: support 2001-star size encoding
ErrorOr<size_t> size() const { return TRY(get_field_as_integral(m_size)); }
ErrorOr<time_t> timestamp() const { return TRY(get_field_as_integral(m_timestamp)); }
ErrorOr<unsigned> checksum() const { return TRY(get_field_as_integral(m_checksum)); }
TarFileType type_flag() const { return TarFileType(m_type_flag); }
StringView link_name() const { return { m_link_name, strlen(m_link_name) }; }
StringView magic() const { return get_field_as_string_view(m_magic); }
StringView version() const { return get_field_as_string_view(m_version); }
StringView owner_name() const { return get_field_as_string_view(m_owner_name); }
StringView group_name() const { return get_field_as_string_view(m_group_name); }
ErrorOr<int> major() const { return TRY(get_field_as_integral(m_major)); }
ErrorOr<int> minor() const { return TRY(get_field_as_integral(m_minor)); }
// FIXME: support ustar filename prefix
StringView prefix() const { return get_field_as_string_view(m_prefix); }
void set_filename(StringView filename) { set_field(m_filename, filename); }
ErrorOr<void> set_mode(mode_t mode) { return set_octal_field(m_mode, mode); }
ErrorOr<void> set_uid(uid_t uid) { return set_octal_field(m_uid, uid); }
ErrorOr<void> set_gid(gid_t gid) { return set_octal_field(m_gid, gid); }
ErrorOr<void> set_size(size_t size) { return set_octal_field(m_size, size); }
ErrorOr<void> set_timestamp(time_t timestamp) { return set_octal_field(m_timestamp, timestamp); }
void set_type_flag(TarFileType type) { m_type_flag = to_underlying(type); }
void set_link_name(StringView link_name) { set_field(m_link_name, link_name); }
// magic doesn't necessarily include a null byte
void set_magic(StringView magic) { set_field(m_magic, magic); }
// version doesn't necessarily include a null byte
void set_version(StringView version) { set_field(m_version, version); }
void set_owner_name(StringView owner_name) { set_field(m_owner_name, owner_name); }
void set_group_name(StringView group_name) { set_field(m_group_name, group_name); }
ErrorOr<void> set_major(int major) { return set_octal_field(m_major, major); }
ErrorOr<void> set_minor(int minor) { return set_octal_field(m_minor, minor); }
void set_prefix(StringView prefix) { set_field(m_prefix, prefix); }
unsigned expected_checksum() const;
ErrorOr<void> calculate_checksum();
bool is_zero_block() const;
bool content_is_like_extended_header() const;
void set_filename_and_prefix(StringView filename);
private:
char m_filename[100] { 0 };
char m_mode[8] { 0 };
char m_uid[8] { 0 };
char m_gid[8] { 0 };
char m_size[12] { 0 };
char m_timestamp[12] { 0 };
char m_checksum[8] { 0 }; // an uninitialized header's checksum is filled with spaces
char m_type_flag { 0 };
char m_link_name[100] { 0 };
char m_magic[6] { 0 };
char m_version[2] { 0 };
char m_owner_name[32] { 0 };
char m_group_name[32] { 0 };
char m_major[8] { 0 };
char m_minor[8] { 0 };
char m_prefix[155] { 0 }; // zero out the prefix for archiving
};
}
template<>
struct AK::Traits<Archive::TarFileHeader> : public AK::DefaultTraits<Archive::TarFileHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};

View file

@ -1,211 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/OwnPtr.h>
#include <LibArchive/TarStream.h>
#include <string.h>
namespace Archive {
TarFileStream::TarFileStream(TarInputStream& tar_stream)
: m_tar_stream(tar_stream)
, m_generation(tar_stream.m_generation)
{
}
ErrorOr<Bytes> TarFileStream::read_some(Bytes bytes)
{
// Verify that the stream has not advanced.
VERIFY(m_tar_stream.m_generation == m_generation);
auto header_size = TRY(m_tar_stream.header().size());
auto to_read = min(bytes.size(), header_size - m_tar_stream.m_file_offset);
auto slice = TRY(m_tar_stream.m_stream->read_some(bytes.trim(to_read)));
m_tar_stream.m_file_offset += slice.size();
return slice;
}
bool TarFileStream::is_eof() const
{
// Verify that the stream has not advanced.
VERIFY(m_tar_stream.m_generation == m_generation);
auto header_size_or_error = m_tar_stream.header().size();
if (header_size_or_error.is_error())
return true;
auto header_size = header_size_or_error.release_value();
return m_tar_stream.m_stream->is_eof()
|| m_tar_stream.m_file_offset >= header_size;
}
ErrorOr<size_t> TarFileStream::write_some(ReadonlyBytes)
{
return Error::from_errno(EBADF);
}
ErrorOr<NonnullOwnPtr<TarInputStream>> TarInputStream::construct(NonnullOwnPtr<Stream> stream)
{
auto tar_stream = TRY(adopt_nonnull_own_or_enomem(new (nothrow) TarInputStream(move(stream))));
TRY(tar_stream->load_next_header());
return tar_stream;
}
TarInputStream::TarInputStream(NonnullOwnPtr<Stream> stream)
: m_stream(move(stream))
{
}
static constexpr unsigned long block_ceiling(unsigned long offset)
{
return block_size * (1 + ((offset - 1) / block_size));
}
ErrorOr<void> TarInputStream::advance()
{
if (finished())
return Error::from_string_literal("Attempted to advance a finished stream");
m_generation++;
// Discard the pending bytes of the current entry.
auto file_size = TRY(m_header.size());
TRY(m_stream->discard(block_ceiling(file_size) - m_file_offset));
m_file_offset = 0;
TRY(load_next_header());
return {};
}
ErrorOr<void> TarInputStream::load_next_header()
{
size_t number_of_consecutive_zero_blocks = 0;
while (true) {
m_header = TRY(m_stream->read_value<TarFileHeader>());
// Discard the rest of the header block.
TRY(m_stream->discard(block_size - sizeof(TarFileHeader)));
if (!header().is_zero_block())
break;
number_of_consecutive_zero_blocks++;
// Two zero blocks in a row marks the end of the archive.
if (number_of_consecutive_zero_blocks >= 2) {
m_found_end_of_archive = true;
return {};
}
}
if (!TRY(valid()))
return Error::from_string_literal("Header has an invalid magic or checksum");
return {};
}
ErrorOr<bool> TarInputStream::valid() const
{
auto const header_magic = header().magic();
auto const header_version = header().version();
if (!((header_magic == gnu_magic && header_version == gnu_version)
|| (header_magic == ustar_magic && header_version == ustar_version)
|| (header_magic == posix1_tar_magic && header_version == posix1_tar_version)))
return false;
// POSIX.1-1988 tar does not have magic numbers, so we also need to verify the header checksum.
return TRY(header().checksum()) == header().expected_checksum();
}
TarFileStream TarInputStream::file_contents()
{
VERIFY(!finished());
return TarFileStream(*this);
}
TarOutputStream::TarOutputStream(MaybeOwned<Stream> stream)
: m_stream(move(stream))
{
}
ErrorOr<void> TarOutputStream::add_directory(StringView path, mode_t mode)
{
VERIFY(!m_finished);
TarFileHeader header {};
TRY(header.set_size(0));
header.set_filename_and_prefix(TRY(String::formatted("{}/", path))); // Old tar implementations assume directory names end with a /
header.set_type_flag(TarFileType::Directory);
TRY(header.set_mode(mode));
header.set_magic(gnu_magic);
header.set_version(gnu_version);
TRY(header.calculate_checksum());
TRY(m_stream->write_until_depleted(Bytes { &header, sizeof(header) }));
u8 padding[block_size] = { 0 };
TRY(m_stream->write_until_depleted(Bytes { &padding, block_size - sizeof(header) }));
return {};
}
ErrorOr<void> TarOutputStream::add_file(StringView path, mode_t mode, ReadonlyBytes bytes)
{
VERIFY(!m_finished);
TarFileHeader header {};
TRY(header.set_size(bytes.size()));
header.set_filename_and_prefix(path);
header.set_type_flag(TarFileType::NormalFile);
TRY(header.set_mode(mode));
header.set_magic(gnu_magic);
header.set_version(gnu_version);
TRY(header.calculate_checksum());
TRY(m_stream->write_until_depleted(ReadonlyBytes { &header, sizeof(header) }));
constexpr Array<u8, block_size> padding { 0 };
TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size - sizeof(header) }));
size_t n_written = 0;
while (n_written < bytes.size()) {
n_written += MUST(m_stream->write_some(bytes.slice(n_written, min(bytes.size() - n_written, block_size))));
}
TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size - (n_written % block_size) }));
return {};
}
ErrorOr<void> TarOutputStream::add_link(StringView path, mode_t mode, StringView link_name)
{
VERIFY(!m_finished);
TarFileHeader header {};
TRY(header.set_size(0));
header.set_filename_and_prefix(path);
header.set_type_flag(TarFileType::SymLink);
TRY(header.set_mode(mode));
header.set_magic(gnu_magic);
header.set_version(gnu_version);
header.set_link_name(link_name);
TRY(header.calculate_checksum());
TRY(m_stream->write_until_depleted(Bytes { &header, sizeof(header) }));
u8 padding[block_size] = { 0 };
TRY(m_stream->write_until_depleted(Bytes { &padding, block_size - sizeof(header) }));
return {};
}
ErrorOr<void> TarOutputStream::finish()
{
VERIFY(!m_finished);
constexpr Array<u8, block_size> padding { 0 };
// 2 empty records that are used to signify the end of the archive.
TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size }));
TRY(m_stream->write_until_depleted(ReadonlyBytes { &padding, block_size }));
m_finished = true;
return {};
}
}

View file

@ -1,130 +0,0 @@
/*
* Copyright (c) 2020, Peter Elliott <pelliott@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/MaybeOwned.h>
#include <AK/Span.h>
#include <AK/Stream.h>
#include <LibArchive/Tar.h>
namespace Archive {
class TarInputStream;
class TarFileStream : public Stream {
public:
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override { return true; }
virtual void close() override {};
private:
TarFileStream(TarInputStream& stream);
TarInputStream& m_tar_stream;
int m_generation;
friend class TarInputStream;
};
class TarInputStream {
public:
static ErrorOr<NonnullOwnPtr<TarInputStream>> construct(NonnullOwnPtr<Stream>);
ErrorOr<void> advance();
bool finished() const { return m_found_end_of_archive || m_stream->is_eof(); }
ErrorOr<bool> valid() const;
TarFileHeader const& header() const { return m_header; }
TarFileStream file_contents();
template<VoidFunction<StringView, StringView> F>
ErrorOr<void> for_each_extended_header(F func);
private:
TarInputStream(NonnullOwnPtr<Stream>);
ErrorOr<void> load_next_header();
TarFileHeader m_header;
NonnullOwnPtr<Stream> m_stream;
unsigned long m_file_offset { 0 };
int m_generation { 0 };
bool m_found_end_of_archive { false };
friend class TarFileStream;
};
class TarOutputStream {
public:
TarOutputStream(MaybeOwned<Stream>);
ErrorOr<void> add_file(StringView path, mode_t, ReadonlyBytes);
ErrorOr<void> add_link(StringView path, mode_t, StringView);
ErrorOr<void> add_directory(StringView path, mode_t);
ErrorOr<void> finish();
private:
MaybeOwned<Stream> m_stream;
bool m_finished { false };
friend class TarFileStream;
};
template<VoidFunction<StringView, StringView> F>
inline ErrorOr<void> TarInputStream::for_each_extended_header(F func)
{
VERIFY(header().content_is_like_extended_header());
Archive::TarFileStream file_stream = file_contents();
auto header_size = TRY(header().size());
ByteBuffer file_contents_buffer = TRY(ByteBuffer::create_zeroed(header_size));
TRY(file_stream.read_until_filled(file_contents_buffer));
StringView file_contents { file_contents_buffer };
while (!file_contents.is_empty()) {
// Split off the length (until the first space).
Optional<size_t> length_end_index = file_contents.find(' ');
if (!length_end_index.has_value())
return Error::from_string_literal("Malformed extended header: No length found.");
Optional<unsigned> length = file_contents.substring_view(0, length_end_index.value()).to_number<unsigned>();
if (!length.has_value())
return Error::from_string_literal("Malformed extended header: Could not parse length.");
if (length_end_index.value() >= length.value())
return Error::from_string_literal("Malformed extended header: Header length too short.");
unsigned int remaining_length = length.value();
remaining_length -= length_end_index.value() + 1;
file_contents = file_contents.substring_view(length_end_index.value() + 1);
if (file_contents.length() < remaining_length - 1)
return Error::from_string_literal("Malformed extended header: Header length too large.");
// Extract the header.
StringView header = file_contents.substring_view(0, remaining_length - 1);
file_contents = file_contents.substring_view(remaining_length - 1);
// Ensure that the header ends at the expected location.
if (file_contents.length() < 1 || !file_contents.starts_with('\n'))
return Error::from_string_literal("Malformed extended header: Header does not end at expected location.");
file_contents = file_contents.substring_view(1);
// Find the delimiting '='.
Optional<size_t> header_delimiter_index = header.find('=');
if (!header_delimiter_index.has_value())
return Error::from_string_literal("Malformed extended header: Header does not have a delimiter.");
StringView key = header.substring_view(0, header_delimiter_index.value());
StringView value = header.substring_view(header_delimiter_index.value() + 1);
func(key, value);
}
return {};
}
}

View file

@ -1,265 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibArchive/Zip.h>
#include <LibCompress/Deflate.h>
#include <LibCrypto/Checksum/CRC32.h>
namespace Archive {
bool Zip::find_end_of_central_directory_offset(ReadonlyBytes buffer, size_t& offset)
{
for (size_t backwards_offset = 0; backwards_offset <= UINT16_MAX; backwards_offset++) // the file may have a trailing comment of an arbitrary 16 bit length
{
if (buffer.size() < (sizeof(EndOfCentralDirectory) - sizeof(u8*)) + backwards_offset)
return false;
auto const signature_offset = (buffer.size() - (sizeof(EndOfCentralDirectory) - sizeof(u8*)) - backwards_offset);
if (auto signature = ReadonlyBytes { buffer.data() + signature_offset, EndOfCentralDirectory::signature.size() };
signature == EndOfCentralDirectory::signature) {
offset = signature_offset;
return true;
}
}
return false;
}
Optional<Zip> Zip::try_create(ReadonlyBytes buffer)
{
size_t end_of_central_directory_offset;
if (!find_end_of_central_directory_offset(buffer, end_of_central_directory_offset))
return {};
EndOfCentralDirectory end_of_central_directory {};
if (!end_of_central_directory.read(buffer.slice(end_of_central_directory_offset)))
return {};
if (end_of_central_directory.disk_number != 0 || end_of_central_directory.central_directory_start_disk != 0 || end_of_central_directory.disk_records_count != end_of_central_directory.total_records_count)
return {}; // TODO: support multi-volume zip archives
size_t member_offset = end_of_central_directory.central_directory_offset;
for (size_t i = 0; i < end_of_central_directory.total_records_count; i++) {
CentralDirectoryRecord central_directory_record {};
if (member_offset > buffer.size())
return {};
if (!central_directory_record.read(buffer.slice(member_offset)))
return {};
if (central_directory_record.general_purpose_flags.encrypted)
return {}; // TODO: support encrypted zip members
if (central_directory_record.general_purpose_flags.data_descriptor)
return {}; // TODO: support zip data descriptors
if (central_directory_record.compression_method != ZipCompressionMethod::Store && central_directory_record.compression_method != ZipCompressionMethod::Deflate)
return {}; // TODO: support obsolete zip compression methods
if (central_directory_record.compression_method == ZipCompressionMethod::Store && central_directory_record.uncompressed_size != central_directory_record.compressed_size)
return {};
if (central_directory_record.start_disk != 0)
return {}; // TODO: support multi-volume zip archives
if (memchr(central_directory_record.name, 0, central_directory_record.name_length) != nullptr)
return {};
LocalFileHeader local_file_header {};
if (central_directory_record.local_file_header_offset > buffer.size())
return {};
if (!local_file_header.read(buffer.slice(central_directory_record.local_file_header_offset)))
return {};
if (buffer.size() - (local_file_header.compressed_data - buffer.data()) < central_directory_record.compressed_size)
return {};
member_offset += central_directory_record.size();
}
return Zip {
end_of_central_directory.total_records_count,
end_of_central_directory.central_directory_offset,
buffer,
};
}
ErrorOr<bool> Zip::for_each_member(Function<ErrorOr<IterationDecision>(ZipMember const&)> callback) const
{
size_t member_offset = m_members_start_offset;
for (size_t i = 0; i < m_member_count; i++) {
CentralDirectoryRecord central_directory_record {};
VERIFY(central_directory_record.read(m_input_data.slice(member_offset)));
LocalFileHeader local_file_header {};
VERIFY(local_file_header.read(m_input_data.slice(central_directory_record.local_file_header_offset)));
ZipMember member;
member.name = TRY(String::from_utf8({ central_directory_record.name, central_directory_record.name_length }));
member.compressed_data = { local_file_header.compressed_data, central_directory_record.compressed_size };
member.compression_method = central_directory_record.compression_method;
member.uncompressed_size = central_directory_record.uncompressed_size;
member.crc32 = central_directory_record.crc32;
member.modification_time = central_directory_record.modification_time;
member.modification_date = central_directory_record.modification_date;
member.is_directory = central_directory_record.external_attributes & zip_directory_external_attribute || member.name.bytes_as_string_view().ends_with('/'); // FIXME: better directory detection
if (TRY(callback(member)) == IterationDecision::Break)
return false;
member_offset += central_directory_record.size();
}
return true;
}
ErrorOr<Statistics> Zip::calculate_statistics() const
{
size_t file_count = 0;
size_t directory_count = 0;
size_t uncompressed_bytes = 0;
TRY(for_each_member([&](auto zip_member) -> ErrorOr<IterationDecision> {
if (zip_member.is_directory)
directory_count++;
else
file_count++;
uncompressed_bytes += zip_member.uncompressed_size;
return IterationDecision::Continue;
}));
return Statistics(file_count, directory_count, uncompressed_bytes);
}
ZipOutputStream::ZipOutputStream(NonnullOwnPtr<Stream> stream)
: m_stream(move(stream))
{
}
static u16 minimum_version_needed(ZipCompressionMethod method)
{
// Deflate was added in PKZip 2.0
return method == ZipCompressionMethod::Deflate ? 20 : 10;
}
ErrorOr<void> ZipOutputStream::add_member(ZipMember const& member)
{
VERIFY(!m_finished);
VERIFY(member.name.bytes_as_string_view().length() <= UINT16_MAX);
VERIFY(member.compressed_data.size() <= UINT32_MAX);
TRY(m_members.try_append(member));
LocalFileHeader local_file_header {
.minimum_version = minimum_version_needed(member.compression_method),
.general_purpose_flags = { .flags = 0 },
.compression_method = static_cast<u16>(member.compression_method),
.modification_time = member.modification_time,
.modification_date = member.modification_date,
.crc32 = member.crc32,
.compressed_size = static_cast<u32>(member.compressed_data.size()),
.uncompressed_size = member.uncompressed_size,
.name_length = static_cast<u16>(member.name.bytes_as_string_view().length()),
.extra_data_length = 0,
.name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
.extra_data = nullptr,
.compressed_data = member.compressed_data.data(),
};
return local_file_header.write(*m_stream);
}
ErrorOr<ZipOutputStream::MemberInformation> ZipOutputStream::add_member_from_stream(StringView path, Stream& stream, Optional<Core::DateTime> const& modification_time)
{
auto buffer = TRY(stream.read_until_eof());
Archive::ZipMember member {};
member.name = TRY(String::from_utf8(path));
if (modification_time.has_value()) {
member.modification_date = to_packed_dos_date(modification_time->year(), modification_time->month(), modification_time->day());
member.modification_time = to_packed_dos_time(modification_time->hour(), modification_time->minute(), modification_time->second());
}
auto deflate_buffer = Compress::DeflateCompressor::compress_all(buffer);
auto compression_ratio = 1.f;
auto compressed_size = buffer.size();
if (!deflate_buffer.is_error() && deflate_buffer.value().size() < buffer.size()) {
member.compressed_data = deflate_buffer.value().bytes();
member.compression_method = Archive::ZipCompressionMethod::Deflate;
compression_ratio = static_cast<float>(deflate_buffer.value().size()) / static_cast<float>(buffer.size());
compressed_size = member.compressed_data.size();
} else {
member.compressed_data = buffer.bytes();
member.compression_method = Archive::ZipCompressionMethod::Store;
}
member.uncompressed_size = buffer.size();
Crypto::Checksum::CRC32 checksum { buffer.bytes() };
member.crc32 = checksum.digest();
member.is_directory = false;
TRY(add_member(member));
return MemberInformation { compression_ratio, compressed_size };
}
ErrorOr<void> ZipOutputStream::add_directory(StringView name, Optional<Core::DateTime> const& modification_time)
{
Archive::ZipMember member {};
member.name = TRY(String::from_utf8(name));
member.compressed_data = {};
member.compression_method = Archive::ZipCompressionMethod::Store;
member.uncompressed_size = 0;
member.crc32 = 0;
member.is_directory = true;
if (modification_time.has_value()) {
member.modification_date = to_packed_dos_date(modification_time->year(), modification_time->month(), modification_time->day());
member.modification_time = to_packed_dos_time(modification_time->hour(), modification_time->minute(), modification_time->second());
}
return add_member(member);
}
ErrorOr<void> ZipOutputStream::finish()
{
VERIFY(!m_finished);
m_finished = true;
auto file_header_offset = 0u;
auto central_directory_size = 0u;
for (ZipMember const& member : m_members) {
auto zip_version = minimum_version_needed(member.compression_method);
CentralDirectoryRecord central_directory_record {
.made_by_version = zip_version,
.minimum_version = zip_version,
.general_purpose_flags = { .flags = 0 },
.compression_method = member.compression_method,
.modification_time = member.modification_time,
.modification_date = member.modification_date,
.crc32 = member.crc32,
.compressed_size = static_cast<u32>(member.compressed_data.size()),
.uncompressed_size = member.uncompressed_size,
.name_length = static_cast<u16>(member.name.bytes_as_string_view().length()),
.extra_data_length = 0,
.comment_length = 0,
.start_disk = 0,
.internal_attributes = 0,
.external_attributes = member.is_directory ? zip_directory_external_attribute : 0,
.local_file_header_offset = file_header_offset, // FIXME: we assume the wrapped output stream was never written to before us
.name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
.extra_data = nullptr,
.comment = nullptr,
};
file_header_offset += sizeof(LocalFileHeader::signature) + (sizeof(LocalFileHeader) - (sizeof(u8*) * 3)) + member.name.bytes_as_string_view().length() + member.compressed_data.size();
TRY(central_directory_record.write(*m_stream));
central_directory_size += central_directory_record.size();
}
EndOfCentralDirectory end_of_central_directory {
.disk_number = 0,
.central_directory_start_disk = 0,
.disk_records_count = static_cast<u16>(m_members.size()),
.total_records_count = static_cast<u16>(m_members.size()),
.central_directory_size = central_directory_size,
.central_directory_offset = file_header_offset,
.comment_length = 0,
.comment = nullptr,
};
return end_of_central_directory.write(*m_stream);
}
}

View file

@ -1,300 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Array.h>
#include <AK/DOSPackedTime.h>
#include <AK/Function.h>
#include <AK/IterationDecision.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Stream.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibArchive/Statistics.h>
#include <LibCore/DateTime.h>
#include <string.h>
namespace Archive {
template<size_t fields_size, class T>
static bool read_helper(ReadonlyBytes buffer, T* self)
{
if (buffer.size() < T::signature.size() + fields_size)
return false;
if (buffer.slice(0, T::signature.size()) != T::signature)
return false;
memcpy(self, buffer.data() + T::signature.size(), fields_size);
return true;
}
// NOTE: Due to the format of zip files compression is streamed and decompression is random access.
static constexpr auto signature_length = 4;
struct [[gnu::packed]] EndOfCentralDirectory {
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x05, 0x06 }; // 'PK\x05\x06'
u16 disk_number;
u16 central_directory_start_disk;
u16 disk_records_count;
u16 total_records_count;
u32 central_directory_size;
u32 central_directory_offset;
u16 comment_length;
u8 const* comment;
bool read(ReadonlyBytes buffer)
{
constexpr auto fields_size = sizeof(EndOfCentralDirectory) - (sizeof(u8*) * 1);
if (!read_helper<fields_size>(buffer, this))
return false;
if (buffer.size() < signature.size() + fields_size + comment_length)
return false;
comment = buffer.data() + signature.size() + fields_size;
return true;
}
ErrorOr<void> write(Stream& stream) const
{
auto write_value = [&stream](auto value) {
return stream.write_until_depleted({ &value, sizeof(value) });
};
TRY(stream.write_until_depleted(signature));
TRY(write_value(disk_number));
TRY(write_value(central_directory_start_disk));
TRY(write_value(disk_records_count));
TRY(write_value(total_records_count));
TRY(write_value(central_directory_size));
TRY(write_value(central_directory_offset));
TRY(write_value(comment_length));
if (comment_length > 0)
TRY(stream.write_until_depleted({ comment, comment_length }));
return {};
}
};
enum class ZipCompressionMethod : u16 {
Store = 0,
Shrink = 1,
Reduce1 = 2,
Reduce2 = 3,
Reduce3 = 4,
Reduce4 = 5,
Implode = 6,
Reserved = 7,
Deflate = 8
};
union ZipGeneralPurposeFlags {
u16 flags;
struct {
u16 encrypted : 1;
u16 compression_options : 2;
u16 data_descriptor : 1;
u16 enhanced_deflation : 1;
u16 compressed_patched_data : 1;
u16 strong_encryption : 1;
u16 : 4;
u16 language_encoding : 1;
u16 : 1;
u16 masked_data_values : 1;
u16 : 2;
};
};
static_assert(sizeof(ZipGeneralPurposeFlags) == sizeof(u16));
struct [[gnu::packed]] CentralDirectoryRecord {
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x01, 0x02 }; // 'PK\x01\x02'
u16 made_by_version;
u16 minimum_version;
ZipGeneralPurposeFlags general_purpose_flags;
ZipCompressionMethod compression_method;
DOSPackedTime modification_time;
DOSPackedDate modification_date;
u32 crc32;
u32 compressed_size;
u32 uncompressed_size;
u16 name_length;
u16 extra_data_length;
u16 comment_length;
u16 start_disk;
u16 internal_attributes;
u32 external_attributes;
u32 local_file_header_offset;
u8 const* name;
u8 const* extra_data;
u8 const* comment;
bool read(ReadonlyBytes buffer)
{
constexpr auto fields_size = sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3);
if (!read_helper<fields_size>(buffer, this))
return false;
if (buffer.size() < size())
return false;
name = buffer.data() + signature.size() + fields_size;
extra_data = name + name_length;
comment = extra_data + extra_data_length;
return true;
}
ErrorOr<void> write(Stream& stream) const
{
auto write_value = [&stream](auto value) {
return stream.write_until_depleted({ &value, sizeof(value) });
};
TRY(stream.write_until_depleted(signature));
TRY(write_value(made_by_version));
TRY(write_value(minimum_version));
TRY(write_value(general_purpose_flags.flags));
TRY(write_value(compression_method));
TRY(write_value(modification_time));
TRY(write_value(modification_date));
TRY(write_value(crc32));
TRY(write_value(compressed_size));
TRY(write_value(uncompressed_size));
TRY(write_value(name_length));
TRY(write_value(extra_data_length));
TRY(write_value(comment_length));
TRY(write_value(start_disk));
TRY(write_value(internal_attributes));
TRY(write_value(external_attributes));
TRY(write_value(local_file_header_offset));
if (name_length > 0)
TRY(stream.write_until_depleted({ name, name_length }));
if (extra_data_length > 0)
TRY(stream.write_until_depleted({ extra_data, extra_data_length }));
if (comment_length > 0)
TRY(stream.write_until_depleted({ comment, comment_length }));
return {};
}
[[nodiscard]] size_t size() const
{
return signature.size() + (sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3)) + name_length + extra_data_length + comment_length;
}
};
static constexpr u32 zip_directory_external_attribute = 1 << 4;
struct [[gnu::packed]] LocalFileHeader {
static constexpr Array<u8, signature_length> signature = { 0x50, 0x4b, 0x03, 0x04 }; // 'PK\x03\x04'
u16 minimum_version;
ZipGeneralPurposeFlags general_purpose_flags;
u16 compression_method;
DOSPackedTime modification_time;
DOSPackedDate modification_date;
u32 crc32;
u32 compressed_size;
u32 uncompressed_size;
u16 name_length;
u16 extra_data_length;
u8 const* name;
u8 const* extra_data;
u8 const* compressed_data;
bool read(ReadonlyBytes buffer)
{
constexpr auto fields_size = sizeof(LocalFileHeader) - (sizeof(u8*) * 3);
if (!read_helper<fields_size>(buffer, this))
return false;
if (buffer.size() < signature.size() + fields_size + name_length + extra_data_length + compressed_size)
return false;
name = buffer.data() + signature.size() + fields_size;
extra_data = name + name_length;
compressed_data = extra_data + extra_data_length;
return true;
}
ErrorOr<void> write(Stream& stream) const
{
auto write_value = [&stream](auto value) {
return stream.write_until_depleted({ &value, sizeof(value) });
};
TRY(stream.write_until_depleted(signature));
TRY(write_value(minimum_version));
TRY(write_value(general_purpose_flags.flags));
TRY(write_value(compression_method));
TRY(write_value(modification_time));
TRY(write_value(modification_date));
TRY(write_value(crc32));
TRY(write_value(compressed_size));
TRY(write_value(uncompressed_size));
TRY(write_value(name_length));
TRY(write_value(extra_data_length));
if (name_length > 0)
TRY(stream.write_until_depleted({ name, name_length }));
if (extra_data_length > 0)
TRY(stream.write_until_depleted({ extra_data, extra_data_length }));
if (compressed_size > 0)
TRY(stream.write_until_depleted({ compressed_data, compressed_size }));
return {};
}
};
struct ZipMember {
String name;
ReadonlyBytes compressed_data; // TODO: maybe the decompression/compression should be handled by LibArchive instead of the user?
ZipCompressionMethod compression_method;
u32 uncompressed_size;
u32 crc32;
bool is_directory;
DOSPackedTime modification_time;
DOSPackedDate modification_date;
};
class Zip {
public:
static Optional<Zip> try_create(ReadonlyBytes buffer);
ErrorOr<bool> for_each_member(Function<ErrorOr<IterationDecision>(ZipMember const&)>) const;
ErrorOr<Statistics> calculate_statistics() const;
private:
static bool find_end_of_central_directory_offset(ReadonlyBytes, size_t& offset);
Zip(u16 member_count, size_t members_start_offset, ReadonlyBytes input_data)
: m_member_count { member_count }
, m_members_start_offset { members_start_offset }
, m_input_data { input_data }
{
}
u16 m_member_count { 0 };
size_t m_members_start_offset { 0 };
ReadonlyBytes m_input_data;
};
class ZipOutputStream {
public:
struct MemberInformation {
float compression_ratio;
size_t compressed_size;
};
ZipOutputStream(NonnullOwnPtr<Stream>);
ErrorOr<void> add_member(ZipMember const&);
ErrorOr<MemberInformation> add_member_from_stream(StringView, Stream&, Optional<Core::DateTime> const& = {});
// NOTE: This does not add any of the files within the directory,
// it just adds an entry for it.
ErrorOr<void> add_directory(StringView, Optional<Core::DateTime> const& = {});
ErrorOr<void> finish();
private:
NonnullOwnPtr<Stream> m_stream;
Vector<ZipMember> m_members;
bool m_finished { false };
};
}

View file

@ -378,7 +378,6 @@ endif()
# Lagom Libraries # Lagom Libraries
set(lagom_standard_libraries set(lagom_standard_libraries
Archive
Compress Compress
Crypto Crypto
Diff Diff

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/MemoryStream.h>
#include <AK/NonnullOwnPtr.h>
#include <LibArchive/TarStream.h>
#include <stdio.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
auto input_stream_or_error = try_make<FixedMemoryStream>(ReadonlyBytes { data, size });
if (input_stream_or_error.is_error())
return 0;
auto tar_stream_or_error = Archive::TarInputStream::construct(input_stream_or_error.release_value());
if (tar_stream_or_error.is_error())
return 0;
auto tar_stream = tar_stream_or_error.release_value();
while (!tar_stream->finished()) {
auto const& header = tar_stream->header();
if (!header.content_is_like_extended_header()) {
if (tar_stream->advance().is_error())
return 0;
else
continue;
}
switch (header.type_flag()) {
case Archive::TarFileType::GlobalExtendedHeader:
case Archive::TarFileType::ExtendedHeader: {
auto result = tar_stream->for_each_extended_header([&](StringView, StringView) {});
if (result.is_error())
return 0;
break;
}
default:
return 0;
}
if (tar_stream->advance().is_error())
return 0;
}
return 0;
}

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibArchive/Zip.h>
#include <stddef.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
auto zip_file = Archive::Zip::try_create({ data, size });
if (!zip_file.has_value())
return 0;
(void)zip_file->for_each_member([](auto&) {
return IterationDecision::Continue;
});
return 0;
}

View file

@ -29,7 +29,6 @@ set(FUZZER_TARGETS
SHA256 SHA256
SHA384 SHA384
SHA512 SHA512
Tar
TextDecoder TextDecoder
TIFFLoader TIFFLoader
TinyVGLoader TinyVGLoader
@ -39,7 +38,6 @@ set(FUZZER_TARGETS
WOFF WOFF
WOFF2 WOFF2
XML XML
Zip
ZlibDecompression ZlibDecompression
) )
@ -62,7 +60,7 @@ set(FUZZER_DEPENDENCIES_ICCProfile LibGfx)
set(FUZZER_DEPENDENCIES_ICOLoader LibGfx) set(FUZZER_DEPENDENCIES_ICOLoader LibGfx)
set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx) set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx)
set(FUZZER_DEPENDENCIES_Js LibJS LibGC) set(FUZZER_DEPENDENCIES_Js LibJS LibGC)
set(FUZZER_DEPENDENCIES_LzmaDecompression LibArchive LibCompress) set(FUZZER_DEPENDENCIES_LzmaDecompression LibCompress)
set(FUZZER_DEPENDENCIES_LzmaRoundtrip LibCompress) set(FUZZER_DEPENDENCIES_LzmaRoundtrip LibCompress)
set(FUZZER_DEPENDENCIES_MatroskaReader LibMedia) set(FUZZER_DEPENDENCIES_MatroskaReader LibMedia)
set(FUZZER_DEPENDENCIES_MD5 LibCrypto) set(FUZZER_DEPENDENCIES_MD5 LibCrypto)
@ -77,7 +75,6 @@ set(FUZZER_DEPENDENCIES_SHA1 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA256 LibCrypto) set(FUZZER_DEPENDENCIES_SHA256 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA384 LibCrypto) set(FUZZER_DEPENDENCIES_SHA384 LibCrypto)
set(FUZZER_DEPENDENCIES_SHA512 LibCrypto) set(FUZZER_DEPENDENCIES_SHA512 LibCrypto)
set(FUZZER_DEPENDENCIES_Tar LibArchive)
set(FUZZER_DEPENDENCIES_TextDecoder LibTextCodec) set(FUZZER_DEPENDENCIES_TextDecoder LibTextCodec)
set(FUZZER_DEPENDENCIES_TIFFLoader LibGfx) set(FUZZER_DEPENDENCIES_TIFFLoader LibGfx)
set(FUZZER_DEPENDENCIES_TTF LibGfx) set(FUZZER_DEPENDENCIES_TTF LibGfx)
@ -88,5 +85,4 @@ set(FUZZER_DEPENDENCIES_WebPLoader LibGfx)
set(FUZZER_DEPENDENCIES_WOFF LibGfx) set(FUZZER_DEPENDENCIES_WOFF LibGfx)
set(FUZZER_DEPENDENCIES_WOFF2 LibGfx) set(FUZZER_DEPENDENCIES_WOFF2 LibGfx)
set(FUZZER_DEPENDENCIES_XML LibXML) set(FUZZER_DEPENDENCIES_XML LibXML)
set(FUZZER_DEPENDENCIES_Zip LibArchive)
set(FUZZER_DEPENDENCIES_ZlibDecompression LibCompress) set(FUZZER_DEPENDENCIES_ZlibDecompression LibCompress)

View file

@ -24,7 +24,6 @@ At the moment, many core library support components are inherited from SerenityO
- LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security - LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security
- LibHTTP: HTTP/1.1 client - LibHTTP: HTTP/1.1 client
- LibGfx: 2D Graphics Library, Image Decoding and Rendering - LibGfx: 2D Graphics Library, Image Decoding and Rendering
- LibArchive: Archive file format support
- LibUnicode: Unicode and locale support - LibUnicode: Unicode and locale support
- LibMedia: Audio and video playback - LibMedia: Audio and video playback
- LibCore: Event loop, OS abstraction layer - LibCore: Event loop, OS abstraction layer

View file

@ -7,7 +7,7 @@ add_library(ladybird SHARED
src/main/cpp/TimerExecutorService.cpp src/main/cpp/TimerExecutorService.cpp
src/main/cpp/JNIHelpers.cpp src/main/cpp/JNIHelpers.cpp
) )
target_link_libraries(ladybird PRIVATE LibArchive jnigraphics android) target_link_libraries(ladybird PRIVATE jnigraphics android)
include(../cmake/AndroidExtras.cmake) include(../cmake/AndroidExtras.cmake)
create_ladybird_bundle(ladybird) create_ladybird_bundle(ladybird)