Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ jobs:
ctest -C ${{ matrix.config.build_type }}

# Alpine/musl: vcpkg's default setup downloads glibc-linked tools, and
# Alpine lacks curlpp / INIReader packages -- exercises the pkg-config +
# Alpine lacks httplib / INIReader packages -- exercises the pkg-config +
# upstream source fallback path.
build-alpine:
name: Alpine_Latest_GCC
Expand All @@ -209,7 +209,7 @@ jobs:
- name: Install dependencies
run: |
apk add --no-cache build-base cmake git ninja pkgconf \
curl-dev inih-dev nlohmann-json openssl-dev pugixml-dev zlib-dev
inih-dev nlohmann-json openssl-dev pugixml-dev zlib-dev
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Examples are built when `MINIO_CPP_TEST=ON`. Run individual examples:
- **`args.h`** - Argument structs for each S3 operation (e.g., `PutObjectArgs`, `GetObjectArgs`)
- **`response.h`** - Response types returned by operations
- **`request.h`** - HTTP request construction
- **`http.h`** - HTTP client abstraction using curlpp
- **`http.h`** - HTTP client abstraction using httplib

### Authentication

Expand All @@ -85,7 +85,7 @@ RDMA and the caller falls back to HTTP.

## Dependencies (vcpkg)

- curlpp - HTTP client
- cpp-httplib - HTTP client
- inih - INI file parsing for config
- nlohmann-json - JSON handling
- openssl - TLS/crypto
Expand Down
12 changes: 11 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ if (WIN32)
list(APPEND MINIO_CPP_LIBS wsock32)
list(APPEND MINIO_CPP_LIBS ws2_32)
endif()
if (APPLE)
# httplib loads macOS system certificates from the Keychain
# (SecTrustSettingsCopyCertificates); the vcpkg httplib target does not
# link the Security framework, so add it here.
list(APPEND MINIO_CPP_LIBS
"-framework CFNetwork" "-framework CoreFoundation" "-framework Security")
endif()

