userver: /data/code/userver/libraries/s3api/src/s3api/s3api_methods.cpp Source File
Loading...
Searching...
No Matches
s3api_methods.cpp
2
3#include <fmt/format.h>
4#include <pugixml.hpp>
5
6#include <unordered_map>
7
8#include <userver/http/common_headers.hpp>
9#include <userver/http/url.hpp>
10
11USERVER_NAMESPACE_BEGIN
12
13namespace s3api::api_methods {
14namespace {
15
16class StringWriter : public pugi::xml_writer {
17public:
18 explicit StringWriter(std::string& out)
19 : out_(out)
20 {}
21
22 void write(const void* data, size_t size) override { out_.append(static_cast<const char*>(data), size); }
23
24private:
25 std::string& out_;
26};
27
28namespace query_args {
29constexpr std::string_view kDelimeter = "delimiter";
30constexpr std::string_view kEncodingType = "encoding-type";
31constexpr std::string_view kMarker = "marker";
32constexpr std::string_view kMaxKeys = "max-keys";
33constexpr std::string_view kMaxParts = "max-parts";
34constexpr std::string_view kMaxUploads = "max-uploads";
35constexpr std::string_view kPartNumber = "partNumber";
36constexpr std::string_view kPartNumberMarker = "part-number-marker";
37constexpr std::string_view kPrefix = "prefix";
38constexpr std::string_view kUploadId = "uploadId";
39constexpr std::string_view kUploadIdMarker = "upload-id-marker";
40} // namespace query_args
41
42namespace content_types {
43constexpr std::string_view kDefault = "application/octet-stream";
44constexpr std::string_view kApplicationXml = "application/xml; charset=UTF-8";
45} // namespace content_types
46
47constexpr std::string_view kEncodingTypeValueUrl = "url";
48
49constexpr std::string_view kS3AwsXmlNamespace = "http://s3.amazonaws.com/doc/2006-03-01/";
50
51// see @ref https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html
52const unsigned kMaxUploadsListLimit = 1000u;
53
54} // namespace
55
56Request PutObject(
57 std::string_view bucket,
58 std::string_view path,
59 std::string data,
60 std::string_view content_type,
61 const std::optional<std::string_view>& content_disposition
62) {
63 Request req;
64 req.method = clients::http::HttpMethod::kPut;
65 req.bucket = bucket;
66 req.req = path;
67
68 req.headers[USERVER_NAMESPACE::http::headers::kContentLength] = std::to_string(data.size());
69 req.headers[USERVER_NAMESPACE::http::headers::kContentType] = content_type;
70
71 if (content_disposition.has_value()) {
72 req.headers[USERVER_NAMESPACE::http::headers::kContentDisposition] = *content_disposition;
73 }
74
75 req.body = std::move(data);
76 return req;
77}
78
79Request DeleteObject(std::string_view bucket, std::string_view path) {
80 Request req;
81 req.method = clients::http::HttpMethod::kDelete;
82 req.bucket = bucket;
83 req.req = path;
84 return req;
85}
86
87Request GetObject(std::string_view bucket, std::string_view path, std::optional<std::string_view> version) {
88 Request req;
89 req.method = clients::http::HttpMethod::kGet;
90 req.bucket = bucket;
91 req.req = path;
92
93 if (version) {
94 req.req += "?" + USERVER_NAMESPACE::http::MakeQuery({{"versionId", *version}});
95 }
96
97 return req;
98}
99
100Request GetObjectHead(std::string_view bucket, std::string_view path) {
101 Request req;
102 req.method = clients::http::HttpMethod::kHead;
103 req.bucket = bucket;
104 req.req = path;
105 return req;
106}
107
108void SetRange(Request& req, size_t begin, size_t end) {
109 req.headers
110 [USERVER_NAMESPACE::http::headers::kRange] = "bytes=" + std::to_string(begin) + '-' + std::to_string(end);
111}
112
113void SetRange(Request& req, std::string_view range) { req.headers[USERVER_NAMESPACE::http::headers::kRange] = range; }
114
115Request GetBuckets() { return Request{{}, "", "", "", clients::http::HttpMethod::kGet}; }
116
117Request ListBucketContents(
118 std::string_view bucket,
119 std::string_view path,
120 int max_keys,
121 std::string_view marker,
122 std::string_view delimiter
123) {
124 Request req;
125 req.method = clients::http::HttpMethod::kGet;
126 req.bucket = bucket;
127
128 std::unordered_map<std::string, std::string> params;
129 params.emplace("prefix", path);
130 if (max_keys > 0) {
131 params.emplace(query_args::kMaxKeys, std::to_string(max_keys));
132 }
133
134 if (!marker.empty()) {
135 params.emplace(query_args::kMarker, marker);
136 }
137
138 if (!delimiter.empty()) {
139 params.emplace(query_args::kDelimeter, delimiter);
140 }
141
142 req.req = "?" + USERVER_NAMESPACE::http::MakeQuery(params);
143 return req;
144}
145Request CopyObject(
146 std::string_view source_bucket,
147 std::string_view source_key,
148 std::string_view dest_bucket,
149 std::string_view dest_key,
150 std::string_view content_type
151) {
152 Request req;
153 req.method = clients::http::HttpMethod::kPut;
154 req.bucket = dest_bucket;
155 req.req = dest_key;
156
157 req.headers[headers::kAmzCopySource] = fmt::format("/{}/{}", source_bucket, source_key);
158 req.headers[USERVER_NAMESPACE::http::headers::kContentType] = content_type;
159
160 return req;
161}
162
163Request CreateInternalApiRequest(
164 const std::string& bucket,
165 const multipart_upload::CreateMultipartUploadRequest& request
166) {
167 Request result;
168 result.method = clients::http::HttpMethod::kPost;
169 result.bucket = bucket;
170 result.req = fmt::format("{}?uploads", request.key);
171
172 if (request.content_type) {
173 result.headers[http::headers::kContentType] = *request.content_type;
174 }
175
176 if (request.content_disposition) {
177 result.headers[http::headers::kContentDisposition] = *request.content_disposition;
178 }
179
180 if (request.content_encoding) {
181 result.headers[http::headers::kContentEncoding] = *request.content_encoding;
182 }
183
184 if (request.content_language) {
185 result.headers[http::headers::kContentLanguage] = *request.content_language;
186 }
187
188 return result;
189}
190
191Request CreateInternalApiRequest(
192 const std::string& bucket,
193 const multipart_upload::AbortMultipartUploadRequest& request
194) {
195 Request result;
196 result.method = clients::http::HttpMethod::kDelete;
197 result.bucket = bucket;
198 result.req = fmt::format("{}?{}", request.key, http::MakeQuery({{query_args::kUploadId, request.upload_id}}));
199 return result;
200}
201
202Request CreateInternalApiRequest(
203 const std::string& bucket,
204 const multipart_upload::CompleteMultipartUploadRequest& request
205) {
206 Request result;
207 result.method = clients::http::HttpMethod::kPost;
208 result.bucket = bucket;
209 result.req = fmt::format("{}?{}", request.key, http::MakeQuery({{query_args::kUploadId, request.upload_id}}));
210
211 pugi::xml_document doc;
212 auto multipart_upload_node = doc.append_child("CompleteMultipartUpload");
213 multipart_upload_node.append_attribute("xmlns").set_value(kS3AwsXmlNamespace.data());
214 for (const auto& part : request.completed_parts) {
215 auto part_node = multipart_upload_node.append_child("Part");
216 part_node.append_child("PartNumber")
217 .append_child(pugi::node_pcdata)
218 .set_value(std::to_string(part.part_number).c_str());
219 part_node.append_child("ETag").append_child(pugi::node_pcdata).set_value(part.etag.c_str());
220 }
221 StringWriter writer{result.body};
222 doc.save(writer, "", pugi::format_raw);
223 result.headers = clients::http::Headers{
224 {http::headers::kContentType, content_types::kApplicationXml},
225 {http::headers::kContentLength, std::to_string(result.body.size())}
226 };
227
228 return result;
229}
230
231Request CreateInternalApiRequest(const std::string& bucket, const multipart_upload::UploadPartRequest& request) {
232 Request result;
233 result.method = clients::http::HttpMethod::kPut;
234 // `Content-Type`-header is absent in S3 API, however PUT-request implementaion in CURL for URIs with query
235 // may implicitly insert `application/x-www-form-urlencoded` which must be taken into account when making
236 // canonical request for signature. But it happens after we sign the request without knowlege of this header.
237 // We explicitly override this Content-Type value here to make this request be signed correctly.
238 result.headers = clients::http::Headers{
239 {http::headers::kContentLength, std::to_string(request.data.size())},
240 {http::headers::kContentType, std::string{content_types::kDefault}}
241 };
242
243 result.bucket = bucket;
244 result.req = fmt::format(
245 "{}?{}",
246 request.key,
248 {{query_args::kUploadId, request.upload_id}, {query_args::kPartNumber, std::to_string(request.part_number)}}
249 )
250 );
251 result.body = std::move(request.data);
252 return result;
253}
254
255Request CreateInternalApiRequest(const std::string& bucket, const multipart_upload::ListPartsRequest& request) {
256 Request result;
257 result.method = clients::http::HttpMethod::kGet;
258 result.bucket = bucket;
259 result.req = request.key;
260
261 http::Args params{{std::string{query_args::kUploadId}, request.upload_id}};
262
263 if (request.max_parts > 0 && request.max_parts < kMaxUploadsListLimit) {
264 params.emplace(query_args::kMaxParts, std::to_string(request.max_parts));
265 }
266
267 if (auto part_number_marker = request.part_number_marker.value_or(0u)) {
268 params.emplace(query_args::kPartNumberMarker, std::to_string(part_number_marker));
269 }
270
271 result.req = fmt::format("{}?{}", request.key, http::MakeQuery(params));
272
273 return result;
274}
275
276Request CreateInternalApiRequest(
277 const std::string& bucket,
278 const multipart_upload::ListMultipartUploadsRequest& request
279) {
280 using EncodingType = multipart_upload::ListMultipartUploadsRequest::EncodingType;
281
282 Request result;
283 result.method = clients::http::HttpMethod::kGet;
284 result.bucket = bucket;
285
286 http::Args params;
287 if (request.delimiter) {
288 params.emplace(query_args::kDelimeter, *request.delimiter);
289 }
290
291 if (request.prefix) {
292 params.emplace(query_args::kPrefix, *request.prefix);
293 }
294
295 if (request.max_uploads > 0 && request.max_uploads < kMaxUploadsListLimit) {
296 params.emplace(query_args::kMaxUploads, std::to_string(request.max_uploads));
297 }
298
299 if (request.key_marker) {
300 params.emplace(query_args::kMarker, *request.key_marker);
301 }
302
303 if (request.upload_id_marker) {
304 params.emplace(query_args::kUploadIdMarker, *request.upload_id_marker);
305 }
306
307 if (request.encoding_type == EncodingType::kUrl) {
308 params.emplace(query_args::kEncodingType, kEncodingTypeValueUrl);
309 }
310
311 result.req = "?uploads";
312 if (!params.empty()) {
313 result.req += "&" + http::MakeQuery(params);
314 }
315
316 return result;
317}
318
319} // namespace s3api::api_methods
320
321USERVER_NAMESPACE_END