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