# Minio C++ Library
# -----------------
Expand Down Expand Up @@ -172,6 +179,9 @@ target_include_directories(miniocpp PUBLIC
$<BUILD_INTERFACE:${MINIO_CPP_INCLUDES}>
)
target_link_libraries(miniocpp PUBLIC ${MINIO_CPP_LIBS})
# httplib's class layout depends on CPPHTTPLIB_OPENSSL_SUPPORT; define it once
# target-wide so every translation unit compiles httplib.h identically.
target_compile_definitions(miniocpp PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
if (MINIO_CPP_ENABLE_RDMA)
target_compile_definitions(miniocpp PUBLIC MINIO_CPP_RDMA)
endif()
Expand Down Expand Up @@ -315,7 +325,7 @@ install(TARGETS miniocpp
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")

# Source-built deps must be in an export set for install(EXPORT) to resolve
# them (curlpp exports itself; miniocpp_inih does not).
# them (miniocpp_inih does not export itself).
if (MINIO_CPP_DEPS_EXPORT_TARGETS)
install(TARGETS ${MINIO_CPP_DEPS_EXPORT_TARGETS}
EXPORT miniocpp-targets
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,14 @@ $ ./configure.sh -DMINIO_CPP_TEST=ON
`vcpkg` can run on Alpine, but its default setup downloads glibc-linked tools
such as CMake, which do not run on musl (setting `VCPKG_FORCE_SYSTEM_BINARIES`
forces use of the apk-installed tools instead). Alpine also has no packages
for `curlpp` or the C++ `INIReader`. The dependency resolver
for `cpp-httplib` or the C++ `INIReader`. The dependency resolver
(`cmake/miniocpp-deps.cmake`) therefore falls back to `pkg-config` and then
to fetching those two libraries from source, so a plain system-package build
works:

```bash
$ apk add build-base cmake git ninja pkgconf \
curl-dev inih-dev nlohmann-json openssl-dev pugixml-dev zlib-dev
inih-dev nlohmann-json openssl-dev pugixml-dev zlib-dev
$ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMINIO_CPP_TEST=ON
$ cmake --build build
```
Expand Down
80 changes: 49 additions & 31 deletions cmake/miniocpp-deps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Resolution order per dependency: vcpkg CONFIG package -> pkg-config ->
# upstream source. The source branch is for distros where vcpkg is
# impractical (its default setup downloads glibc-linked tools that do not run
# on musl) and no curlpp / C++ INIReader packages exist. It clones at
# on musl) and no C++ INIReader packages exist. It clones at
# configure time with plain git, so it works on the CMake 3.13.4 floor (no
# FetchContent); vcpkg builds never reach it.
#
Expand All @@ -20,44 +20,62 @@ find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)

# curlpp -- pinned master commit, no patches needed: CURLOPT_CLOSEPOLICY is
# gone upstream (dropped in curl 8.10) and the build is target-based and
# self-exporting. Static target keeps BUILD_SHARED_LIBS=OFF builds working.
find_package(unofficial-curlpp CONFIG QUIET)
if (unofficial-curlpp_FOUND)
set(MINIO_CPP_CURLPP_TARGET unofficial::curlpp::curlpp)
# cpp-httplib -- header-only; vcpkg -> pkg-config -> upstream source (pinned
# tag). The code needs the progress overloads and set_max_timeout added in
# 0.19; require 0.51 (the current vcpkg port) so ancient distro packages
# cannot be selected.
find_package(httplib CONFIG QUIET)
if (httplib_FOUND AND DEFINED httplib_VERSION AND
httplib_VERSION VERSION_LESS "0.51")
message(STATUS "cpp-httplib ${httplib_VERSION} is too old; falling back")
set(httplib_FOUND FALSE)
endif()
if (httplib_FOUND)
set(MINIO_CPP_HTTPLIB_TARGET httplib::httplib)
else()
if (PkgConfig_FOUND)
pkg_check_modules(MINIO_CPP_CURLPP QUIET IMPORTED_TARGET curlpp)
pkg_check_modules(MINIO_CPP_HTTPLIB QUIET IMPORTED_TARGET cpp-httplib)
endif()
if (MINIO_CPP_HTTPLIB_FOUND AND DEFINED MINIO_CPP_HTTPLIB_VERSION AND
MINIO_CPP_HTTPLIB_VERSION VERSION_LESS "0.51")
message(STATUS "cpp-httplib ${MINIO_CPP_HTTPLIB_VERSION} is too old")
set(MINIO_CPP_HTTPLIB_FOUND FALSE)
endif()
if (MINIO_CPP_CURLPP_FOUND)
set(MINIO_CPP_CURLPP_TARGET PkgConfig::MINIO_CPP_CURLPP)
if (MINIO_CPP_HTTPLIB_FOUND)
set(MINIO_CPP_HTTPLIB_TARGET PkgConfig::MINIO_CPP_HTTPLIB)
else()
message(STATUS "curlpp: no package found, building from source")
set(MINIO_CPP_CURLPP_SRC "${CMAKE_CURRENT_BINARY_DIR}/_deps/curlpp-src")
if (NOT EXISTS "${MINIO_CPP_CURLPP_SRC}/CMakeLists.txt")
message(STATUS "cpp-httplib: no usable package found, building from source")
set(MINIO_CPP_HTTPLIB_SRC "${CMAKE_CURRENT_BINARY_DIR}/_deps/cpp-httplib-src")
set(MINIO_CPP_HTTPLIB_PINNED_TAG "v0.53.1")
if (NOT EXISTS "${MINIO_CPP_HTTPLIB_SRC}/CMakeLists.txt")
execute_process(COMMAND git clone --quiet
https://github.com/jpbarrette/curlpp.git
"${MINIO_CPP_CURLPP_SRC}"
RESULT_VARIABLE _curlpp_clone)
if (NOT _curlpp_clone STREQUAL "0")
message(FATAL_ERROR "curlpp: git clone failed")
https://github.com/yhirose/cpp-httplib.git
"${MINIO_CPP_HTTPLIB_SRC}"
RESULT_VARIABLE _httplib_clone)
if (NOT _httplib_clone STREQUAL "0")
message(FATAL_ERROR "cpp-httplib: git clone failed")
endif()
endif()
# Also reset a cached checkout to the pinned commit, not just a fresh
# clone.
# Fetch tags so a cached clone can resolve the pinned tag.
execute_process(COMMAND git fetch --quiet --tags origin
WORKING_DIRECTORY "${MINIO_CPP_HTTPLIB_SRC}"
RESULT_VARIABLE _httplib_fetch)
if (NOT _httplib_fetch STREQUAL "0")
message(FATAL_ERROR "cpp-httplib: git fetch failed")
endif()
execute_process(COMMAND git checkout --quiet
ec1b66e699557cd9d608d322c013a1ebda16bd08
WORKING_DIRECTORY "${MINIO_CPP_CURLPP_SRC}"
RESULT_VARIABLE _curlpp_checkout)
if (NOT _curlpp_checkout STREQUAL "0")
message(FATAL_ERROR "curlpp: git checkout of pinned commit failed")
${MINIO_CPP_HTTPLIB_PINNED_TAG}
WORKING_DIRECTORY "${MINIO_CPP_HTTPLIB_SRC}"
RESULT_VARIABLE _httplib_checkout)
if (NOT _httplib_checkout STREQUAL "0")
message(FATAL_ERROR "cpp-httplib: git checkout of pinned tag failed")
endif()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
set(CURLPP_BUILD_SHARED_LIBS OFF CACHE BOOL "Build curlpp shared library" FORCE)
add_subdirectory("${MINIO_CPP_CURLPP_SRC}"
"${CMAKE_CURRENT_BINARY_DIR}/_deps/curlpp-build")
set(MINIO_CPP_CURLPP_TARGET curlpp_static)
set_target_properties(curlpp_static PROPERTIES POSITION_INDEPENDENT_CODE ON)
# cpp-httplib is header-only; enable its CMake install target so that
# find_package(httplib) works for downstream consumers after install.
set(HTTPLIB_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory("${MINIO_CPP_HTTPLIB_SRC}"
"${CMAKE_CURRENT_BINARY_DIR}/_deps/cpp-httplib-build")
set(MINIO_CPP_HTTPLIB_TARGET httplib::httplib)
endif()
endif()

Expand Down Expand Up @@ -126,7 +144,7 @@ else()
endif()

set(MINIO_CPP_DEPS_LINK_LIBS
${MINIO_CPP_CURLPP_TARGET}
${MINIO_CPP_HTTPLIB_TARGET}
${MINIO_CPP_INIH_TARGET}
nlohmann_json::nlohmann_json
${MINIO_CPP_PUGIXML_TARGET}
Expand Down
3 changes: 2 additions & 1 deletion examples/GetBucketTags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ int main() {
if (resp) {
std::cout << "Bucket tags: " << std::endl;
for (auto& [key, value] : resp->tags) {
std::cout << "Key: " << key << ", " << "Value: " << value << std::endl;
std::cout << "Key: " << key << ", "
<< "Value: " << value << std::endl;
}
} else {
std::cout << "unable to get bucket tags; " << resp.error().String()
Expand Down
3 changes: 2 additions & 1 deletion examples/GetObjectTags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ int main() {
if (resp) {
std::cout << "Object tags: " << std::endl;
for (auto& [key, value] : resp->tags) {
std::cout << "Key: " << key << ", " << "Value: " << value << std::endl;
std::cout << "Key: " << key << ", "
<< "Value: " << value << std::endl;
}
} else {
std::cout << "unable to get object tags; " << resp.error().String()
Expand Down
5 changes: 5 additions & 0 deletions include/miniocpp/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
#include "response.h"
#include "result.h"

// windows.h maps GetObject to GetObjectA; keep the member function names.
#ifdef _WIN32
#undef GetObject
#endif

namespace minio::s3 {

class Client;
Expand Down
26 changes: 4 additions & 22 deletions include/miniocpp/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
#ifndef MINIO_CPP_HTTP_H_INCLUDED
#define MINIO_CPP_HTTP_H_INCLUDED

#include <curlpp/Easy.hpp>
#include <curlpp/Multi.hpp>
#include <exception>
#include <functional>
#include <iostream>
Expand Down Expand Up @@ -77,18 +75,15 @@ using ProgressFunction = std::function<bool(ProgressFunctionArgs)>;
struct Response;

struct DataFunctionArgs {
curlpp::Easy* handle = nullptr;
Response* response = nullptr;
std::string datachunk;
void* userdata = nullptr;

DataFunctionArgs() = default;
DataFunctionArgs(curlpp::Easy* handle, Response* response, void* userdata)
: handle(handle), response(response), userdata(userdata) {}
DataFunctionArgs(curlpp::Easy* handle, Response* response,
std::string datachunk, void* userdata)
: handle(handle),
response(response),
DataFunctionArgs(Response* response, void* userdata)
: response(response), userdata(userdata) {}
DataFunctionArgs(Response* response, std::string datachunk, void* userdata)
: response(response),
datachunk(std::move(datachunk)),
userdata(userdata) {}

Expand Down Expand Up @@ -164,24 +159,11 @@ struct Response {
Response() = default;
~Response() = default;

size_t ResponseCallback(curlpp::Multi* const requests,
curlpp::Easy* const request, const char* const buffer,
size_t size, size_t length);

explicit operator bool() const {
return error.empty() && status_code >= 200 && status_code <= 299;
}

error::Error Error() const;

private:
std::string response_;
bool continue100_ = false;
bool status_code_read_ = false;
bool headers_read_ = false;

error::Error ReadStatusCode();
error::Error ReadHeaders();
}; // struct Response

} // namespace minio::http
Expand Down
7 changes: 7 additions & 0 deletions include/miniocpp/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ std::string Join(const std::list<std::string>& values,
std::string Join(const std::vector<std::string>& values,
const std::string& delimiter);

// AWS SigV4 percent-encoding: RFC 3986 unreserved characters are kept,
// everything else is percent-encoded with uppercase hex.
std::string UriEncode(const std::string& value);

// UriDecode reverses percent-encoding; '+' is not treated as space.
std::string UriDecode(const std::string& value);

// EncodePath does URL encoding of path. It also normalizes multiple slashes.
std::string EncodePath(const std::string& path);

Expand Down
7 changes: 3 additions & 4 deletions src/args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

#include "miniocpp/args.h"

#include <curlpp/cURLpp.hpp>
#include <exception>
#include <filesystem>
#include <iostream>
Expand Down Expand Up @@ -62,7 +61,7 @@ utils::Multimap ObjectWriteArgs::Headers() const {

std::string tagging;
for (auto& [key, value] : tags) {
std::string tag = curlpp::escape(key) + "=" + curlpp::escape(value);
std::string tag = utils::UriEncode(key) + "=" + utils::UriEncode(value);
if (!tagging.empty()) {
tagging += "&";
}
Expand Down Expand Up @@ -126,9 +125,9 @@ utils::Multimap ObjectConditionalReadArgs::Headers() const {
utils::Multimap ObjectConditionalReadArgs::CopyHeaders() const {
utils::Multimap result_headers;

std::string copy_source = curlpp::escape("/" + bucket + "/" + object);
std::string copy_source = utils::UriEncode("/" + bucket + "/" + object);
if (!version_id.empty()) {
copy_source += "?versionId=" + curlpp::escape(version_id);
copy_source += "?versionId=" + utils::UriEncode(version_id);
}

result_headers.Add("x-amz-copy-source", copy_source);
Expand Down
31 changes: 20 additions & 11 deletions src/baseclient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,11 @@ Result<CompleteMultipartUploadResponse> BaseClient::CompleteMultipartUpload(
std::stringstream ss;
ss << "<CompleteMultipartUpload>";
for (auto& part : args.parts) {
ss << "<Part>" << "<PartNumber>" << part.number << "</PartNumber>"
<< "<ETag>" << "\"" << part.etag << "\"" << "</ETag>";
ss << "<Part>"
<< "<PartNumber>" << part.number << "</PartNumber>"
<< "<ETag>"
<< "\"" << part.etag << "\""
<< "</ETag>";
if (!part.checksum_crc64nvme.empty()) {
ss << "<ChecksumCRC64NVME>" << part.checksum_crc64nvme
<< "</ChecksumCRC64NVME>";
Expand Down Expand Up @@ -1416,8 +1419,9 @@ Result<MakeBucketResponse> BaseClient::MakeBucket(MakeBucketArgs args) {
std::string body;
if (region != "us-east-1") {
std::stringstream ss;
ss << "<CreateBucketConfiguration>" << "<LocationConstraint>" << region
<< "</LocationConstraint>" << "</CreateBucketConfiguration>";
ss << "<CreateBucketConfiguration>"
<< "<LocationConstraint>" << region << "</LocationConstraint>"
<< "</CreateBucketConfiguration>";
body = ss.str();
req.body = body;
}
Expand Down Expand Up @@ -1804,8 +1808,10 @@ Result<SetBucketTagsResponse> BaseClient::SetBucketTags(
if (!args.tags.empty()) {
ss << "<TagSet>";
for (auto& [key, value] : args.tags) {
ss << "<Tag>" << "<Key>" << key << "</Key>" << "<Value>" << value
<< "</Value>" << "</Tag>";
ss << "<Tag>"
<< "<Key>" << key << "</Key>"
<< "<Value>" << value << "</Value>"
<< "</Tag>";
}
ss << "</TagSet>";
}
Expand Down Expand Up @@ -1934,9 +1940,10 @@ Result<SetObjectRetentionResponse> BaseClient::SetObjectRetention(
}

std::stringstream ss;
ss << "<Retention>" << "<Mode>" << RetentionModeToString(args.retention_mode)
<< "</Mode>" << "<RetainUntilDate>"
<< args.retain_until_date.ToISO8601UTC() << "</RetainUntilDate>"
ss << "<Retention>"
<< "<Mode>" << RetentionModeToString(args.retention_mode) << "</Mode>"
<< "<RetainUntilDate>" << args.retain_until_date.ToISO8601UTC()
<< "</RetainUntilDate>"
<< "</Retention>";

std::string body = ss.str();
Expand Down Expand Up @@ -1978,8 +1985,10 @@ Result<SetObjectTagsResponse> BaseClient::SetObjectTags(
if (!args.tags.empty()) {
ss << "<TagSet>";
for (auto& [key, value] : args.tags) {
ss << "<Tag>" << "<Key>" << key << "</Key>" << "<Value>" << value
<< "</Value>" << "</Tag>";
ss << "<Tag>"
<< "<Key>" << key << "</Key>"
<< "<Value>" << value << "</Value>"
<< "</Tag>";
}
ss << "</TagSet>";
}
Expand Down
Loading
Loading