Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge main
Signed-off-by: Balakrishna Avulapati <[email protected]>
  • Loading branch information
bavulapati committed Jul 27, 2025
commit e7a95d7fd01afc5fa99cc2e076827c056986b769
12 changes: 8 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ if(SOURCEMETA_CORE_UUID)
add_subdirectory(src/core/uuid)
endif()

if(SOURCEMETA_CORE_GZIP OR SOURCEMETA_CORE_CONTRIB_ZLIB)
if(SOURCEMETA_CORE_CONTRIB_ZLIB OR SOURCEMETA_CORE_GZIP)
find_package(ZLIB REQUIRED)
endif()

if(SOURCEMETA_CORE_GZIP)
add_subdirectory(src/core/gzip)
endif()

if(SOURCEMETA_CORE_MD5 OR SOURCEMETA_CORE_CONTRIB_BEARSSL)
if(SOURCEMETA_CORE_CONTRIB_BEARSSL OR SOURCEMETA_CORE_MD5)
find_package(BearSSL REQUIRED)
endif()

if(SOURCEMETA_CORE_MD5)
add_subdirectory(src/core/md5)
endif()

Expand Down Expand Up @@ -121,8 +127,6 @@ if(SOURCEMETA_CORE_DOCS)
endif()

if(PROJECT_IS_TOP_LEVEL)
# TODO: Try once more to enable this per target,
# so, we don't need to manually disable it here
sourcemeta_target_clang_format(SOURCES
src/*.h src/*.cc
benchmark/*.h benchmark/*.cc
Expand Down
2 changes: 1 addition & 1 deletion cmake/common/clang-tidy.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Checks": "-*, bugprone-unchecked-optional-access, concurrency-*, modernize-*, performance-*, portability-*",
"Checks": "-*, bugprone-*, -bugprone-easily-swappable-parameters,-bugprone-unchecked-optional-access, concurrency-*, modernize-*, performance-*, portability-*",
"WarningsAsErrors": "*",
"FormatStyle": "none",
"UseColor": true
Expand Down
4 changes: 3 additions & 1 deletion src/core/gzip/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME gzip SOURCES gzip.cc)
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME gzip
PRIVATE_HEADERS error.h
SOURCES gzip.cc)

if(SOURCEMETA_CORE_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME gzip)
Expand Down
136 changes: 111 additions & 25 deletions src/core/gzip/gzip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,129 @@ extern "C" {
}

#include <array> // std::array
#include <cstring> // std::memset
#include <sstream> // std::ostringstream
#include <sstream> // std::istringstream, std::ostringstream
#include <utility> // std::move

namespace sourcemeta::core {

auto gzip(std::string_view input) -> std::optional<std::string> {
z_stream stream;
std::memset(&stream, 0, sizeof(stream));
int code = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
16 + MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
if (code != Z_OK) {
return std::nullopt;
constexpr auto ZLIB_BUFFER_SIZE{4096};

auto gzip(std::istream &input, std::ostream &output) -> void {
z_stream zstream{};
if (deflateInit2(&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 16 + MAX_WBITS,
8, Z_DEFAULT_STRATEGY) != Z_OK) {
throw GZIPError{"Could not compress input"};
}

stream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
stream.avail_in = static_cast<uInt>(input.size());
std::array<char, ZLIB_BUFFER_SIZE> buffer_input;
std::array<char, ZLIB_BUFFER_SIZE> buffer_output;
bool reached_end_of_input{false};
auto code{Z_OK};

std::array<char, 4096> buffer;
std::ostringstream compressed;
while (code != Z_STREAM_END) {
if (zstream.avail_in == 0 && !reached_end_of_input) {
input.read(buffer_input.data(), buffer_input.size());
const auto bytes_read = input.gcount();
if (bytes_read > 0) {
zstream.next_in = reinterpret_cast<Bytef *>(buffer_input.data());
zstream.avail_in = static_cast<uInt>(bytes_read);
} else {
reached_end_of_input = true;
}
}

do {
stream.next_out = reinterpret_cast<Bytef *>(buffer.data());
stream.avail_out = sizeof(buffer);
code = deflate(&stream, Z_FINISH);
compressed.write(buffer.data(), sizeof(buffer) - stream.avail_out);
} while (code == Z_OK);
zstream.next_out = reinterpret_cast<Bytef *>(buffer_output.data());
zstream.avail_out = static_cast<uInt>(buffer_output.size());

if (code != Z_STREAM_END) {
return std::nullopt;
const int flush_mode = reached_end_of_input ? Z_FINISH : Z_NO_FLUSH;
code = deflate(&zstream, flush_mode);
if (code == Z_STREAM_ERROR) {
deflateEnd(&zstream);
throw GZIPError{"Could not compress input"};
}

const auto bytes_written = buffer_output.size() - zstream.avail_out;
if (bytes_written > 0) {
output.write(buffer_output.data(),
static_cast<std::streamsize>(bytes_written));
if (!output) {
deflateEnd(&zstream);
throw GZIPError{"Could not compress input"};
}
}
}

code = deflateEnd(&stream);
if (code != Z_OK) {
return std::nullopt;
if (deflateEnd(&zstream) != Z_OK) {
throw GZIPError{"Could not compress input"};
}
}

auto gunzip(std::istream &input, std::ostream &output) -> void {
z_stream zstream{};
if (inflateInit2(&zstream, 16 + MAX_WBITS) != Z_OK) {
throw GZIPError("Could not decompress input");
}

std::array<char, ZLIB_BUFFER_SIZE> buffer_input;
std::array<char, ZLIB_BUFFER_SIZE> buffer_output;

auto code{Z_OK};
while (code != Z_STREAM_END) {
if (zstream.avail_in == 0 && input) {
input.read(buffer_input.data(), buffer_input.size());
const auto bytes_read = input.gcount();
if (bytes_read > 0) {
zstream.next_in = reinterpret_cast<Bytef *>(buffer_input.data());
zstream.avail_in = static_cast<uInt>(bytes_read);
} else {
break;
}
}

zstream.next_out = reinterpret_cast<Bytef *>(buffer_output.data());
zstream.avail_out = static_cast<uInt>(buffer_output.size());

code = inflate(&zstream, Z_NO_FLUSH);
if (code == Z_NEED_DICT || code == Z_DATA_ERROR || code == Z_MEM_ERROR) {
inflateEnd(&zstream);
throw GZIPError("Could not decompress input");
} else {
const auto bytes_written = buffer_output.size() - zstream.avail_out;
output.write(buffer_output.data(),
static_cast<std::streamsize>(bytes_written));
if (!output) {
inflateEnd(&zstream);
throw GZIPError("Could not decompress input");
}
}
}

return compressed.str();
inflateEnd(&zstream);
if (code != Z_STREAM_END) {
throw GZIPError("Could not decompress input");
}
}

auto gzip(std::istream &stream) -> std::string {
std::ostringstream output;
gzip(stream, output);
return output.str();
}

auto gzip(const std::string &input) -> std::string {
std::istringstream stream{input};
return gzip(stream);
}

auto gunzip(std::istream &stream) -> std::string {
std::ostringstream output;
gunzip(stream, output);
return output.str();
}

auto gunzip(const std::string &input) -> std::string {
std::istringstream stream{input};
return gunzip(stream);
}

} // namespace sourcemeta::core
101 changes: 93 additions & 8 deletions src/core/gzip/include/sourcemeta/core/gzip.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
#include <sourcemeta/core/gzip_export.h>
#endif

#include <optional> // std::optional
// NOLINTBEGIN(misc-include-cleaner)
#include <sourcemeta/core/gzip_error.h>
// NOLINTEND(misc-include-cleaner)

#include <istream> // std::istream
#include <ostream> // std::ostream
#include <string> // std::string
#include <string_view> // std::string_view

/// @defgroup gzip GZIP
/// @brief A growing implementation of RFC 1952 GZIP.
/// @brief An implementation of RFC 1952 GZIP.
///
/// This functionality is included as follows:
///
Expand All @@ -22,19 +27,99 @@ namespace sourcemeta::core {

/// @ingroup gzip
///
/// Compress an input string into a sequence of bytes represented using a
/// string. For example:
/// Compress an input stream into an output stream. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
/// #include <sstream>
///
/// std::istringstream input{"Hello World"};
/// std::ostringstream output;
/// sourcemeta::core::gzip(input, output);
/// assert(!output.str().empty());
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gzip(std::istream &input, std::ostream &output)
-> void;

/// @ingroup gzip
///
/// A convenience function to compress an input stream into a sequence of
/// bytes represented using a string. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
/// #include <sstream>
///
/// std::istringstream stream{"Hello World"};
/// const auto result{sourcemeta::core::gzip(stream)};
/// assert(result == "Hello World");
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gzip(std::istream &stream) -> std::string;

/// @ingroup gzip
///
/// A convenience function to compress an input string into a sequence of bytes
/// represented using a string. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
///
/// const auto result{sourcemeta::core::gzip("Hello World")};
/// assert(result.has_value());
/// assert(!result.value().empty());
/// assert(!result.empty());
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gzip(const std::string &input) -> std::string;

/// @ingroup gzip
///
/// Decompress an input stream into an output stream. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
/// #include <sstream>
///
/// std::istringstream input{sourcemeta::core::gzip("Hello World")};
/// std::ostringstream output;
/// sourcemeta::core::gunzip(input, output);
/// assert(output.str() == "Hello World");
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gunzip(std::istream &input,
std::ostream &output) -> void;

/// @ingroup gzip
///
/// A convenience function to decompress an input stream into a sequence of
/// bytes represented using a string. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
/// #include <sstream>
///
/// const auto input{sourcemeta::core::gzip("Hello World")};
/// std::istringstream stream{input};
/// const auto result{sourcemeta::core::gunzip(stream)};
/// assert(result == "Hello World");
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gunzip(std::istream &stream) -> std::string;

/// @ingroup gzip
///
/// A convenience function to decompress an input string into a sequence of
/// bytes represented using a string. For example:
///
/// ```cpp
/// #include <sourcemeta/core/gzip.h>
/// #include <cassert>
///
/// const auto result{sourcemeta::core::gunzip("Hello World")};
/// assert(result == "Hello World");
/// ```
SOURCEMETA_CORE_GZIP_EXPORT auto gzip(std::string_view input)
-> std::optional<std::string>;
SOURCEMETA_CORE_GZIP_EXPORT auto gunzip(const std::string &input)
-> std::string;

} // namespace sourcemeta::core

Expand Down
40 changes: 40 additions & 0 deletions src/core/gzip/include/sourcemeta/core/gzip_error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef SOURCEMETA_CORE_GZIP_ERROR_H_
#define SOURCEMETA_CORE_GZIP_ERROR_H_

#ifndef SOURCEMETA_CORE_GZIP_EXPORT
#include <sourcemeta/core/gzip_export.h>
#endif

#include <exception> // std::exception
#include <string> // std::string
#include <utility> // std::move

namespace sourcemeta::core {

// Exporting symbols that depends on the standard C++ library is considered
// safe.
// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
#if defined(_MSC_VER)
#pragma warning(disable : 4251 4275)
#endif

/// @ingroup gzip
/// An error that represents a general GZIP error event
class SOURCEMETA_CORE_GZIP_EXPORT GZIPError : public std::exception {
public:
GZIPError(std::string message) : message_{std::move(message)} {}
[[nodiscard]] auto what() const noexcept -> const char * override {
return this->message_.c_str();
}

private:
std::string message_;
};

#if defined(_MSC_VER)
#pragma warning(default : 4251 4275)
#endif

} // namespace sourcemeta::core

#endif
Loading