6#include <unordered_map>
8#include <userver/http/common_headers.hpp>
9#include <userver/http/url.hpp>
10#include <userver/utils/string_literal.hpp>
12USERVER_NAMESPACE_BEGIN
14namespace s3api::api_methods {
17class StringWriter :
public pugi::xml_writer {
19 explicit StringWriter(std::string& out)
23 void write(
const void* data, size_t size)
override { out_.append(
static_cast<
const char*>(data), size); }
30constexpr std::string_view kDelimiter =
"delimiter";
31constexpr std::string_view kEncodingType =
"encoding-type";
32constexpr std::string_view kMarker =
"marker";
33constexpr std::string_view kMaxKeys =
"max-keys";
34constexpr std::string_view kMaxParts =
"max-parts";
35constexpr std::string_view kMaxUploads =
"max-uploads";
36constexpr std::string_view kPartNumber =
"partNumber";
37constexpr std::string_view kPartNumberMarker =
"part-number-marker";
38constexpr std::string_view kPrefix =
"prefix";
39constexpr std::string_view kUploadId =
"uploadId";
40constexpr std::string_view kUploadIdMarker =
"upload-id-marker";
43namespace content_types {
44constexpr std::string_view kDefault =
"application/octet-stream";
45constexpr std::string_view kApplicationXml =
"application/xml; charset=UTF-8";
48constexpr std::string_view kEncodingTypeValueUrl =
"url";
50constexpr utils::
StringLiteral kS3AwsXmlNamespace =
"http://s3.amazonaws.com/doc/2006-03-01/";
53const unsigned kMaxUploadsListLimit = 1000u;
58 std::string_view bucket,
59 std::string_view path,
61 std::string_view content_type,
62 const std::optional<std::string_view>& content_disposition
69 req.headers
[USERVER_NAMESPACE::http::
headers::kContentLength
] = std::to_string(data.size());
70 req.headers
[USERVER_NAMESPACE::http::
headers::kContentType
] = content_type;
72 if (content_disposition.has_value()) {
73 req.headers
[USERVER_NAMESPACE::http::
headers::kContentDisposition
] = *content_disposition;
76 req.body = std::move(data);
80Request DeleteObject(std::string_view bucket, std::string_view path) {
82 req.method = clients::http::
HttpMethod::kDelete;
88Request GetObject(std::string_view bucket, std::string_view path, std::optional<std::string_view> version) {
95 req.req +=
"?" + USERVER_NAMESPACE
::http
::MakeQuery({{
"versionId", *version}}
);
101Request GetObjectHead(std::string_view bucket, std::string_view path) {
103 req.method = clients::http::
HttpMethod::kHead;
109void SetRange(
Request& req, size_t begin, size_t end) {
111 [USERVER_NAMESPACE::http::
headers::
kRange] =
"bytes=" + std::to_string(begin) +
'-' + std::to_string(end);
114void SetRange(
Request& req, std::string_view range) { req.headers
[USERVER_NAMESPACE::http::
headers::
kRange] = range; }
119 std::string_view bucket,
120 std::string_view path,
122 std::string_view marker,
123 std::string_view delimiter
129 std::unordered_map<std::string, std::string> params;
130 params.emplace(
"prefix", path);
132 params.emplace(query_args::kMaxKeys, std::to_string(max_keys));
135 if (!marker.empty()) {
136 params.emplace(query_args::kMarker, marker);
139 if (!delimiter.empty()) {
140 params.emplace(query_args::kDelimiter, delimiter);
147 std::string_view source_bucket,
148 std::string_view source_key,
149 std::string_view dest_bucket,
150 std::string_view dest_key,
151 std::string_view content_type
155 req.bucket = dest_bucket;
158 req.headers
[headers::kAmzCopySource
] = fmt::format(
"/{}/{}", source_bucket, http
::EncodeS3Key(source_key
));
159 req.headers
[USERVER_NAMESPACE::http::
headers::kContentType
] = content_type;
164Request CreateInternalApiRequest(
165 const std::string& bucket,
169 result.method = clients::http::
HttpMethod::kPost;
170 result.bucket = bucket;
171 result.req = fmt::format(
"{}?uploads", http
::EncodeS3Key(request.key
));
173 if (request.content_type) {
174 result.headers
[http::
headers::kContentType
] = *request.content_type;
177 if (request.content_disposition) {
178 result.headers
[http::
headers::kContentDisposition
] = *request.content_disposition;
181 if (request.content_encoding) {
182 result.headers
[http::
headers::kContentEncoding
] = *request.content_encoding;
185 if (request.content_language) {
186 result.headers
[http::
headers::kContentLanguage
] = *request.content_language;
192Request CreateInternalApiRequest(
193 const std::string& bucket,
197 result.method = clients::http::
HttpMethod::kDelete;
198 result.bucket = bucket;
199 result.req = fmt::format(
202 http
::MakeQuery({{query_args::kUploadId, request.upload_id}}
)
207Request CreateInternalApiRequest(
208 const std::string& bucket,
212 result.method = clients::http::
HttpMethod::kPost;
213 result.bucket = bucket;
214 result.req = fmt::format(
217 http
::MakeQuery({{query_args::kUploadId, request.upload_id}}
)
220 pugi::xml_document doc;
221 auto multipart_upload_node = doc.append_child(
"CompleteMultipartUpload");
222 multipart_upload_node.append_attribute(
"xmlns").set_value(kS3AwsXmlNamespace.c_str());
223 for (
const auto& part : request.completed_parts) {
224 auto part_node = multipart_upload_node.append_child(
"Part");
225 part_node.append_child(
"PartNumber")
226 .append_child(pugi::node_pcdata)
227 .set_value(std::to_string(part.part_number).c_str());
228 part_node.append_child(
"ETag").append_child(pugi::node_pcdata).set_value(part.etag.c_str());
230 StringWriter writer{result.body};
231 doc.save(writer,
"", pugi::format_raw);
232 result.headers
= clients
::http
::Headers{
233 {http::
headers::kContentType, content_types::kApplicationXml},
234 {http::
headers::kContentLength, std::to_string(result.body.size())}
242 result.method = clients::http::
HttpMethod::kPut;
247 result.headers
= clients
::http
::Headers{
248 {http::
headers::kContentLength, std::to_string(request.data.size())},
249 {http::
headers::kContentType, std::string{content_types::kDefault}}
252 result.bucket = bucket;
253 result.req = fmt::format(
257 {{query_args::kUploadId, request.upload_id}, {query_args::kPartNumber, std::to_string(request.part_number)}}
260 result.body = std::move(request.data);
264Request CreateInternalApiRequest(
const std::string& bucket,
const multipart_upload::
ListPartsRequest& request) {
266 result.method = clients::http::
HttpMethod::kGet;
267 result.bucket = bucket;
270 http::Args params{{std::string{query_args::kUploadId}, request.upload_id}};
272 if (request.max_parts > 0 && request.max_parts < kMaxUploadsListLimit) {
273 params.emplace(query_args::kMaxParts, std::to_string(request.max_parts));
276 if (
auto part_number_marker = request.part_number_marker.value_or(0u)) {
277 params.emplace(query_args::kPartNumberMarker, std::to_string(part_number_marker));
285Request CreateInternalApiRequest(
286 const std::string& bucket,
292 result.method = clients::http::
HttpMethod::kGet;
293 result.bucket = bucket;
296 if (request.delimiter) {
297 params.emplace(query_args::kDelimiter, *request.delimiter);
300 if (request.prefix) {
301 params.emplace(query_args::kPrefix, *request.prefix);
304 if (request.max_uploads > 0 && request.max_uploads < kMaxUploadsListLimit) {
305 params.emplace(query_args::kMaxUploads, std::to_string(request.max_uploads));
308 if (request.key_marker) {
309 params.emplace(query_args::kMarker, *request.key_marker);
312 if (request.upload_id_marker) {
313 params.emplace(query_args::kUploadIdMarker, *request.upload_id_marker);
316 if (request.encoding_type == EncodingType::kUrl) {
317 params.emplace(query_args::kEncodingType, kEncodingTypeValueUrl);
320 result.req =
"?uploads";
321 if (!params.empty()) {