Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudflare R2 Support #37

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
14 changes: 11 additions & 3 deletions include/amazon/AmazonClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
#include <vector>
#include "http/HttpClient.h"

struct CAmazonCredentials
struct CAmazonConfigs
{
enum S3PROVIDER
{
AWS_S3 = 0,
CF_R2 = 1
};

std::string accessKeyId;
std::string secretAccessKey;
std::string sessionToken;
std::string accountKeyId;
S3PROVIDER m_provider;

bool IsValid() const
{
Expand All @@ -19,7 +27,7 @@ struct CAmazonCredentials
class CAmazonClient
{
public:
CAmazonClient(std::string, CAmazonCredentials, std::string);
CAmazonClient(std::string, CAmazonConfigs, std::string);

protected:
struct Request
Expand All @@ -36,6 +44,6 @@ class CAmazonClient
Framework::Http::RequestResult ExecuteRequest(const Request&);

std::string m_service;
CAmazonCredentials m_credentials;
CAmazonConfigs m_configs;
std::string m_region;
};
5 changes: 4 additions & 1 deletion include/amazon/AmazonS3Client.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,14 @@ struct PutObjectRequest
class CAmazonS3Client : public CAmazonClient
{
public:
CAmazonS3Client(CAmazonCredentials, std::string = "us-east-1");
CAmazonS3Client(CAmazonConfigs, std::string = "us-east-1");

GetBucketLocationResult GetBucketLocation(const GetBucketLocationRequest&);
GetObjectResult GetObject(const GetObjectRequest&);
HeadObjectResult HeadObject(const HeadObjectRequest&);
ListObjectsResult ListObjects(std::string);
void PutObject(const PutObjectRequest&);

private:
Request CreateRequest(Framework::Http::HTTP_VERB, std::string, std::string, std::string = "");
};
16 changes: 8 additions & 8 deletions src/amazon/AmazonClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,17 @@ static std::string timeToString(const tm* timeInfo)
return std::string(output);
}

CAmazonClient::CAmazonClient(std::string service, CAmazonCredentials credentials, std::string region)
CAmazonClient::CAmazonClient(std::string service, CAmazonConfigs configs, std::string region)
: m_service(std::move(service))
, m_credentials(std::move(credentials))
, m_configs(std::move(configs))
, m_region(std::move(region))
{
}

Framework::Http::RequestResult CAmazonClient::ExecuteRequest(const Request& request)
{
assert(!m_credentials.accessKeyId.empty());
assert(!m_credentials.secretAccessKey.empty());
assert(!m_configs.accessKeyId.empty());
assert(!m_configs.secretAccessKey.empty());
assert(!request.host.empty());
assert(!request.urlHost.empty());

Expand All @@ -167,9 +167,9 @@ Framework::Http::RequestResult CAmazonClient::ExecuteRequest(const Request& requ
headers.insert(std::make_pair("Host", request.host));
headers.insert(std::make_pair("x-amz-content-sha256", contentHashString));
headers.insert(std::make_pair("x-amz-date", timestamp));
if(!m_credentials.sessionToken.empty())
if(!m_configs.sessionToken.empty())
{
headers.insert(std::make_pair("x-amz-security-token", m_credentials.sessionToken));
headers.insert(std::make_pair("x-amz-security-token", m_configs.sessionToken));
}

auto canonicalRequest = buildCanonicalRequest(request.method, request.uri, request.query, contentHashString, headers);
Expand All @@ -183,11 +183,11 @@ Framework::Http::RequestResult CAmazonClient::ExecuteRequest(const Request& requ
#endif

auto signedHeaders = buildSignedHeadersParam(headers);
auto signingKey = buildSigningKey(m_credentials.secretAccessKey, date, m_region, m_service, requestType);
auto signingKey = buildSigningKey(m_configs.secretAccessKey, date, m_region, m_service, requestType);
auto signature = hashToString(Framework::HashUtils::ComputeHmacSha256(signingKey.data(), signingKey.size(), stringToSign.c_str(), stringToSign.length()));

auto authorizationString = string_format("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
m_credentials.accessKeyId.c_str(), scope.c_str(), signedHeaders.c_str(), signature.c_str());
m_configs.accessKeyId.c_str(), scope.c_str(), signedHeaders.c_str(), signature.c_str());
headers.insert(std::make_pair("Authorization", authorizationString));
headers.insert(request.headers.begin(), request.headers.end());

Expand Down
89 changes: 58 additions & 31 deletions src/amazon/AmazonS3Client.cpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,74 @@
#include "amazon/AmazonS3Client.h"
#include <stdexcept>
#include <memory>
#include <cassert>
#include "string_format.h"
#include "xml/Parser.h"
#include "Url.h"

#define S3_HOSTNAME "s3.amazonaws.com"
CAmazonS3Client::CAmazonS3Client(CAmazonConfigs configs, std::string region)
: CAmazonClient("s3", std::move(configs), std::move(region))
{
}

CAmazonS3Client::CAmazonS3Client(CAmazonCredentials credentials, std::string region)
: CAmazonClient("s3", std::move(credentials), std::move(region))
CAmazonClient::Request CAmazonS3Client::CreateRequest(Framework::Http::HTTP_VERB method, std::string bucket, std::string region, std::string path)
{
Request rq;
rq.method = method;

// Path‐style bucket access
if(m_configs.m_provider == CAmazonConfigs::S3PROVIDER::CF_R2)
{
auto endpoint = "r2.cloudflarestorage.com";
rq.uri = string_format("/%s", bucket.c_str());
if(!path.empty())
{
rq.uri = string_format("%s/%s", rq.uri.c_str(), path.c_str());
}
rq.host = string_format("%s.%s", m_configs.accountKeyId.c_str(), endpoint);
rq.urlHost = rq.host;
return rq;
}

// Virtual hosted‐style bucket access
assert(m_configs.m_provider == CAmazonConfigs::S3PROVIDER::AWS_S3);

rq.uri = "/";
if(!path.empty())
{
rq.uri = string_format("/%s", path.c_str());
}

std::string endpoint = "amazonaws.com";
if(region.empty())
{
rq.host = string_format("%s.s3.%s", bucket.c_str(), endpoint.c_str());
rq.urlHost = string_format("s3.%s", endpoint.c_str());
}
else
{
rq.host = string_format("%s.s3-%s.%s", bucket.c_str(), region.c_str(), endpoint.c_str());
rq.urlHost = rq.host;
}
return rq;
}

GetBucketLocationResult CAmazonS3Client::GetBucketLocation(const GetBucketLocationRequest& request)
{
GetBucketLocationResult result;
// CF R2 does not support regions, thus doesnt support location call,
// but it seems region is needed for signature verification
if(m_configs.m_provider == CAmazonConfigs::CF_R2)
{
result.locationConstraint = m_region;
return result;
}

if(request.bucket.empty())
{
throw new std::runtime_error("Bucket name must be provided.");
}

Request rq;
rq.method = Framework::Http::HTTP_VERB::GET;
rq.host = string_format("%s." S3_HOSTNAME, request.bucket.c_str());
rq.urlHost = S3_HOSTNAME;
rq.uri = "/";
Request rq = CreateRequest(Framework::Http::HTTP_VERB::GET, request.bucket, "");
//We add a bucket parameter even if the S3 API doesn't use it to prevent caching
rq.query = string_format("bucket=%s&location=", request.bucket.c_str());

Expand All @@ -33,8 +78,6 @@ GetBucketLocationResult CAmazonS3Client::GetBucketLocation(const GetBucketLocati
throw std::runtime_error("Failed to get bucket location.");
}

GetBucketLocationResult result;

auto documentNode = std::unique_ptr<Framework::Xml::CNode>(Framework::Xml::CParser::ParseDocument(response.data));
auto locationConstraintNode = documentNode->Select("LocationConstraint");
if(locationConstraintNode)
Expand All @@ -48,11 +91,7 @@ GetBucketLocationResult CAmazonS3Client::GetBucketLocation(const GetBucketLocati

GetObjectResult CAmazonS3Client::GetObject(const GetObjectRequest& request)
{
Request rq;
rq.method = Framework::Http::HTTP_VERB::GET;
rq.uri = "/" + Framework::UrlEncode(request.key);
rq.host = string_format("%s.s3-%s.amazonaws.com", request.bucket.c_str(), m_region.c_str());
rq.urlHost = rq.host;
Request rq = CreateRequest(Framework::Http::HTTP_VERB::GET, request.bucket, m_region, Framework::UrlEncode(request.key));

if(request.range.first != request.range.second)
{
Expand All @@ -77,11 +116,7 @@ GetObjectResult CAmazonS3Client::GetObject(const GetObjectRequest& request)

HeadObjectResult CAmazonS3Client::HeadObject(const HeadObjectRequest& request)
{
Request rq;
rq.method = Framework::Http::HTTP_VERB::HEAD;
rq.uri = "/" + Framework::UrlEncode(request.key);
rq.host = string_format("%s.s3-%s.amazonaws.com", request.bucket.c_str(), m_region.c_str());
rq.urlHost = rq.host;
Request rq = CreateRequest(Framework::Http::HTTP_VERB::HEAD, request.bucket, m_region, Framework::UrlEncode(request.key));

auto response = ExecuteRequest(rq);
if(response.statusCode != Framework::Http::HTTP_STATUS_CODE::OK)
Expand Down Expand Up @@ -109,11 +144,7 @@ HeadObjectResult CAmazonS3Client::HeadObject(const HeadObjectRequest& request)

ListObjectsResult CAmazonS3Client::ListObjects(std::string bucket)
{
Request rq;
rq.method = Framework::Http::HTTP_VERB::GET;
rq.uri = "/";
rq.host = string_format("%s.s3-%s.amazonaws.com", bucket.c_str(), m_region.c_str());
rq.urlHost = rq.host;
Request rq = CreateRequest(Framework::Http::HTTP_VERB::GET, bucket, m_region);

auto response = ExecuteRequest(rq);
if(response.statusCode != Framework::Http::HTTP_STATUS_CODE::OK)
Expand All @@ -140,11 +171,7 @@ ListObjectsResult CAmazonS3Client::ListObjects(std::string bucket)

void CAmazonS3Client::PutObject(const PutObjectRequest& request)
{
Request rq;
rq.method = Framework::Http::HTTP_VERB::PUT;
rq.uri = "/" + Framework::UrlEncode(request.key);
rq.host = string_format("%s.s3-%s.amazonaws.com", request.bucket.c_str(), m_region.c_str());
rq.urlHost = rq.host;
Request rq = CreateRequest(Framework::Http::HTTP_VERB::PUT, request.bucket.c_str(), m_region, Framework::UrlEncode(request.key));
rq.content = request.data;

auto response = ExecuteRequest(rq);
Expand Down