mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-22 09:12:13 -05:00
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:
parent
bdabc9b70d
commit
03b9e555c0
Notes:
github-actions[bot]
2024-11-25 12:38:39 +00:00
Author: https://github.com/rmg-x Commit: https://github.com/LadybirdBrowser/ladybird/commit/03b9e555c0e Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2527
14 changed files with 2 additions and 1267 deletions
|
@ -1,8 +0,0 @@
|
||||||
set(SOURCES
|
|
||||||
Tar.cpp
|
|
||||||
TarStream.cpp
|
|
||||||
Zip.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
serenity_lib(LibArchive archive)
|
|
||||||
target_link_libraries(LibArchive PRIVATE LibCompress LibCore LibCrypto)
|
|
|
@ -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 };
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
};
|
|
|
@ -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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 };
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -378,7 +378,6 @@ endif()
|
||||||
|
|
||||||
# Lagom Libraries
|
# Lagom Libraries
|
||||||
set(lagom_standard_libraries
|
set(lagom_standard_libraries
|
||||||
Archive
|
|
||||||
Compress
|
Compress
|
||||||
Crypto
|
Crypto
|
||||||
Diff
|
Diff
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue