Skip to content

Instantly share code, notes, and snippets.

@hobu
Created December 14, 2017 01:54
Show Gist options
  • Select an option

  • Save hobu/13717564ccdb144337ee0334947ce4ef to your computer and use it in GitHub Desktop.

Select an option

Save hobu/13717564ccdb144337ee0334947ce4ef to your computer and use it in GitHub Desktop.
diff --git a/vendor/arbiter/arbiter.cpp b/vendor/arbiter/arbiter.cpp
index 797358fdc..6cffbf9ec 100644
--- a/vendor/arbiter/arbiter.cpp
+++ b/vendor/arbiter/arbiter.cpp
@@ -86,13 +86,22 @@ namespace
if (!b.isNull())
{
- for (const auto& key : b.getMemberNames())
+ if (b.isObject())
{
- // If A doesn't have this key, then set it to B's value.
- // If A has the key but it's an object, then recursively merge.
- // Otherwise A already has a value here that we won't overwrite.
- if (!out.isMember(key)) out[key] = b[key];
- else if (out[key].isObject()) merge(out[key], b[key]);
+ for (const auto& key : b.getMemberNames())
+ {
+ // If A doesn't have this key, then set it to B's value.
+ // If A has the key but it's an object, then recursively
+ // merge.
+ // Otherwise A already has a value here that we won't
+ // overwrite.
+ if (!out.isMember(key)) out[key] = b[key];
+ else if (out[key].isObject()) merge(out[key], b[key]);
+ }
+ }
+ else
+ {
+ out = b;
}
}
@@ -142,24 +151,19 @@ Arbiter::Arbiter(const Json::Value& in)
auto https(Https::create(*m_pool, json["http"]));
if (https) m_drivers[https->type()] = std::move(https);
- if (json["s3"].isArray())
- {
- for (const auto& sub : json["s3"])
- {
- auto s3(S3::create(*m_pool, sub));
- m_drivers[s3->type()] = std::move(s3);
- }
- }
- else
- {
- auto s3(S3::create(*m_pool, json["s3"]));
- if (s3) m_drivers[s3->type()] = std::move(s3);
- }
+ auto s3(S3::create(*m_pool, json["s3"]));
+ for (auto& s : s3) m_drivers[s->type()] = std::move(s);
// Credential-based drivers should probably all do something similar to the
// S3 driver to support multiple profiles.
auto dropbox(Dropbox::create(*m_pool, json["dropbox"]));
if (dropbox) m_drivers[dropbox->type()] = std::move(dropbox);
+
+#ifdef ARBITER_OPENSSL
+ auto google(Google::create(*m_pool, json["gs"]));
+ if (google) m_drivers[google->type()] = std::move(google);
+#endif
+
#endif
}
@@ -308,7 +312,7 @@ void Arbiter::copy(
{
std::cout <<
++i << " / " << paths.size() << ": " <<
- path << " -> " << dstEndpoint.fullPath(subpath) <<
+ path << " -> " << dstEndpoint.prefixedFullPath(subpath) <<
std::endl;
}
@@ -775,6 +779,41 @@ void Endpoint::put(
getHttpDriver().put(path, data, headers, query);
}
+http::Response Endpoint::httpGet(
+ std::string path,
+ http::Headers headers,
+ http::Query query,
+ const std::size_t reserve) const
+{
+ return getHttpDriver().internalGet(fullPath(path), headers, query, reserve);
+}
+
+http::Response Endpoint::httpPut(
+ std::string path,
+ const std::vector<char>& data,
+ http::Headers headers,
+ http::Query query) const
+{
+ return getHttpDriver().internalPut(fullPath(path), data, headers, query);
+}
+
+http::Response Endpoint::httpHead(
+ std::string path,
+ http::Headers headers,
+ http::Query query) const
+{
+ return getHttpDriver().internalHead(fullPath(path), headers, query);
+}
+
+http::Response Endpoint::httpPost(
+ std::string path,
+ const std::vector<char>& data,
+ http::Headers headers,
+ http::Query query) const
+{
+ return getHttpDriver().internalPost(fullPath(path), data, headers, query);
+}
+
std::string Endpoint::fullPath(const std::string& subpath) const
{
return m_root + subpath;
@@ -1347,13 +1386,38 @@ void Http::put(
}
}
+void Http::post(
+ const std::string path,
+ const std::string& data,
+ const Headers h,
+ const Query q) const
+{
+ return post(path, std::vector<char>(data.begin(), data.end()), h, q);
+}
+
+void Http::post(
+ const std::string path,
+ const std::vector<char>& data,
+ const Headers headers,
+ const Query query) const
+{
+ auto http(m_pool.acquire());
+ auto res(http.post(typedPath(path), data, headers, query));
+
+ if (!res.ok())
+ {
+ std::cout << res.str() << std::endl;
+ throw ArbiterError("Couldn't HTTP POST to " + path);
+ }
+}
+
Response Http::internalGet(
const std::string path,
const Headers headers,
const Query query,
const std::size_t reserve) const
{
- return m_pool.acquire().get(path, headers, query, reserve);
+ return m_pool.acquire().get(typedPath(path), headers, query, reserve);
}
Response Http::internalPut(
@@ -1362,7 +1426,7 @@ Response Http::internalPut(
const Headers headers,
const Query query) const
{
- return m_pool.acquire().put(path, data, headers, query);
+ return m_pool.acquire().put(typedPath(path), data, headers, query);
}
Response Http::internalHead(
@@ -1370,16 +1434,26 @@ Response Http::internalHead(
const Headers headers,
const Query query) const
{
- return m_pool.acquire().head(path, headers, query);
+ return m_pool.acquire().head(typedPath(path), headers, query);
}
Response Http::internalPost(
const std::string path,
const std::vector<char>& data,
- const Headers headers,
+ Headers headers,
const Query query) const
{
- return m_pool.acquire().post(path, data, headers, query);
+ if (!headers.count("Content-Length"))
+ {
+ headers["Content-Length"] = std::to_string(data.size());
+ }
+ return m_pool.acquire().post(typedPath(path), data, headers, query);
+}
+
+std::string Http::typedPath(const std::string& p) const
+{
+ if (Arbiter::getType(p) != "file") return p;
+ else return type() + "://" + p;
}
} // namespace drivers
@@ -1405,7 +1479,6 @@ Response Http::internalPost(
// //////////////////////////////////////////////////////////////////////
#ifndef ARBITER_IS_AMALGAMATION
-#include <arbiter/arbiter.hpp>
#include <arbiter/drivers/s3.hpp>
#endif
@@ -1449,7 +1522,7 @@ namespace
// See:
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
- const std::string credIp("http://169.254.169.254/");
+ const std::string credIp("169.254.169.254/");
const std::string credBase(
credIp + "latest/meta-data/iam/security-credentials/");
@@ -1516,30 +1589,51 @@ S3::S3(
, m_config(std::move(config))
{ }
-std::unique_ptr<S3> S3::create(Pool& pool, const Json::Value& json)
+std::vector<std::unique_ptr<S3>> S3::create(Pool& pool, const Json::Value& json)
+{
+ std::vector<std::unique_ptr<S3>> result;
+
+ if (json.isArray())
+ {
+ for (const auto& curr : json)
+ {
+ if (auto s = createOne(pool, curr))
+ {
+ result.push_back(std::move(s));
+ }
+ }
+ }
+ else if (auto s = createOne(pool, json))
+ {
+ result.push_back(std::move(s));
+ }
+
+ return result;
+}
+
+std::unique_ptr<S3> S3::createOne(Pool& pool, const Json::Value& json)
{
const std::string profile(extractProfile(json));
auto auth(Auth::create(json, profile));
if (!auth) return std::unique_ptr<S3>();
- auto config(Config::create(json, profile));
- if (!config) return std::unique_ptr<S3>();
-
+ std::unique_ptr<Config> config(new Config(json, profile));
return makeUnique<S3>(pool, profile, std::move(auth), std::move(config));
}
std::string S3::extractProfile(const Json::Value& json)
{
- if (auto p = util::env("AWS_PROFILE")) return *p;
- else if (auto p = util::env("AWS_DEFAULT_PROFILE")) return *p;
- else if (
+ if (
!json.isNull() &&
json.isMember("profile") &&
json["profile"].asString().size())
{
return json["profile"].asString();
}
+
+ if (auto p = util::env("AWS_PROFILE")) return *p;
+ if (auto p = util::env("AWS_DEFAULT_PROFILE")) return *p;
else return "default";
}
@@ -1547,38 +1641,41 @@ std::unique_ptr<S3::Auth> S3::Auth::create(
const Json::Value& json,
const std::string profile)
{
- // Try environment settings first.
+ // Try explicit JSON configuration first.
+ if (
+ !json.isNull() &&
+ json.isMember("access") &&
+ (json.isMember("secret") || json.isMember("hidden")))
+ {
+ return makeUnique<Auth>(
+ json["access"].asString(),
+ json.isMember("secret") ?
+ json["secret"].asString() :
+ json["hidden"].asString(),
+ json["token"].asString());
+ }
+
+ // Try environment settings next.
{
auto access(util::env("AWS_ACCESS_KEY_ID"));
auto hidden(util::env("AWS_SECRET_ACCESS_KEY"));
+ auto token(util::env("AWS_SESSION_TOKEN"));
if (access && hidden)
{
- return makeUnique<Auth>(*access, *hidden);
+ return makeUnique<Auth>(*access, *hidden, token ? *token : "");
}
access = util::env("AMAZON_ACCESS_KEY_ID");
hidden = util::env("AMAZON_SECRET_ACCESS_KEY");
+ token = util::env("AMAZON_SESSION_TOKEN");
if (access && hidden)
{
- return makeUnique<Auth>(*access, *hidden);
+ return makeUnique<Auth>(*access, *hidden, token ? *token : "");
}
}
- // Try explicit JSON configuration next.
- if (
- !json.isNull() &&
- json.isMember("access") &&
- (json.isMember("secret") || json.isMember("hidden")))
- {
- return makeUnique<Auth>(
- json["access"].asString(),
- json.isMember("secret") ?
- json["secret"].asString() :
- json["hidden"].asString());
- }
-
const std::string credPath(
util::env("AWS_CREDENTIAL_FILE") ?
*util::env("AWS_CREDENTIAL_FILE") : "~/.aws/credentials");
@@ -1627,31 +1724,21 @@ std::unique_ptr<S3::Auth> S3::Auth::create(
}
S3::Config::Config(
- const std::string region,
- const std::string baseUrl,
- const bool sse,
- const bool precheck)
- : m_region(region)
- , m_baseUrl(baseUrl)
- , m_precheck(precheck)
-{
- if (sse)
- {
- // This could grow to support other SSE schemes, like KMS and customer-
- // supplied keys.
- m_baseHeaders["x-amz-server-side-encryption"] = "AES256";
- }
-}
-
-std::unique_ptr<S3::Config> S3::Config::create(
const Json::Value& json,
const std::string profile)
+ : m_region(extractRegion(json, profile))
+ , m_baseUrl(extractBaseUrl(json, m_region))
+ , m_precheck(json["precheck"].asBool())
{
- const auto region(extractRegion(json, profile));
- const auto baseUrl(extractBaseUrl(json, region));
- const bool sse(json["sse"].asBool());
- const bool precheck(json["precheck"].asBool());
- return makeUnique<Config>(region, baseUrl, sse, precheck);
+ if (json["sse"].asBool())
+ {
+ m_baseHeaders["x-amz-server-side-encryption"] = "AES256";
+ }
+
+ if (json["requesterPays"].asBool())
+ {
+ m_baseHeaders["x-amz-request-payer"] = "requester";
+ }
}
std::string S3::Config::extractRegion(
@@ -1769,7 +1856,10 @@ S3::AuthFields S3::Auth::fields() const
m_access = creds["AccessKeyId"].asString();
m_hidden = creds["SecretAccessKey"].asString();
m_token = creds["Token"].asString();
- m_expiration.reset(new Time(creds["Expiration"].asString(), arbiter::Time::iso8601));
+ m_expiration.reset(
+ new Time(
+ creds["Expiration"].asString(),
+ arbiter::Time::iso8601));
if (*m_expiration - now < reauthSeconds)
{
@@ -1806,7 +1896,8 @@ std::unique_ptr<std::size_t> S3::tryGetSize(std::string rawPath) const
Headers(),
empty);
- Response res(Http::internalHead(resource.url(), apiV4.headers()));
+ drivers::Http http(m_pool);
+ Response res(http.internalHead(resource.url(), apiV4.headers()));
if (res.ok() && res.headers().count("Content-Length"))
{
@@ -1820,9 +1911,12 @@ std::unique_ptr<std::size_t> S3::tryGetSize(std::string rawPath) const
bool S3::get(
const std::string rawPath,
std::vector<char>& data,
- const Headers headers,
+ const Headers userHeaders,
const Query query) const
{
+ Headers headers(m_config->baseHeaders());
+ headers.insert(userHeaders.begin(), userHeaders.end());
+
std::unique_ptr<std::size_t> size(
m_config->precheck() && !headers.count("Range") ?
tryGetSize(rawPath) : nullptr);
@@ -1837,8 +1931,9 @@ bool S3::get(
headers,
empty);
+ drivers::Http http(m_pool);
Response res(
- Http::internalGet(
+ http.internalGet(
resource.url(),
apiV4.headers(),
apiV4.query(),
@@ -1851,8 +1946,7 @@ bool S3::get(
}
else
{
- std::cout << std::string(res.data().data(), res.data().size()) <<
- std::endl;
+ std::cout << res.code() << ": " << res.str() << std::endl;
return false;
}
}
@@ -1877,8 +1971,9 @@ void S3::put(
headers,
data);
+ drivers::Http http(m_pool);
Response res(
- Http::internalPut(
+ http.internalPut(
resource.url(),
data,
apiV4.headers(),
@@ -1968,7 +2063,8 @@ std::vector<std::string> S3::glob(std::string path, bool verbose) const
// beyond the prefix if recursive is true.
if (recursive || !isSubdir)
{
- results.push_back("s3://" + bucket + "/" + key);
+ results.push_back(
+ type() + "://" + bucket + "/" + key);
}
if (more)
@@ -2147,8 +2243,8 @@ std::string S3::ApiV4::getAuthHeader(
"Signature=" + signature;
}
-S3::Resource::Resource(std::string baseUrl, std::string fullPath)
- : m_baseUrl(baseUrl)
+S3::Resource::Resource(std::string base, std::string fullPath)
+ : m_baseUrl(base)
, m_bucket()
, m_object()
, m_virtualHosted(true)
@@ -2159,15 +2255,29 @@ S3::Resource::Resource(std::string baseUrl, std::string fullPath)
m_bucket = fullPath.substr(0, split);
if (split != std::string::npos) m_object = fullPath.substr(split + 1);
- m_virtualHosted = m_bucket.find_first_of('.') == std::string::npos;
+ // Always use virtual-host style paths. We'll use HTTP for our back-end
+ // calls to allow this. If we were to use HTTPS on the back-end, then we
+ // would have to use non-virtual-hosted paths if the bucket name contained
+ // '.' characters.
+ //
+ // m_virtualHosted = m_bucket.find_first_of('.') == std::string::npos;
+}
+
+std::string S3::Resource::baseUrl() const
+{
+ return m_baseUrl;
+}
+
+std::string S3::Resource::bucket() const
+{
+ return m_virtualHosted ? m_bucket : "";
}
std::string S3::Resource::url() const
{
- // We can't use virtual-host style paths if the bucket contains dots.
if (m_virtualHosted)
{
- return "https://" + m_bucket + "." + m_baseUrl + m_object;
+ return "http://" + m_bucket + "." + m_baseUrl + m_object;
}
else
{
@@ -2212,6 +2322,389 @@ std::string S3::Resource::host() const
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: arbiter/drivers/google.cpp
+// //////////////////////////////////////////////////////////////////////
+
+#ifndef ARBITER_IS_AMALGAMATION
+#include <arbiter/drivers/google.hpp>
+#endif
+
+#include <vector>
+
+#ifdef ARBITER_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+#endif
+
+#ifndef ARBITER_IS_AMALGAMATION
+#include <arbiter/arbiter.hpp>
+#include <arbiter/drivers/fs.hpp>
+#include <arbiter/util/transforms.hpp>
+#endif
+
+#ifdef ARBITER_CUSTOM_NAMESPACE
+namespace ARBITER_CUSTOM_NAMESPACE
+{
+#endif
+
+namespace arbiter
+{
+
+namespace
+{
+ std::mutex sslMutex;
+
+ const std::string baseGoogleUrl("www.googleapis.com/storage/v1/");
+ const std::string uploadUrl("www.googleapis.com/upload/storage/v1/");
+ const http::Query altMediaQuery{ { "alt", "media" } };
+
+ class GResource
+ {
+ public:
+ GResource(std::string path)
+ {
+ const std::size_t split(path.find("/"));
+ m_bucket = path.substr(0, split) + "/";
+ if (split != std::string::npos) m_object = path.substr(split + 1);
+ }
+
+ const std::string& bucket() const { return m_bucket; }
+ const std::string& object() const { return m_object; }
+ std::string endpoint() const
+ {
+ // https://cloud.google.com/storage/docs/json_api/#encoding
+ static const std::string exclusions("!$&'()*+,;=:@");
+
+ // https://cloud.google.com/storage/docs/json_api/v1/
+ return
+ baseGoogleUrl + "b/" + bucket() +
+ "o/" + http::sanitize(object(), exclusions);
+ }
+
+ std::string uploadEndpoint() const
+ {
+ return uploadUrl + "b/" + bucket() + "o";
+ }
+
+ std::string listEndpoint() const
+ {
+ return baseGoogleUrl + "b/" + bucket() + "o";
+ }
+
+ private:
+ std::string m_bucket;
+ std::string m_object;
+
+ };
+} // unnamed namespace
+
+namespace drivers
+{
+
+Google::Google(http::Pool& pool, std::unique_ptr<Auth> auth)
+ : Https(pool)
+ , m_auth(std::move(auth))
+{ }
+
+std::unique_ptr<Google> Google::create(
+ http::Pool& pool,
+ const Json::Value& json)
+{
+ if (auto auth = Auth::create(json))
+ {
+ return util::makeUnique<Google>(pool, std::move(auth));
+ }
+
+ return std::unique_ptr<Google>();
+}
+
+std::unique_ptr<std::size_t> Google::tryGetSize(const std::string path) const
+{
+ http::Headers headers(m_auth->headers());
+ const GResource resource(path);
+
+ drivers::Https https(m_pool);
+ const auto res(
+ https.internalHead(resource.endpoint(), headers, altMediaQuery));
+
+ if (res.ok() && res.headers().count("Content-Length"))
+ {
+ const auto& s(res.headers().at("Content-Length"));
+ return util::makeUnique<std::size_t>(std::stoul(s));
+ }
+
+ return std::unique_ptr<std::size_t>();
+}
+
+bool Google::get(
+ const std::string path,
+ std::vector<char>& data,
+ const http::Headers userHeaders,
+ const http::Query query) const
+{
+ http::Headers headers(m_auth->headers());
+ headers.insert(userHeaders.begin(), userHeaders.end());
+ const GResource resource(path);
+
+ drivers::Https https(m_pool);
+ const auto res(
+ https.internalGet(resource.endpoint(), headers, altMediaQuery));
+
+ if (res.ok())
+ {
+ data = res.data();
+ return true;
+ }
+ else
+ {
+ std::cout <<
+ "Failed get - " << res.code() << ": " << res.str() << std::endl;
+ return false;
+ }
+}
+
+void Google::put(
+ const std::string path,
+ const std::vector<char>& data,
+ const http::Headers userHeaders,
+ const http::Query userQuery) const
+{
+ const GResource resource(path);
+ const std::string url(resource.uploadEndpoint());
+
+ http::Headers headers(m_auth->headers());
+ headers["Expect"] = "";
+ headers.insert(userHeaders.begin(), userHeaders.end());
+
+ http::Query query(userQuery);
+ query["uploadType"] = "media";
+ query["name"] = resource.object();
+
+ drivers::Https https(m_pool);
+ const auto res(https.internalPost(url, data, headers, query));
+}
+
+std::vector<std::string> Google::glob(std::string path, bool verbose) const
+{
+ std::vector<std::string> results;
+
+ path.pop_back();
+ const bool recursive(path.back() == '*');
+ if (recursive) path.pop_back();
+
+ const GResource resource(path);
+ const std::string url(resource.listEndpoint());
+ std::string pageToken;
+
+ drivers::Https https(m_pool);
+ http::Query query;
+
+ // When the delimiter is set to "/", then the response will contain a
+ // "prefixes" key in addition to the "items" key. The "prefixes" key will
+ // contain the directories found, which we will ignore.
+ if (!recursive) query["delimiter"] = "/";
+ if (resource.object().size()) query["prefix"] = resource.object();
+
+ do
+ {
+ if (pageToken.size()) query["pageToken"] = pageToken;
+
+ const auto res(https.internalGet(url, m_auth->headers(), query));
+
+ if (!res.ok())
+ {
+ throw ArbiterError(std::to_string(res.code()) + ": " + res.str());
+ }
+
+ const Json::Value json(util::parse(res.str()));
+ for (const auto& item : json["items"])
+ {
+ results.push_back(
+ type() + "://" +
+ resource.bucket() + item["name"].asString());
+ }
+
+ pageToken = json["nextPageToken"].asString();
+ } while (pageToken.size());
+
+ return results;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<Google::Auth> Google::Auth::create(const Json::Value& json)
+{
+ if (auto path = util::env("GOOGLE_APPLICATION_CREDENTIALS"))
+ {
+ if (const auto file = drivers::Fs().tryGet(*path))
+ {
+ return util::makeUnique<Auth>(util::parse(*file));
+ }
+ }
+ else if (json.isString())
+ {
+ const auto path = json.asString();
+ if (const auto file = drivers::Fs().tryGet(path))
+ {
+ return util::makeUnique<Auth>(util::parse(*file));
+ }
+ }
+ else if (json.isObject())
+ {
+ return util::makeUnique<Auth>(json);
+ }
+
+ return std::unique_ptr<Auth>();
+}
+
+Google::Auth::Auth(const Json::Value& creds)
+ : m_creds(creds)
+{
+ maybeRefresh();
+}
+
+http::Headers Google::Auth::headers() const
+{
+ std::lock_guard<std::mutex> lock(m_mutex);
+ maybeRefresh();
+ return m_headers;
+}
+
+void Google::Auth::maybeRefresh() const
+{
+ using namespace crypto;
+
+ const auto now(Time().asUnix());
+ if (m_expiration - now > 120) return; // Refresh when under 2 mins left.
+
+ // https://developers.google.com/identity/protocols/OAuth2ServiceAccount
+ Json::Value h;
+ h["alg"] = "RS256";
+ h["typ"] = "JWT";
+
+ Json::Value c;
+ c["iss"] = m_creds["client_email"].asString();
+ c["scope"] = "https://www.googleapis.com/auth/devstorage.read_write";
+ c["aud"] = "https://www.googleapis.com/oauth2/v4/token";
+ c["iat"] = Json::Int64(now);
+ c["exp"] = Json::Int64(now + 3600);
+
+ const std::string header(encodeBase64(util::toFastString(h)));
+ const std::string claims(encodeBase64(util::toFastString(c)));
+
+ const std::string key(m_creds["private_key"].asString());
+ const std::string signature(
+ http::sanitize(encodeBase64(sign(header + '.' + claims, key))));
+
+ const std::string assertion(header + '.' + claims + '.' + signature);
+ const std::string sbody =
+ "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&"
+ "assertion=" + assertion;
+ const std::vector<char> body(sbody.begin(), sbody.end());
+
+ const http::Headers headers { { "Expect", "" } };
+ const std::string tokenRequestUrl("www.googleapis.com/oauth2/v4/token");
+
+ http::Pool pool;
+ drivers::Https https(pool);
+ const auto res(https.internalPost(tokenRequestUrl, body, headers));
+
+ if (!res.ok()) throw ArbiterError("Failed to get token: " + res.str());
+
+ const Json::Value token(util::parse(res.str()));
+ m_headers["Authorization"] = "Bearer " + token["access_token"].asString();
+ m_expiration = now + token["expires_in"].asInt64();
+}
+
+std::string Google::Auth::sign(
+ const std::string data,
+ const std::string pkey) const
+{
+ std::string signature;
+
+#ifdef ARBITER_OPENSSL
+ std::lock_guard<std::mutex> lock(sslMutex);
+
+ auto loadKey([](std::string s, bool isPublic)->EVP_PKEY*
+ {
+ // BIO_new_mem_buf needs non-const char*, so use a vector.
+ std::vector<char> vec(s.data(), s.data() + s.size());
+
+ EVP_PKEY* key(nullptr);
+ if (BIO* bio = BIO_new_mem_buf(vec.data(), vec.size()))
+ {
+ if (isPublic)
+ {
+ key = PEM_read_bio_PUBKEY(bio, &key, nullptr, nullptr);
+ }
+ else
+ {
+ key = PEM_read_bio_PrivateKey(bio, &key, nullptr, nullptr);
+ }
+
+ BIO_free(bio);
+
+ if (!key)
+ {
+ std::vector<char> err(256, 0);
+ ERR_error_string(ERR_get_error(), err.data());
+ throw ArbiterError(
+ std::string("Could not load key: ") + err.data());
+ }
+ }
+
+ return key;
+ });
+
+
+ EVP_PKEY* key(loadKey(pkey, false));
+
+ EVP_MD_CTX ctx;
+ EVP_MD_CTX_init(&ctx);
+ EVP_DigestSignInit(&ctx, nullptr, EVP_sha256(), nullptr, key);
+
+ if (EVP_DigestSignUpdate(&ctx, data.data(), data.size()) == 1)
+ {
+ std::size_t size(0);
+ if (EVP_DigestSignFinal(&ctx, nullptr, &size) == 1)
+ {
+ std::vector<unsigned char> v(size, 0);
+ if (EVP_DigestSignFinal(&ctx, v.data(), &size) == 1)
+ {
+ signature.assign(reinterpret_cast<const char*>(v.data()), size);
+ }
+ }
+ }
+
+ EVP_MD_CTX_cleanup(&ctx);
+ if (signature.empty()) throw ArbiterError("Could not sign JWT");
+ return signature;
+#else
+ throw ArbiterError("Cannot use google driver without OpenSSL");
+#endif
+}
+
+} // namespace drivers
+} // namespace arbiter
+
+#ifdef ARBITER_CUSTOM_NAMESPACE
+}
+#endif
+
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: arbiter/drivers/google.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: arbiter/drivers/dropbox.cpp
// //////////////////////////////////////////////////////////////////////
@@ -2253,8 +2746,9 @@ namespace arbiter
namespace
{
- const std::string baseGetUrl("https://content.dropboxapi.com/");
- const std::string getUrl(baseGetUrl + "2/files/download");
+ const std::string baseDropboxUrl("https://content.dropboxapi.com/");
+ const std::string getUrl(baseDropboxUrl + "2/files/download");
+ const std::string putUrl(baseDropboxUrl + "2/files/upload");
const std::string listUrl("https://api.dropboxapi.com/2/files/list_folder");
const std::string metaUrl("https://api.dropboxapi.com/2/files/get_metadata");
@@ -2281,6 +2775,7 @@ namespace drivers
{
using namespace http;
+using namespace util;
Dropbox::Dropbox(Pool& pool, const Dropbox::Auth& auth)
: Http(pool)
@@ -2289,14 +2784,19 @@ Dropbox::Dropbox(Pool& pool, const Dropbox::Auth& auth)
std::unique_ptr<Dropbox> Dropbox::create(Pool& pool, const Json::Value& json)
{
- std::unique_ptr<Dropbox> dropbox;
-
- if (!json.isNull() && json.isMember("token"))
+ if (!json.isNull())
{
- dropbox.reset(new Dropbox(pool, Auth(json["token"].asString())));
+ if (json.isObject() && json.isMember("token"))
+ {
+ return makeUnique<Dropbox>(pool, Auth(json["token"].asString()));
+ }
+ else if (json.isString())
+ {
+ return makeUnique<Dropbox>(pool, Auth(json.asString()));
+ }
}
- return dropbox;
+ return std::unique_ptr<Dropbox>();
}
Headers Dropbox::httpGetHeaders() const
@@ -2432,10 +2932,23 @@ bool Dropbox::get(
void Dropbox::put(
const std::string rawPath,
const std::vector<char>& data,
- const Headers headers,
+ const Headers userHeaders,
const Query query) const
{
- throw ArbiterError("PUT not yet supported for " + type());
+ const std::string path(sanitize(rawPath));
+
+ Headers headers(httpGetHeaders());
+
+ Json::Value json;
+ json["path"] = std::string("/" + path);
+ headers["Dropbox-API-Arg"] = toSanitizedString(json);
+ headers["Content-Type"] = "application/octet-stream";
+
+ headers.insert(userHeaders.begin(), userHeaders.end());
+
+ const Response res(Http::internalPost(putUrl, data, headers, query));
+
+ if (!res.ok()) throw ArbiterError(res.str());
}
std::string Dropbox::continueFileInfo(std::string cursor) const
@@ -2464,19 +2977,22 @@ std::string Dropbox::continueFileInfo(std::string cursor) const
return std::string("");
}
-std::vector<std::string> Dropbox::glob(std::string rawPath, bool verbose) const
+std::vector<std::string> Dropbox::glob(std::string path, bool verbose) const
{
std::vector<std::string> results;
- const std::string path(sanitize(rawPath.substr(0, rawPath.size() - 2)));
+ path.pop_back();
+ const bool recursive(path.back() == '*');
+ if (recursive) path.pop_back();
+ if (path.back() == '/') path.pop_back();
- auto listPath = [this](std::string path)->std::string
+ auto listPath = [this, recursive](std::string path)->std::string
{
Headers headers(httpPostHeaders());
Json::Value request;
request["path"] = std::string("/" + path);
- request["recursive"] = false;
+ request["recursive"] = recursive;
request["include_media_info"] = false;
request["include_deleted"] = false;
@@ -2507,7 +3023,8 @@ std::vector<std::string> Dropbox::glob(std::string rawPath, bool verbose) const
bool more(false);
std::string cursor("");
- auto processPath = [verbose, &results, &more, &cursor](std::string data)
+ auto processPath =
+ [this, verbose, &results, &more, &cursor](std::string data)
{
if (data.empty()) return;
@@ -2540,7 +3057,7 @@ std::vector<std::string> Dropbox::glob(std::string rawPath, bool verbose) const
if (std::equal(tag.begin(), tag.end(), fileTag.begin(), ins))
{
// Results already begin with a slash.
- results.push_back("dropbox:/" + v["path_lower"].asString());
+ results.push_back(type() + ":/" + v["path_lower"].asString());
}
}
};
@@ -3816,7 +4333,7 @@ namespace
const std::string hexVals("0123456789abcdef");
} // unnamed namespace
-std::string encodeBase64(const std::vector<char>& data)
+std::string encodeBase64(const std::vector<char>& data, const bool pad)
{
std::vector<uint8_t> input;
for (std::size_t i(0); i < data.size(); ++i)
@@ -3825,17 +4342,18 @@ std::string encodeBase64(const std::vector<char>& data)
input.push_back(*reinterpret_cast<uint8_t*>(&c));
}
- std::size_t fullSteps(input.size() / 3);
+ const std::size_t fullSteps(data.size() / 3);
+ const std::size_t remainder(data.size() % 3);
+
while (input.size() % 3) input.push_back(0);
uint8_t* pos(input.data());
- uint8_t* end(input.data() + fullSteps * 3);
std::string output(fullSteps * 4, '_');
std::size_t outIndex(0);
const uint32_t mask(0x3F);
- while (pos != end)
+ for (std::size_t i(0); i < fullSteps; ++i)
{
uint32_t chunk((*pos) << 16 | *(pos + 1) << 8 | *(pos + 2));
@@ -3847,24 +4365,26 @@ std::string encodeBase64(const std::vector<char>& data)
pos += 3;
}
- if (end != input.data() + input.size())
+ if (remainder)
{
- const std::size_t num(pos - end == 1 ? 2 : 3);
uint32_t chunk(*(pos) << 16 | *(pos + 1) << 8 | *(pos + 2));
output.push_back(base64Vals[(chunk >> 18) & mask]);
output.push_back(base64Vals[(chunk >> 12) & mask]);
- if (num == 3) output.push_back(base64Vals[(chunk >> 6) & mask]);
- }
+ if (remainder == 2) output.push_back(base64Vals[(chunk >> 6) & mask]);
- while (output.size() % 4) output.push_back('=');
+ if (pad)
+ {
+ while (output.size() % 4) output.push_back('=');
+ }
+ }
return output;
}
-std::string encodeBase64(const std::string& input)
+std::string encodeBase64(const std::string& input, const bool pad)
{
- return encodeBase64(std::vector<char>(input.begin(), input.end()));
+ return encodeBase64(std::vector<char>(input.begin(), input.end()), pad);
}
std::string encodeAsHex(const std::vector<char>& input)
@@ -4017,6 +4537,12 @@ int64_t Time::operator-(const Time& other) const
return std::difftime(m_time, other.m_time);
}
+int64_t Time::asUnix() const
+{
+ static const Time epoch("1970-01-01T00:00:00Z");
+ return *this - epoch;
+}
+
} // namespace arbiter
#ifdef ARBITER_CUSTOM_NAMESPACE
diff --git a/vendor/arbiter/arbiter.hpp b/vendor/arbiter/arbiter.hpp
index a67730dae..a555b4371 100644
--- a/vendor/arbiter/arbiter.hpp
+++ b/vendor/arbiter/arbiter.hpp
@@ -1,7 +1,7 @@
/// Arbiter amalgamated header (https://github.com/connormanning/arbiter).
/// It is intended to be used with #include "arbiter.hpp"
-// Git SHA: 311b81e3dcb5e8c55b2148f7eba46a12cff66914
+// Git SHA: c3b439d09d272818e221d3c57c16de14b89a6512
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: LICENSE
@@ -153,6 +153,10 @@ public:
std::vector<char> data() const { return m_data; }
const Headers& headers() const { return m_headers; }
+ std::string str() const
+ {
+ return std::string(data().data(), data().size());
+ }
private:
int m_code;
@@ -523,6 +527,7 @@ public:
// Return value is in seconds.
int64_t operator-(const Time& other) const;
+ int64_t asUnix() const;
private:
std::time_t m_time;
@@ -803,6 +808,8 @@ class ARBITER_DLL Fs : public Driver
public:
Fs() { }
+ using Driver::get;
+
static std::unique_ptr<Fs> create(const Json::Value& json);
virtual std::string type() const override { return "file"; }
@@ -950,13 +957,14 @@ public:
http::Headers headers,
http::Query query) const;
-protected:
- /** HTTP-derived Drivers should override this version of GET to allow for
- * custom headers and query parameters.
- */
- virtual bool get(
+ void post(
std::string path,
- std::vector<char>& data,
+ const std::string& data,
+ http::Headers headers,
+ http::Query query) const;
+ void post(
+ std::string path,
+ const std::vector<char>& data,
http::Headers headers,
http::Query query) const;
@@ -986,6 +994,18 @@ protected:
http::Headers headers = http::Headers(),
http::Query query = http::Query()) const;
+protected:
+ /** HTTP-derived Drivers should override this version of GET to allow for
+ * custom headers and query parameters.
+ */
+ virtual bool get(
+ std::string path,
+ std::vector<char>& data,
+ http::Headers headers,
+ http::Query query) const;
+
+ http::Pool& m_pool;
+
private:
virtual bool get(
std::string path,
@@ -994,12 +1014,7 @@ private:
return get(path, data, http::Headers(), http::Query());
}
- std::string typedPath(const std::string& p) const
- {
- return type() + "://" + p;
- }
-
- http::Pool& m_pool;
+ std::string typedPath(const std::string& p) const;
};
/** @brief HTTPS driver. Identical to the HTTP driver except for its type
@@ -3850,8 +3865,8 @@ namespace arbiter
namespace crypto
{
-std::string encodeBase64(const std::vector<char>& data);
-std::string encodeBase64(const std::string& data);
+std::string encodeBase64(const std::vector<char>& data, bool pad = true);
+std::string encodeBase64(const std::string& data, bool pad = true);
std::string encodeAsHex(const std::vector<char>& data);
std::string encodeAsHex(const std::string& data);
@@ -3887,6 +3902,11 @@ std::string encodeAsHex(const std::string& data);
#ifndef ARBITER_IS_AMALGAMATION
#include <arbiter/util/exports.hpp>
+
+#ifndef ARBITER_EXTERNAL_JSON
+#include <arbiter/third/json/json.hpp>
+#endif
+
#endif
@@ -4056,6 +4076,26 @@ namespace util
if (t) return makeUnique<T>(*t);
else return std::unique_ptr<T>();
}
+
+ inline Json::Value parse(const std::string& s)
+ {
+ Json::Reader reader;
+ Json::Value json;
+ if (!reader.parse(s, json))
+ {
+ throw std::runtime_error(
+ "Parse failure: " + reader.getFormattedErrorMessages());
+ }
+ return json;
+ }
+
+ inline std::string toFastString(const Json::Value& json)
+ {
+ std::string s = Json::FastWriter().write(json);
+ s.pop_back(); // Strip trailing newline.
+ return s;
+ }
+
} // namespace util
} // namespace arbiter
@@ -4124,7 +4164,11 @@ public:
* `~/.aws/credentials` or the file at AWS_CREDENTIAL_FILE.
* - EC2 instance profile.
*/
- static std::unique_ptr<S3> create(
+ static std::vector<std::unique_ptr<S3>> create(
+ http::Pool& pool,
+ const Json::Value& json);
+
+ static std::unique_ptr<S3> createOne(
http::Pool& pool,
const Json::Value& json);
@@ -4189,9 +4233,10 @@ private:
class S3::Auth
{
public:
- Auth(std::string access, std::string hidden)
+ Auth(std::string access, std::string hidden, std::string token = "")
: m_access(access)
, m_hidden(hidden)
+ , m_token(token)
{ }
Auth(std::string iamRole)
@@ -4217,11 +4262,7 @@ private:
class S3::Config
{
public:
- Config(std::string region, std::string baseUrl, bool sse, bool precheck);
-
- static std::unique_ptr<Config> create(
- const Json::Value& json,
- std::string profile);
+ Config(const Json::Value& json, std::string profile);
const std::string& region() const { return m_region; }
const std::string& baseUrl() const { return m_baseUrl; }
@@ -4252,8 +4293,8 @@ public:
std::string url() const;
std::string host() const;
- std::string baseUrl() const { return m_baseUrl; }
- std::string bucket() const { return m_bucket; }
+ std::string baseUrl() const;
+ std::string bucket() const;
std::string object() const;
private:
@@ -4327,6 +4368,103 @@ private:
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: arbiter/drivers/google.hpp
+// //////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef ARBITER_IS_AMALGAMATION
+#include <arbiter/drivers/http.hpp>
+#endif
+
+#include <mutex>
+
+#ifdef ARBITER_CUSTOM_NAMESPACE
+namespace ARBITER_CUSTOM_NAMESPACE
+{
+#endif
+
+namespace arbiter
+{
+
+namespace drivers
+{
+
+class Google : public Https
+{
+ class Auth;
+public:
+ Google(http::Pool& pool, std::unique_ptr<Auth> auth);
+
+ static std::unique_ptr<Google> create(
+ http::Pool& pool,
+ const Json::Value& json);
+
+ // Overrides.
+ virtual std::string type() const override { return "gs"; } // Match gsutil.
+
+ virtual std::unique_ptr<std::size_t> tryGetSize(
+ std::string path) const override;
+
+ /** Inherited from Drivers::Http. */
+ virtual void put(
+ std::string path,
+ const std::vector<char>& data,
+ http::Headers headers,
+ http::Query query) const override;
+
+private:
+ /** Inherited from Drivers::Http. */
+ virtual bool get(
+ std::string path,
+ std::vector<char>& data,
+ http::Headers headers,
+ http::Query query) const override;
+
+ virtual std::vector<std::string> glob(
+ std::string path,
+ bool verbose) const override;
+
+ std::unique_ptr<Auth> m_auth;
+};
+
+class Google::Auth
+{
+public:
+ Auth(const Json::Value& creds);
+ static std::unique_ptr<Auth> create(const Json::Value& json);
+
+ http::Headers headers() const;
+
+private:
+ void maybeRefresh() const;
+ std::string sign(std::string data, std::string privateKey) const;
+
+ const Json::Value m_creds;
+ mutable int64_t m_expiration = 0; // Unix time.
+ mutable http::Headers m_headers;
+
+ mutable std::mutex m_mutex;
+};
+
+} // namespace drivers
+} // namespace arbiter
+
+#ifdef ARBITER_CUSTOM_NAMESPACE
+}
+#endif
+
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: arbiter/drivers/google.hpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: arbiter/drivers/dropbox.hpp
// //////////////////////////////////////////////////////////////////////
@@ -4652,6 +4790,29 @@ public:
/** Get a further nested subpath relative to this Endpoint's root. */
Endpoint getSubEndpoint(std::string subpath) const;
+ http::Response httpGet(
+ std::string path,
+ http::Headers headers = http::Headers(),
+ http::Query query = http::Query(),
+ std::size_t reserve = 0) const;
+
+ http::Response httpPut(
+ std::string path,
+ const std::vector<char>& data,
+ http::Headers headers = http::Headers(),
+ http::Query query = http::Query()) const;
+
+ http::Response httpHead(
+ std::string path,
+ http::Headers headers = http::Headers(),
+ http::Query query = http::Query()) const;
+
+ http::Response httpPost(
+ std::string path,
+ const std::vector<char>& data,
+ http::Headers headers = http::Headers(),
+ http::Query query = http::Query()) const;
+
private:
Endpoint(const Driver& driver, std::string root);
@@ -4696,15 +4857,17 @@ private:
#endif
#ifndef ARBITER_IS_AMALGAMATION
-#include <arbiter/util/exports.hpp>
-#include <arbiter/driver.hpp>
#include <arbiter/endpoint.hpp>
+#include <arbiter/driver.hpp>
+#include <arbiter/drivers/dropbox.hpp>
#include <arbiter/drivers/fs.hpp>
-#include <arbiter/drivers/test.hpp>
+#include <arbiter/drivers/google.hpp>
#include <arbiter/drivers/http.hpp>
#include <arbiter/drivers/s3.hpp>
-#include <arbiter/drivers/dropbox.hpp>
+#include <arbiter/drivers/test.hpp>
+#include <arbiter/util/exports.hpp>
#include <arbiter/util/types.hpp>
+#include <arbiter/util/util.hpp>
#ifndef ARBITER_EXTERNAL_JSON
#include <arbiter/third/json/json.hpp>
@@ -4849,8 +5012,8 @@ public:
/** Copy the single file @p file to the destination @p to. If @p to ends
* with a `/` or '\' character, then @p file will be copied into the
- * directory @p to with the basename of @p file. If @p does not end with a
- * slash character, then @p to will be interpreted as a file path.
+ * directory @p to with the basename of @p file. If @p to does not end
+ * with a slash character, then @p to will be interpreted as a file path.
*
* If @p to is a local filesystem path, then `fs::mkdirp` will be called
* prior to copying.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment