1#include <userver/s3api/authenticators/utils.hpp>
10#include <boost/algorithm/string.hpp>
12#include <userver/clients/http/request.hpp>
13#include <userver/crypto/hash.hpp>
14#include <userver/utils/datetime_light.hpp>
15#include <userver/utils/str_icase.hpp>
17USERVER_NAMESPACE_BEGIN
21std::string HttpMethodToString(
const clients::http::
HttpMethod http_method) {
22 std::string http_method_string;
24 switch (http_method) {
26 http_method_string =
"DELETE";
29 http_method_string =
"GET";
32 http_method_string =
"HEAD";
35 http_method_string =
"POST";
38 http_method_string =
"PUT";
41 http_method_string =
"PATCH";
44 throw std::runtime_error(
"Unknown http method");
47 return http_method_string;
50std::string RemoveExcessiveSpaces(std::string value) {
51 std::ranges::replace(value,
'\n',
' ');
52 const auto garbage = std::ranges::unique(value, [](
char a,
char b) {
return a ==
' ' && b ==
' '; });
53 value.erase(garbage.begin(), garbage.end());
59std::string MakeHeaderContentMd5(
const std::string& data) {
63std::string MakeStringToSign(
65 const std::string& header_date,
66 const std::optional<std::string>& header_content_md5
68 std::ostringstream signature;
70 signature << HttpMethodToString(request.method) <<
'\n';
74 if (header_content_md5) {
75 signature << *header_content_md5;
83 static const std::string kContentType{
"Content-Type"};
85 const auto it = request.headers
.find(kContentType
);
87 if (it != request.headers
.cend()) {
88 signature << it->second;
95 signature << header_date <<
'\n';
99 std::vector<std::pair<std::string, std::string>> canonical_headers;
100 canonical_headers.reserve(request.headers
.size());
102 std::ranges::copy_if(request.headers, std::back_inserter(canonical_headers), [](
const auto& header) {
103 static constexpr std::string_view kAmzHeader =
"x-amz-";
104 auto header_start = std::string_view{header.first}.substr(0, kAmzHeader.size());
107 std::ranges::for_each(canonical_headers, [](
auto& header) { boost::to_lower(header.first); });
108 std::ranges::sort(canonical_headers, [](
const auto& header1,
const auto& header2) {
109 return header1.first < header2.first;
112 for (
const auto& [header, value] : canonical_headers) {
113 signature << header <<
':' << RemoveExcessiveSpaces(value) <<
'\n';
120 if (!request.bucket.empty()) {
121 signature <<
'/' + request.bucket;
124 auto actual_subresources = std::set<std::string>{
143 signature <<
'/' + request.req.substr(0, request.req.find(
'?'));
146 if (
auto pos = request.req.find(
'?'); pos != std::string::npos) {
147 std::vector<std::string> subresources_strings;
148 auto query = request.req.substr(pos + 1);
149 boost::split(subresources_strings, query, [](
char c) {
return c ==
'&'; });
151 std::map<std::string, std::optional<std::string>> subresources;
152 for (
auto&& subresource : subresources_strings) {
153 std::optional<std::string> parameter_value = std::nullopt;
155 if (
auto eq_pos = subresource.find(
'='); eq_pos != std::string::npos) {
156 parameter_value.emplace(subresource.substr(eq_pos + 1));
157 subresource.resize(eq_pos);
160 if (actual_subresources.count(subresource) != 0) {
161 subresources.emplace(std::move(subresource), std::move(parameter_value));
165 bool is_first =
true;
166 for (
auto& [subresource, value] : subresources) {
167 signature << (is_first ?
"?" :
"&");
170 signature << fmt::format(
"{}={}", subresource, *value);
172 signature << subresource;
178 return signature.str();
181std::string MakeSignature(
const std::string& string_to_sign,
const Secret& secret_key) {
185std::string MakeHeaderAuthorization(
186 const std::string& string_to_sign,
187 const std::string& access_key,
188 const Secret& secret_key
190 return "AWS " + access_key +
":" + MakeSignature(string_to_sign, secret_key);