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