]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_rest_swift.cc
import ceph 15.2.14
[ceph.git] / ceph / src / rgw / rgw_rest_swift.cc
CommitLineData
7c673cae 1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
9f95a23c 2// vim: ts=8 sw=2 smarttab ft=cpp
7c673cae
FG
3
4#include <boost/algorithm/string/predicate.hpp>
3efd9988 5#include <boost/format.hpp>
7c673cae
FG
6#include <boost/optional.hpp>
7#include <boost/utility/in_place_factory.hpp>
8
11fdf7f2 9#include "include/ceph_assert.h"
7c673cae
FG
10#include "ceph_ver.h"
11
12#include "common/Formatter.h"
13#include "common/utf8.h"
14#include "common/ceph_json.h"
15
16#include "rgw_rest_swift.h"
17#include "rgw_acl_swift.h"
18#include "rgw_cors_swift.h"
19#include "rgw_formats.h"
20#include "rgw_client_io.h"
9f95a23c 21#include "rgw_compression.h"
7c673cae
FG
22
23#include "rgw_auth.h"
24#include "rgw_swift_auth.h"
25
26#include "rgw_request.h"
27#include "rgw_process.h"
28
11fdf7f2
TL
29#include "rgw_zone.h"
30
31#include "services/svc_zone.h"
32
7c673cae
FG
33#include <array>
34#include <sstream>
35#include <memory>
36
37#include <boost/utility/string_ref.hpp>
38
39#define dout_context g_ceph_context
40#define dout_subsys ceph_subsys_rgw
41
42int RGWListBuckets_ObjStore_SWIFT::get_params()
43{
44 prefix = s->info.args.get("prefix");
45 marker = s->info.args.get("marker");
46 end_marker = s->info.args.get("end_marker");
3efd9988 47 wants_reversed = s->info.args.exists("reverse");
7c673cae 48
3efd9988
FG
49 if (wants_reversed) {
50 std::swap(marker, end_marker);
51 }
52
53 std::string limit_str = s->info.args.get("limit");
7c673cae 54 if (!limit_str.empty()) {
3efd9988 55 std::string err;
7c673cae
FG
56 long l = strict_strtol(limit_str.c_str(), 10, &err);
57 if (!err.empty()) {
58 return -EINVAL;
59 }
60
61 if (l > (long)limit_max || l < 0) {
62 return -ERR_PRECONDITION_FAILED;
63 }
64
65 limit = (uint64_t)l;
66 }
67
224ce89b 68 if (s->cct->_conf->rgw_swift_need_stats) {
7c673cae
FG
69 bool stats, exists;
70 int r = s->info.args.get_bool("stats", &stats, &exists);
71
72 if (r < 0) {
73 return r;
74 }
75
76 if (exists) {
77 need_stats = stats;
78 }
224ce89b
WB
79 } else {
80 need_stats = false;
7c673cae
FG
81 }
82
83 return 0;
84}
85
86static void dump_account_metadata(struct req_state * const s,
3efd9988 87 const RGWUsageStats& global_stats,
11fdf7f2 88 const std::map<std::string, RGWUsageStats> &policies_stats,
7c673cae
FG
89 /* const */map<string, bufferlist>& attrs,
90 const RGWQuotaInfo& quota,
91 const RGWAccessControlPolicy_SWIFTAcct &policy)
92{
93 /* Adding X-Timestamp to keep align with Swift API */
94 dump_header(s, "X-Timestamp", ceph_clock_now());
95
3efd9988
FG
96 dump_header(s, "X-Account-Container-Count", global_stats.buckets_count);
97 dump_header(s, "X-Account-Object-Count", global_stats.objects_count);
98 dump_header(s, "X-Account-Bytes-Used", global_stats.bytes_used);
99 dump_header(s, "X-Account-Bytes-Used-Actual", global_stats.bytes_used_rounded);
100
101 for (const auto& kv : policies_stats) {
102 const auto& policy_name = camelcase_dash_http_attr(kv.first);
103 const auto& policy_stats = kv.second;
104
105 dump_header_infixed(s, "X-Account-Storage-Policy-", policy_name,
106 "-Container-Count", policy_stats.buckets_count);
107 dump_header_infixed(s, "X-Account-Storage-Policy-", policy_name,
108 "-Object-Count", policy_stats.objects_count);
109 dump_header_infixed(s, "X-Account-Storage-Policy-", policy_name,
110 "-Bytes-Used", policy_stats.bytes_used);
111 dump_header_infixed(s, "X-Account-Storage-Policy-", policy_name,
112 "-Bytes-Used-Actual", policy_stats.bytes_used_rounded);
113 }
7c673cae
FG
114
115 /* Dump TempURL-related stuff */
116 if (s->perm_mask == RGW_PERM_FULL_CONTROL) {
9f95a23c
TL
117 auto iter = s->user->get_info().temp_url_keys.find(0);
118 if (iter != std::end(s->user->get_info().temp_url_keys) && ! iter->second.empty()) {
7c673cae
FG
119 dump_header(s, "X-Account-Meta-Temp-Url-Key", iter->second);
120 }
121
9f95a23c
TL
122 iter = s->user->get_info().temp_url_keys.find(1);
123 if (iter != std::end(s->user->get_info().temp_url_keys) && ! iter->second.empty()) {
7c673cae
FG
124 dump_header(s, "X-Account-Meta-Temp-Url-Key-2", iter->second);
125 }
126 }
127
128 /* Dump quota headers. */
129 if (quota.enabled) {
130 if (quota.max_size >= 0) {
131 dump_header(s, "X-Account-Meta-Quota-Bytes", quota.max_size);
132 }
133
134 /* Limit on the number of objects in a given account is a RadosGW's
135 * extension. Swift's account quota WSGI filter doesn't support it. */
136 if (quota.max_objects >= 0) {
137 dump_header(s, "X-Account-Meta-Quota-Count", quota.max_objects);
138 }
139 }
140
141 /* Dump user-defined metadata items and generic attrs. */
142 const size_t PREFIX_LEN = sizeof(RGW_ATTR_META_PREFIX) - 1;
143 map<string, bufferlist>::iterator iter;
144 for (iter = attrs.lower_bound(RGW_ATTR_PREFIX); iter != attrs.end(); ++iter) {
145 const char *name = iter->first.c_str();
146 map<string, string>::const_iterator geniter = rgw_to_http_attrs.find(name);
147
148 if (geniter != rgw_to_http_attrs.end()) {
149 dump_header(s, geniter->second, iter->second);
150 } else if (strncmp(name, RGW_ATTR_META_PREFIX, PREFIX_LEN) == 0) {
151 dump_header_prefixed(s, "X-Account-Meta-",
152 camelcase_dash_http_attr(name + PREFIX_LEN),
153 iter->second);
154 }
155 }
156
157 /* Dump account ACLs */
224ce89b
WB
158 auto account_acls = policy.to_str();
159 if (account_acls) {
160 dump_header(s, "X-Account-Access-Control", std::move(*account_acls));
7c673cae
FG
161 }
162}
163
164void RGWListBuckets_ObjStore_SWIFT::send_response_begin(bool has_buckets)
165{
166 if (op_ret) {
167 set_req_state_err(s, op_ret);
168 } else if (!has_buckets && s->format == RGW_FORMAT_PLAIN) {
169 op_ret = STATUS_NO_CONTENT;
170 set_req_state_err(s, op_ret);
171 }
172
173 if (! s->cct->_conf->rgw_swift_enforce_content_length) {
174 /* Adding account stats in the header to keep align with Swift API */
175 dump_account_metadata(s,
3efd9988
FG
176 global_stats,
177 policies_stats,
7c673cae 178 attrs,
ec96510d 179 s->user->get_info().user_quota,
7c673cae
FG
180 static_cast<RGWAccessControlPolicy_SWIFTAcct&>(*s->user_acl));
181 dump_errno(s);
11fdf7f2 182 dump_header(s, "Accept-Ranges", "bytes");
7c673cae
FG
183 end_header(s, NULL, NULL, NO_CONTENT_LENGTH, true);
184 }
185
186 if (! op_ret) {
187 dump_start(s);
188 s->formatter->open_array_section_with_attrs("account",
9f95a23c 189 FormatterAttrs("name", s->user->get_display_name().c_str(), NULL));
7c673cae
FG
190
191 sent_data = true;
192 }
193}
194
9f95a23c 195void RGWListBuckets_ObjStore_SWIFT::handle_listing_chunk(rgw::sal::RGWBucketList&& buckets)
3efd9988
FG
196{
197 if (wants_reversed) {
198 /* Just store in the reversal buffer. Its content will be handled later,
199 * in send_response_end(). */
200 reverse_buffer.emplace(std::begin(reverse_buffer), std::move(buckets));
201 } else {
202 return send_response_data(buckets);
203 }
204}
205
9f95a23c 206void RGWListBuckets_ObjStore_SWIFT::send_response_data(rgw::sal::RGWBucketList& buckets)
7c673cae
FG
207{
208 if (! sent_data) {
209 return;
210 }
211
212 /* Take care of the prefix parameter of Swift API. There is no business
213 * in applying the filter earlier as we really need to go through all
214 * entries regardless of it (the headers like X-Account-Container-Count
215 * aren't affected by specifying prefix). */
9f95a23c 216 const std::map<std::string, rgw::sal::RGWBucket*>& m = buckets.get_buckets();
7c673cae
FG
217 for (auto iter = m.lower_bound(prefix);
218 iter != m.end() && boost::algorithm::starts_with(iter->first, prefix);
219 ++iter) {
9f95a23c 220 dump_bucket_entry(*iter->second);
3efd9988
FG
221 }
222}
7c673cae 223
9f95a23c 224void RGWListBuckets_ObjStore_SWIFT::dump_bucket_entry(const rgw::sal::RGWBucket& obj)
3efd9988
FG
225{
226 s->formatter->open_object_section("container");
9f95a23c 227 s->formatter->dump_string("name", obj.get_name());
3efd9988
FG
228
229 if (need_stats) {
9f95a23c
TL
230 s->formatter->dump_int("count", obj.get_count());
231 s->formatter->dump_int("bytes", obj.get_size());
3efd9988
FG
232 }
233
234 s->formatter->close_section();
235
236 if (! s->cct->_conf->rgw_swift_enforce_content_length) {
237 rgw_flush_formatter(s, s->formatter);
238 }
239}
240
9f95a23c 241void RGWListBuckets_ObjStore_SWIFT::send_response_data_reversed(rgw::sal::RGWBucketList& buckets)
3efd9988
FG
242{
243 if (! sent_data) {
244 return;
245 }
246
247 /* Take care of the prefix parameter of Swift API. There is no business
248 * in applying the filter earlier as we really need to go through all
249 * entries regardless of it (the headers like X-Account-Container-Count
250 * aren't affected by specifying prefix). */
9f95a23c 251 std::map<std::string, rgw::sal::RGWBucket*>& m = buckets.get_buckets();
3efd9988
FG
252
253 auto iter = m.rbegin();
254 for (/* initialized above */;
255 iter != m.rend() && !boost::algorithm::starts_with(iter->first, prefix);
256 ++iter) {
257 /* NOP */;
258 }
259
260 for (/* iter carried */;
261 iter != m.rend() && boost::algorithm::starts_with(iter->first, prefix);
262 ++iter) {
9f95a23c 263 dump_bucket_entry(*iter->second);
7c673cae
FG
264 }
265}
266
267void RGWListBuckets_ObjStore_SWIFT::send_response_end()
268{
3efd9988
FG
269 if (wants_reversed) {
270 for (auto& buckets : reverse_buffer) {
271 send_response_data_reversed(buckets);
272 }
273 }
274
7c673cae
FG
275 if (sent_data) {
276 s->formatter->close_section();
277 }
278
279 if (s->cct->_conf->rgw_swift_enforce_content_length) {
280 /* Adding account stats in the header to keep align with Swift API */
281 dump_account_metadata(s,
3efd9988
FG
282 global_stats,
283 policies_stats,
7c673cae 284 attrs,
ec96510d 285 s->user->get_info().user_quota,
7c673cae
FG
286 static_cast<RGWAccessControlPolicy_SWIFTAcct&>(*s->user_acl));
287 dump_errno(s);
3efd9988 288 end_header(s, nullptr, nullptr, s->formatter->get_len(), true);
7c673cae
FG
289 }
290
291 if (sent_data || s->cct->_conf->rgw_swift_enforce_content_length) {
292 rgw_flush_formatter_and_reset(s, s->formatter);
293 }
294}
295
296int RGWListBucket_ObjStore_SWIFT::get_params()
297{
298 prefix = s->info.args.get("prefix");
299 marker = s->info.args.get("marker");
300 end_marker = s->info.args.get("end_marker");
301 max_keys = s->info.args.get("limit");
1adf2230
AA
302
303 // non-standard
304 s->info.args.get_bool("allow_unordered", &allow_unordered, false);
305
306 delimiter = s->info.args.get("delimiter");
307
7c673cae
FG
308 op_ret = parse_max_keys();
309 if (op_ret < 0) {
310 return op_ret;
311 }
f64942e4
AA
312 // S3 behavior is to silently cap the max-keys.
313 // Swift behavior is to abort.
7c673cae
FG
314 if (max > default_max)
315 return -ERR_PRECONDITION_FAILED;
316
7c673cae
FG
317 string path_args;
318 if (s->info.args.exists("path")) { // should handle empty path
319 path_args = s->info.args.get("path");
320 if (!delimiter.empty() || !prefix.empty()) {
321 return -EINVAL;
322 }
323 prefix = path_args;
324 delimiter="/";
325
326 path = prefix;
327 if (path.size() && path[path.size() - 1] != '/')
328 path.append("/");
329
330 int len = prefix.size();
331 int delim_size = delimiter.size();
332
333 if (len >= delim_size) {
334 if (prefix.substr(len - delim_size).compare(delimiter) != 0)
335 prefix.append(delimiter);
336 }
337 }
338
339 return 0;
340}
341
342static void dump_container_metadata(struct req_state *,
9f95a23c 343 const rgw::sal::RGWBucket*,
7c673cae
FG
344 const RGWQuotaInfo&,
345 const RGWBucketWebsiteConf&);
346
347void RGWListBucket_ObjStore_SWIFT::send_response()
348{
349 vector<rgw_bucket_dir_entry>::iterator iter = objs.begin();
350 map<string, bool>::iterator pref_iter = common_prefixes.begin();
351
352 dump_start(s);
353 dump_container_metadata(s, bucket, bucket_quota,
354 s->bucket_info.website_conf);
355
1adf2230
AA
356 s->formatter->open_array_section_with_attrs("container",
357 FormatterAttrs("name",
358 s->bucket.name.c_str(),
359 NULL));
7c673cae
FG
360
361 while (iter != objs.end() || pref_iter != common_prefixes.end()) {
362 bool do_pref = false;
363 bool do_objs = false;
364 rgw_obj_key key;
365 if (iter != objs.end()) {
366 key = iter->key;
367 }
368 if (pref_iter == common_prefixes.end())
369 do_objs = true;
370 else if (iter == objs.end())
371 do_pref = true;
372 else if (!key.empty() && key.name.compare(pref_iter->first) == 0) {
373 do_objs = true;
374 ++pref_iter;
375 } else if (!key.empty() && key.name.compare(pref_iter->first) <= 0)
376 do_objs = true;
377 else
378 do_pref = true;
379
1adf2230 380 if (do_objs && (allow_unordered || marker.empty() || marker < key)) {
7c673cae
FG
381 if (key.name.compare(path) == 0)
382 goto next;
383
384 s->formatter->open_object_section("object");
385 s->formatter->dump_string("name", key.name);
386 s->formatter->dump_string("hash", iter->meta.etag);
387 s->formatter->dump_int("bytes", iter->meta.accounted_size);
388 if (!iter->meta.user_data.empty())
389 s->formatter->dump_string("user_custom_data", iter->meta.user_data);
390 string single_content_type = iter->meta.content_type;
391 if (iter->meta.content_type.size()) {
392 // content type might hold multiple values, just dump the last one
393 ssize_t pos = iter->meta.content_type.rfind(',');
394 if (pos > 0) {
395 ++pos;
396 while (single_content_type[pos] == ' ')
397 ++pos;
398 single_content_type = single_content_type.substr(pos);
399 }
400 s->formatter->dump_string("content_type", single_content_type);
401 }
402 dump_time(s, "last_modified", &iter->meta.mtime);
403 s->formatter->close_section();
404 }
405
406 if (do_pref && (marker.empty() || pref_iter->first.compare(marker.name) > 0)) {
407 const string& name = pref_iter->first;
408 if (name.compare(delimiter) == 0)
409 goto next;
410
411 s->formatter->open_object_section_with_attrs("subdir", FormatterAttrs("name", name.c_str(), NULL));
412
413 /* swift is a bit inconsistent here */
414 switch (s->format) {
415 case RGW_FORMAT_XML:
416 s->formatter->dump_string("name", name);
417 break;
418 default:
419 s->formatter->dump_string("subdir", name);
420 }
421 s->formatter->close_section();
422 }
423next:
424 if (do_objs)
425 ++iter;
426 else
427 ++pref_iter;
428 }
429
430 s->formatter->close_section();
431
432 int64_t content_len = 0;
433 if (! op_ret) {
434 content_len = s->formatter->get_len();
435 if (content_len == 0) {
436 op_ret = STATUS_NO_CONTENT;
437 }
438 } else if (op_ret > 0) {
439 op_ret = 0;
440 }
441
442 set_req_state_err(s, op_ret);
443 dump_errno(s);
444 end_header(s, this, NULL, content_len);
445 if (op_ret < 0) {
446 return;
447 }
448
449 rgw_flush_formatter_and_reset(s, s->formatter);
1adf2230 450} // RGWListBucket_ObjStore_SWIFT::send_response
7c673cae
FG
451
452static void dump_container_metadata(struct req_state *s,
9f95a23c 453 const rgw::sal::RGWBucket* bucket,
7c673cae
FG
454 const RGWQuotaInfo& quota,
455 const RGWBucketWebsiteConf& ws_conf)
456{
457 /* Adding X-Timestamp to keep align with Swift API */
458 dump_header(s, "X-Timestamp", utime_t(s->bucket_info.creation_time));
459
9f95a23c
TL
460 dump_header(s, "X-Container-Object-Count", bucket->get_count());
461 dump_header(s, "X-Container-Bytes-Used", bucket->get_size());
462 dump_header(s, "X-Container-Bytes-Used-Actual", bucket->get_size_rounded());
7c673cae
FG
463
464 if (s->object.empty()) {
c07f9fc5
FG
465 auto swift_policy = \
466 static_cast<RGWAccessControlPolicy_SWIFT*>(s->bucket_acl.get());
7c673cae
FG
467 std::string read_acl, write_acl;
468 swift_policy->to_str(read_acl, write_acl);
469
470 if (read_acl.size()) {
471 dump_header(s, "X-Container-Read", read_acl);
472 }
473 if (write_acl.size()) {
474 dump_header(s, "X-Container-Write", write_acl);
475 }
11fdf7f2
TL
476 if (!s->bucket_info.placement_rule.name.empty()) {
477 dump_header(s, "X-Storage-Policy", s->bucket_info.placement_rule.name);
7c673cae 478 }
11fdf7f2 479 dump_header(s, "X-Storage-Class", s->bucket_info.placement_rule.get_storage_class());
7c673cae
FG
480
481 /* Dump user-defined metadata items and generic attrs. */
482 const size_t PREFIX_LEN = sizeof(RGW_ATTR_META_PREFIX) - 1;
483 map<string, bufferlist>::iterator iter;
484 for (iter = s->bucket_attrs.lower_bound(RGW_ATTR_PREFIX);
485 iter != s->bucket_attrs.end();
486 ++iter) {
487 const char *name = iter->first.c_str();
488 map<string, string>::const_iterator geniter = rgw_to_http_attrs.find(name);
489
490 if (geniter != rgw_to_http_attrs.end()) {
491 dump_header(s, geniter->second, iter->second);
492 } else if (strncmp(name, RGW_ATTR_META_PREFIX, PREFIX_LEN) == 0) {
493 dump_header_prefixed(s, "X-Container-Meta-",
494 camelcase_dash_http_attr(name + PREFIX_LEN),
495 iter->second);
496 }
497 }
498 }
499
500 /* Dump container versioning info. */
501 if (! s->bucket_info.swift_ver_location.empty()) {
502 dump_header(s, "X-Versions-Location",
503 url_encode(s->bucket_info.swift_ver_location));
504 }
505
506 /* Dump quota headers. */
507 if (quota.enabled) {
508 if (quota.max_size >= 0) {
509 dump_header(s, "X-Container-Meta-Quota-Bytes", quota.max_size);
510 }
511
512 if (quota.max_objects >= 0) {
513 dump_header(s, "X-Container-Meta-Quota-Count", quota.max_objects);
514 }
515 }
516
517 /* Dump Static Website headers. */
518 if (! ws_conf.index_doc_suffix.empty()) {
519 dump_header(s, "X-Container-Meta-Web-Index", ws_conf.index_doc_suffix);
520 }
521
522 if (! ws_conf.error_doc.empty()) {
523 dump_header(s, "X-Container-Meta-Web-Error", ws_conf.error_doc);
524 }
525
526 if (! ws_conf.subdir_marker.empty()) {
527 dump_header(s, "X-Container-Meta-Web-Directory-Type",
528 ws_conf.subdir_marker);
529 }
530
531 if (! ws_conf.listing_css_doc.empty()) {
532 dump_header(s, "X-Container-Meta-Web-Listings-CSS",
533 ws_conf.listing_css_doc);
534 }
535
536 if (ws_conf.listing_enabled) {
537 dump_header(s, "X-Container-Meta-Web-Listings", "true");
538 }
11fdf7f2
TL
539
540 /* Dump bucket's modification time. Compliance with the Swift API really
541 * needs that. */
542 dump_last_modified(s, s->bucket_mtime);
7c673cae
FG
543}
544
545void RGWStatAccount_ObjStore_SWIFT::execute()
546{
547 RGWStatAccount_ObjStore::execute();
9f95a23c 548 op_ret = store->ctl()->user->get_attrs_by_uid(s->user->get_id(), &attrs, s->yield);
7c673cae
FG
549}
550
551void RGWStatAccount_ObjStore_SWIFT::send_response()
552{
553 if (op_ret >= 0) {
554 op_ret = STATUS_NO_CONTENT;
555 dump_account_metadata(s,
3efd9988
FG
556 global_stats,
557 policies_stats,
7c673cae 558 attrs,
ec96510d 559 s->user->get_info().user_quota,
7c673cae
FG
560 static_cast<RGWAccessControlPolicy_SWIFTAcct&>(*s->user_acl));
561 }
562
563 set_req_state_err(s, op_ret);
564 dump_errno(s);
565
566 end_header(s, NULL, NULL, 0, true);
567
568 dump_start(s);
569}
570
571void RGWStatBucket_ObjStore_SWIFT::send_response()
572{
573 if (op_ret >= 0) {
574 op_ret = STATUS_NO_CONTENT;
575 dump_container_metadata(s, bucket, bucket_quota,
576 s->bucket_info.website_conf);
577 }
578
579 set_req_state_err(s, op_ret);
580 dump_errno(s);
581
582 end_header(s, this, NULL, 0, true);
583 dump_start(s);
584}
585
586static int get_swift_container_settings(req_state * const s,
9f95a23c 587 rgw::sal::RGWRadosStore * const store,
7c673cae
FG
588 RGWAccessControlPolicy * const policy,
589 bool * const has_policy,
590 uint32_t * rw_mask,
591 RGWCORSConfiguration * const cors_config,
592 bool * const has_cors)
593{
28e407b8
AA
594 const char * const read_list = s->info.env->get("HTTP_X_CONTAINER_READ");
595 const char * const write_list = s->info.env->get("HTTP_X_CONTAINER_WRITE");
7c673cae
FG
596
597 *has_policy = false;
598
28e407b8 599 if (read_list || write_list) {
7c673cae 600 RGWAccessControlPolicy_SWIFT swift_policy(s->cct);
9f95a23c
TL
601 const auto r = swift_policy.create(store->ctl()->user,
602 s->user->get_id(),
603 s->user->get_display_name(),
7c673cae
FG
604 read_list,
605 write_list,
606 *rw_mask);
607 if (r < 0) {
608 return r;
609 }
610
611 *policy = swift_policy;
612 *has_policy = true;
613 }
614
615 *has_cors = false;
616
617 /*Check and update CORS configuration*/
618 const char *allow_origins = s->info.env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_ALLOW_ORIGIN");
619 const char *allow_headers = s->info.env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_ALLOW_HEADERS");
620 const char *expose_headers = s->info.env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_EXPOSE_HEADERS");
621 const char *max_age = s->info.env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_MAX_AGE");
622 if (allow_origins) {
623 RGWCORSConfiguration_SWIFT *swift_cors = new RGWCORSConfiguration_SWIFT;
624 int r = swift_cors->create_update(allow_origins, allow_headers, expose_headers, max_age);
625 if (r < 0) {
626 dout(0) << "Error creating/updating the cors configuration" << dendl;
627 delete swift_cors;
628 return r;
629 }
630 *has_cors = true;
631 *cors_config = *swift_cors;
632 cors_config->dump();
633 delete swift_cors;
634 }
635
636 return 0;
637}
638
639#define ACCT_REMOVE_ATTR_PREFIX "HTTP_X_REMOVE_ACCOUNT_META_"
640#define ACCT_PUT_ATTR_PREFIX "HTTP_X_ACCOUNT_META_"
641#define CONT_REMOVE_ATTR_PREFIX "HTTP_X_REMOVE_CONTAINER_META_"
642#define CONT_PUT_ATTR_PREFIX "HTTP_X_CONTAINER_META_"
643
644static void get_rmattrs_from_headers(const req_state * const s,
645 const char * const put_prefix,
646 const char * const del_prefix,
647 set<string>& rmattr_names)
648{
7c673cae
FG
649 const size_t put_prefix_len = strlen(put_prefix);
650 const size_t del_prefix_len = strlen(del_prefix);
651
31f18b77 652 for (const auto& kv : s->info.env->get_map()) {
7c673cae 653 size_t prefix_len = 0;
31f18b77 654 const char * const p = kv.first.c_str();
7c673cae
FG
655
656 if (strncasecmp(p, del_prefix, del_prefix_len) == 0) {
657 /* Explicitly requested removal. */
658 prefix_len = del_prefix_len;
659 } else if ((strncasecmp(p, put_prefix, put_prefix_len) == 0)
31f18b77 660 && kv.second.empty()) {
7c673cae
FG
661 /* Removal requested by putting an empty value. */
662 prefix_len = put_prefix_len;
663 }
664
665 if (prefix_len > 0) {
666 string name(RGW_ATTR_META_PREFIX);
667 name.append(lowercase_dash_http_attr(p + prefix_len));
668 rmattr_names.insert(name);
669 }
670 }
671}
672
673static int get_swift_versioning_settings(
674 req_state * const s,
675 boost::optional<std::string>& swift_ver_location)
676{
677 /* Removing the Swift's versions location has lower priority than setting
678 * a new one. That's the reason why we're handling it first. */
679 const std::string vlocdel =
680 s->info.env->get("HTTP_X_REMOVE_VERSIONS_LOCATION", "");
681 if (vlocdel.size()) {
682 swift_ver_location = boost::in_place(std::string());
683 }
684
685 if (s->info.env->exists("HTTP_X_VERSIONS_LOCATION")) {
686 /* If the Swift's versioning is globally disabled but someone wants to
687 * enable it for a given container, new version of Swift will generate
688 * the precondition failed error. */
689 if (! s->cct->_conf->rgw_swift_versioning_enabled) {
690 return -ERR_PRECONDITION_FAILED;
691 }
692
693 swift_ver_location = s->info.env->get("HTTP_X_VERSIONS_LOCATION", "");
694 }
695
696 return 0;
697}
698
699int RGWCreateBucket_ObjStore_SWIFT::get_params()
700{
701 bool has_policy;
702 uint32_t policy_rw_mask = 0;
703
704 int r = get_swift_container_settings(s, store, &policy, &has_policy,
705 &policy_rw_mask, &cors_config, &has_cors);
706 if (r < 0) {
707 return r;
708 }
709
710 if (!has_policy) {
9f95a23c 711 policy.create_default(s->user->get_id(), s->user->get_display_name());
7c673cae
FG
712 }
713
9f95a23c 714 location_constraint = store->svc()->zone->get_zonegroup().api_name;
7c673cae
FG
715 get_rmattrs_from_headers(s, CONT_PUT_ATTR_PREFIX,
716 CONT_REMOVE_ATTR_PREFIX, rmattr_names);
11fdf7f2 717 placement_rule.init(s->info.env->get("HTTP_X_STORAGE_POLICY", ""), s->info.storage_class);
7c673cae
FG
718
719 return get_swift_versioning_settings(s, swift_ver_location);
720}
721
3efd9988
FG
722static inline int handle_metadata_errors(req_state* const s, const int op_ret)
723{
724 if (op_ret == -EFBIG) {
725 /* Handle the custom error message of exceeding maximum custom attribute
726 * (stored as xattr) size. */
727 const auto error_message = boost::str(
728 boost::format("Metadata value longer than %lld")
11fdf7f2 729 % s->cct->_conf.get_val<Option::size_t>("rgw_max_attr_size"));
3efd9988
FG
730 set_req_state_err(s, EINVAL, error_message);
731 return -EINVAL;
732 } else if (op_ret == -E2BIG) {
733 const auto error_message = boost::str(
734 boost::format("Too many metadata items; max %lld")
11fdf7f2 735 % s->cct->_conf.get_val<uint64_t>("rgw_max_attrs_num_in_req"));
3efd9988
FG
736 set_req_state_err(s, EINVAL, error_message);
737 return -EINVAL;
738 }
739
740 return op_ret;
741}
742
7c673cae
FG
743void RGWCreateBucket_ObjStore_SWIFT::send_response()
744{
3efd9988
FG
745 const auto meta_ret = handle_metadata_errors(s, op_ret);
746 if (meta_ret != op_ret) {
747 op_ret = meta_ret;
748 } else {
749 if (!op_ret) {
750 op_ret = STATUS_CREATED;
751 } else if (op_ret == -ERR_BUCKET_EXISTS) {
752 op_ret = STATUS_ACCEPTED;
753 }
754 set_req_state_err(s, op_ret);
755 }
756
7c673cae
FG
757 dump_errno(s);
758 /* Propose ending HTTP header with 0 Content-Length header. */
759 end_header(s, NULL, NULL, 0);
760 rgw_flush_formatter_and_reset(s, s->formatter);
761}
762
763void RGWDeleteBucket_ObjStore_SWIFT::send_response()
764{
765 int r = op_ret;
766 if (!r)
767 r = STATUS_NO_CONTENT;
768
769 set_req_state_err(s, r);
770 dump_errno(s);
771 end_header(s, this, NULL, 0);
772 rgw_flush_formatter_and_reset(s, s->formatter);
773}
774
775static int get_delete_at_param(req_state *s, boost::optional<real_time> &delete_at)
776{
777 /* Handle Swift object expiration. */
778 real_time delat_proposal;
779 string x_delete = s->info.env->get("HTTP_X_DELETE_AFTER", "");
780
781 if (x_delete.empty()) {
782 x_delete = s->info.env->get("HTTP_X_DELETE_AT", "");
783 } else {
784 /* X-Delete-After HTTP is present. It means we need add its value
785 * to the current time. */
786 delat_proposal = real_clock::now();
787 }
788
789 if (x_delete.empty()) {
790 delete_at = boost::none;
791 if (s->info.env->exists("HTTP_X_REMOVE_DELETE_AT")) {
792 delete_at = boost::in_place(real_time());
793 }
794 return 0;
795 }
796 string err;
797 long ts = strict_strtoll(x_delete.c_str(), 10, &err);
798
799 if (!err.empty()) {
800 return -EINVAL;
801 }
802
803 delat_proposal += make_timespan(ts);
804 if (delat_proposal < real_clock::now()) {
805 return -EINVAL;
806 }
807
808 delete_at = delat_proposal;
809
810 return 0;
811}
812
813int RGWPutObj_ObjStore_SWIFT::verify_permission()
814{
815 op_ret = RGWPutObj_ObjStore::verify_permission();
816
817 /* We have to differentiate error codes depending on whether user is
818 * anonymous (401 Unauthorized) or he doesn't have necessary permissions
819 * (403 Forbidden). */
820 if (s->auth.identity->is_anonymous() && op_ret == -EACCES) {
821 return -EPERM;
822 } else {
823 return op_ret;
824 }
825}
826
11fdf7f2
TL
827int RGWPutObj_ObjStore_SWIFT::update_slo_segment_size(rgw_slo_entry& entry) {
828
829 int r = 0;
830 const string& path = entry.path;
831
832 /* If the path starts with slashes, strip them all. */
833 const size_t pos_init = path.find_first_not_of('/');
834
835 if (pos_init == string::npos) {
836 return -EINVAL;
837 }
838
839 const size_t pos_sep = path.find('/', pos_init);
840 if (pos_sep == string::npos) {
841 return -EINVAL;
842 }
843
844 string bucket_name = path.substr(pos_init, pos_sep - pos_init);
845 string obj_name = path.substr(pos_sep + 1);
846
847 rgw_bucket bucket;
848
7f7e6c64
TL
849 RGWBucketInfo *pbucket_info = nullptr;
850 RGWBucketInfo bucket_info;
11fdf7f2 851 if (bucket_name.compare(s->bucket.name) != 0) {
11fdf7f2 852 map<string, bufferlist> bucket_attrs;
9f95a23c 853 r = store->getRados()->get_bucket_info(store->svc(), s->user->get_id().tenant,
11fdf7f2 854 bucket_name, bucket_info, nullptr,
9f95a23c 855 s->yield, &bucket_attrs);
11fdf7f2
TL
856 if (r < 0) {
857 ldpp_dout(this, 0) << "could not get bucket info for bucket="
858 << bucket_name << dendl;
859 return r;
860 }
861 bucket = bucket_info.bucket;
7f7e6c64 862 pbucket_info = &bucket_info;
11fdf7f2
TL
863 } else {
864 bucket = s->bucket;
7f7e6c64 865 pbucket_info = &s->bucket_info;
11fdf7f2
TL
866 }
867
868 /* fetch the stored size of the seg (or error if not valid) */
869 rgw_obj_key slo_key(obj_name);
870 rgw_obj slo_seg(bucket, slo_key);
871
872 /* no prefetch */
873 RGWObjectCtx obj_ctx(store);
874 obj_ctx.set_atomic(slo_seg);
875
7f7e6c64 876 RGWRados::Object op_target(store->getRados(), *pbucket_info, obj_ctx, slo_seg);
11fdf7f2
TL
877 RGWRados::Object::Read read_op(&op_target);
878
879 bool compressed;
880 RGWCompressionInfo cs_info;
881 map<std::string, buffer::list> attrs;
882 uint64_t size_bytes{0};
883
884 read_op.params.attrs = &attrs;
885 read_op.params.obj_size = &size_bytes;
886
9f95a23c 887 r = read_op.prepare(s->yield);
11fdf7f2
TL
888 if (r < 0) {
889 return r;
890 }
891
892 r = rgw_compression_info_from_attrset(attrs, compressed, cs_info);
893 if (r < 0) {
894 return -EIO;
895 }
896
897 if (compressed) {
898 size_bytes = cs_info.orig_size;
899 }
900
901 /* "When the PUT operation sees the multipart-manifest=put query
902 * parameter, it reads the request body and verifies that each
903 * segment object exists and that the sizes and ETags match. If
904 * there is a mismatch, the PUT operation fails."
905 */
906 if (entry.size_bytes &&
907 (entry.size_bytes != size_bytes)) {
908 return -EINVAL;
909 }
910
911 entry.size_bytes = size_bytes;
912
913 return 0;
914} /* RGWPutObj_ObjStore_SWIFT::update_slo_segment_sizes */
915
7c673cae
FG
916int RGWPutObj_ObjStore_SWIFT::get_params()
917{
918 if (s->has_bad_meta) {
919 return -EINVAL;
920 }
921
922 if (!s->length) {
923 const char *encoding = s->info.env->get("HTTP_TRANSFER_ENCODING");
924 if (!encoding || strcmp(encoding, "chunked") != 0) {
9f95a23c 925 ldpp_dout(this, 20) << "neither length nor chunked encoding" << dendl;
7c673cae
FG
926 return -ERR_LENGTH_REQUIRED;
927 }
928
929 chunked_upload = true;
930 }
931
932 supplied_etag = s->info.env->get("HTTP_ETAG");
933
934 if (!s->generic_attrs.count(RGW_ATTR_CONTENT_TYPE)) {
9f95a23c 935 ldpp_dout(this, 5) << "content type wasn't provided, trying to guess" << dendl;
7c673cae
FG
936 const char *suffix = strrchr(s->object.name.c_str(), '.');
937 if (suffix) {
938 suffix++;
939 if (*suffix) {
940 string suffix_str(suffix);
941 const char *mime = rgw_find_mime_by_ext(suffix_str);
942 if (mime) {
943 s->generic_attrs[RGW_ATTR_CONTENT_TYPE] = mime;
944 }
945 }
946 }
947 }
948
9f95a23c 949 policy.create_default(s->user->get_id(), s->user->get_display_name());
7c673cae
FG
950
951 int r = get_delete_at_param(s, delete_at);
952 if (r < 0) {
9f95a23c 953 ldpp_dout(this, 5) << "ERROR: failed to get Delete-At param" << dendl;
7c673cae
FG
954 return r;
955 }
956
957 if (!s->cct->_conf->rgw_swift_custom_header.empty()) {
958 string custom_header = s->cct->_conf->rgw_swift_custom_header;
959 if (s->info.env->exists(custom_header.c_str())) {
960 user_data = s->info.env->get(custom_header.c_str());
961 }
962 }
963
964 dlo_manifest = s->info.env->get("HTTP_X_OBJECT_MANIFEST");
965 bool exists;
966 string multipart_manifest = s->info.args.get("multipart-manifest", &exists);
967 if (exists) {
968 if (multipart_manifest != "put") {
9f95a23c 969 ldpp_dout(this, 5) << "invalid multipart-manifest http param: " << multipart_manifest << dendl;
7c673cae
FG
970 return -EINVAL;
971 }
972
973#define MAX_SLO_ENTRY_SIZE (1024 + 128) // 1024 - max obj name, 128 - enough extra for other info
974 uint64_t max_len = s->cct->_conf->rgw_max_slo_entries * MAX_SLO_ENTRY_SIZE;
975
976 slo_info = new RGWSLOInfo;
977
11fdf7f2
TL
978 int r = 0;
979 std::tie(r, slo_info->raw_data) = rgw_rest_get_json_input_keep_data(s->cct, s, slo_info->entries, max_len);
7c673cae 980 if (r < 0) {
9f95a23c 981 ldpp_dout(this, 5) << "failed to read input for slo r=" << r << dendl;
7c673cae
FG
982 return r;
983 }
984
985 if ((int64_t)slo_info->entries.size() > s->cct->_conf->rgw_max_slo_entries) {
9f95a23c 986 ldpp_dout(this, 5) << "too many entries in slo request: " << slo_info->entries.size() << dendl;
7c673cae
FG
987 return -EINVAL;
988 }
989
990 MD5 etag_sum;
991 uint64_t total_size = 0;
11fdf7f2
TL
992 for (auto& entry : slo_info->entries) {
993 etag_sum.Update((const unsigned char *)entry.etag.c_str(),
7c673cae 994 entry.etag.length());
11fdf7f2
TL
995
996 /* if size_bytes == 0, it should be replaced with the
997 * real segment size (which could be 0); this follows from the
998 * fact that Swift requires all segments to exist, but permits
999 * the size_bytes element to be omitted from the SLO manifest, see
1000 * https://docs.openstack.org/swift/latest/api/large_objects.html
1001 */
1002 r = update_slo_segment_size(entry);
1003 if (r < 0) {
1004 return r;
1005 }
1006
7c673cae
FG
1007 total_size += entry.size_bytes;
1008
9f95a23c 1009 ldpp_dout(this, 20) << "slo_part: " << entry.path
7c673cae
FG
1010 << " size=" << entry.size_bytes
1011 << " etag=" << entry.etag
1012 << dendl;
1013 }
1014 complete_etag(etag_sum, &lo_etag);
1015 slo_info->total_size = total_size;
1016
11fdf7f2 1017 ofs = slo_info->raw_data.length();
7c673cae
FG
1018 }
1019
1020 return RGWPutObj_ObjStore::get_params();
1021}
1022
1023void RGWPutObj_ObjStore_SWIFT::send_response()
1024{
3efd9988
FG
1025 const auto meta_ret = handle_metadata_errors(s, op_ret);
1026 if (meta_ret) {
1027 op_ret = meta_ret;
1028 } else {
1029 if (!op_ret) {
1030 op_ret = STATUS_CREATED;
1031 }
1032 set_req_state_err(s, op_ret);
7c673cae
FG
1033 }
1034
1035 if (! lo_etag.empty()) {
1036 /* Static Large Object of Swift API has two etags represented by
1037 * following members:
1038 * - etag - for the manifest itself (it will be stored in xattrs),
1039 * - lo_etag - for the content composited from SLO's segments.
1040 * The value is calculated basing on segments' etags.
1041 * In response for PUT request we have to expose the second one.
1042 * The first one may be obtained by GET with "multipart-manifest=get"
1043 * in query string on a given SLO. */
1044 dump_etag(s, lo_etag, true /* quoted */);
1045 } else {
1046 dump_etag(s, etag);
1047 }
1048
1049 dump_last_modified(s, mtime);
1050 set_req_state_err(s, op_ret);
1051 dump_errno(s);
1052 end_header(s, this);
1053 rgw_flush_formatter_and_reset(s, s->formatter);
1054}
1055
1056static int get_swift_account_settings(req_state * const s,
9f95a23c 1057 rgw::sal::RGWRadosStore * const store,
7c673cae
FG
1058 RGWAccessControlPolicy_SWIFTAcct * const policy,
1059 bool * const has_policy)
1060{
1061 *has_policy = false;
1062
1063 const char * const acl_attr = s->info.env->get("HTTP_X_ACCOUNT_ACCESS_CONTROL");
1064 if (acl_attr) {
1065 RGWAccessControlPolicy_SWIFTAcct swift_acct_policy(s->cct);
9f95a23c
TL
1066 const bool r = swift_acct_policy.create(store->ctl()->user,
1067 s->user->get_id(),
1068 s->user->get_display_name(),
7c673cae
FG
1069 string(acl_attr));
1070 if (r != true) {
1071 return -EINVAL;
1072 }
1073
1074 *policy = swift_acct_policy;
1075 *has_policy = true;
1076 }
1077
1078 return 0;
1079}
1080
1081int RGWPutMetadataAccount_ObjStore_SWIFT::get_params()
1082{
1083 if (s->has_bad_meta) {
1084 return -EINVAL;
1085 }
1086
1087 int ret = get_swift_account_settings(s,
1088 store,
1089 // FIXME: we need to carry unique_ptr in generic class
1090 // and allocate appropriate ACL class in the ctor
1091 static_cast<RGWAccessControlPolicy_SWIFTAcct *>(&policy),
1092 &has_policy);
1093 if (ret < 0) {
1094 return ret;
1095 }
1096
1097 get_rmattrs_from_headers(s, ACCT_PUT_ATTR_PREFIX, ACCT_REMOVE_ATTR_PREFIX,
1098 rmattr_names);
1099 return 0;
1100}
1101
1102void RGWPutMetadataAccount_ObjStore_SWIFT::send_response()
1103{
3efd9988
FG
1104 const auto meta_ret = handle_metadata_errors(s, op_ret);
1105 if (meta_ret != op_ret) {
1106 op_ret = meta_ret;
1107 } else {
1108 if (!op_ret) {
1109 op_ret = STATUS_NO_CONTENT;
1110 }
1111 set_req_state_err(s, op_ret);
7c673cae 1112 }
3efd9988 1113
7c673cae
FG
1114 dump_errno(s);
1115 end_header(s, this);
1116 rgw_flush_formatter_and_reset(s, s->formatter);
1117}
1118
1119int RGWPutMetadataBucket_ObjStore_SWIFT::get_params()
1120{
1121 if (s->has_bad_meta) {
1122 return -EINVAL;
1123 }
1124
1125 int r = get_swift_container_settings(s, store, &policy, &has_policy,
1126 &policy_rw_mask, &cors_config, &has_cors);
1127 if (r < 0) {
1128 return r;
1129 }
1130
1131 get_rmattrs_from_headers(s, CONT_PUT_ATTR_PREFIX, CONT_REMOVE_ATTR_PREFIX,
1132 rmattr_names);
11fdf7f2 1133 placement_rule.init(s->info.env->get("HTTP_X_STORAGE_POLICY", ""), s->info.storage_class);
7c673cae
FG
1134
1135 return get_swift_versioning_settings(s, swift_ver_location);
1136}
1137
1138void RGWPutMetadataBucket_ObjStore_SWIFT::send_response()
1139{
3efd9988
FG
1140 const auto meta_ret = handle_metadata_errors(s, op_ret);
1141 if (meta_ret != op_ret) {
1142 op_ret = meta_ret;
1143 } else {
1144 if (!op_ret && (op_ret != -EINVAL)) {
1145 op_ret = STATUS_NO_CONTENT;
1146 }
1147 set_req_state_err(s, op_ret);
7c673cae 1148 }
3efd9988 1149
7c673cae
FG
1150 dump_errno(s);
1151 end_header(s, this);
1152 rgw_flush_formatter_and_reset(s, s->formatter);
1153}
1154
1155int RGWPutMetadataObject_ObjStore_SWIFT::get_params()
1156{
1157 if (s->has_bad_meta) {
1158 return -EINVAL;
1159 }
1160
1161 /* Handle Swift object expiration. */
1162 int r = get_delete_at_param(s, delete_at);
1163 if (r < 0) {
9f95a23c 1164 ldpp_dout(this, 5) << "ERROR: failed to get Delete-At param" << dendl;
7c673cae
FG
1165 return r;
1166 }
1167
7c673cae
FG
1168 dlo_manifest = s->info.env->get("HTTP_X_OBJECT_MANIFEST");
1169
1170 return 0;
1171}
1172
1173void RGWPutMetadataObject_ObjStore_SWIFT::send_response()
1174{
3efd9988
FG
1175 const auto meta_ret = handle_metadata_errors(s, op_ret);
1176 if (meta_ret != op_ret) {
1177 op_ret = meta_ret;
1178 } else {
1179 if (!op_ret) {
1180 op_ret = STATUS_ACCEPTED;
1181 }
1182 set_req_state_err(s, op_ret);
7c673cae 1183 }
3efd9988 1184
31f18b77 1185 if (!s->is_err()) {
7c673cae
FG
1186 dump_content_length(s, 0);
1187 }
3efd9988 1188
7c673cae
FG
1189 dump_errno(s);
1190 end_header(s, this);
1191 rgw_flush_formatter_and_reset(s, s->formatter);
1192}
1193
1194static void bulkdelete_respond(const unsigned num_deleted,
1195 const unsigned int num_unfound,
1196 const std::list<RGWBulkDelete::fail_desc_t>& failures,
1197 const int prot_flags, /* in */
1198 ceph::Formatter& formatter) /* out */
1199{
1200 formatter.open_object_section("delete");
1201
1202 string resp_status;
1203 string resp_body;
1204
1205 if (!failures.empty()) {
1206 int reason = ERR_INVALID_REQUEST;
1207 for (const auto fail_desc : failures) {
1208 if (-ENOENT != fail_desc.err && -EACCES != fail_desc.err) {
1209 reason = fail_desc.err;
1210 }
1211 }
7c673cae
FG
1212 rgw_err err;
1213 set_req_state_err(err, reason, prot_flags);
1214 dump_errno(err, resp_status);
1215 } else if (0 == num_deleted && 0 == num_unfound) {
1216 /* 400 Bad Request */
1217 dump_errno(400, resp_status);
1218 resp_body = "Invalid bulk delete.";
1219 } else {
1220 /* 200 OK */
1221 dump_errno(200, resp_status);
1222 }
1223
1224 encode_json("Number Deleted", num_deleted, &formatter);
1225 encode_json("Number Not Found", num_unfound, &formatter);
1226 encode_json("Response Body", resp_body, &formatter);
1227 encode_json("Response Status", resp_status, &formatter);
1228
1229 formatter.open_array_section("Errors");
1230 for (const auto fail_desc : failures) {
1231 formatter.open_array_section("object");
1232
1233 stringstream ss_name;
1234 ss_name << fail_desc.path;
1235 encode_json("Name", ss_name.str(), &formatter);
1236
1237 rgw_err err;
1238 set_req_state_err(err, fail_desc.err, prot_flags);
1239 string status;
1240 dump_errno(err, status);
1241 encode_json("Status", status, &formatter);
1242 formatter.close_section();
1243 }
1244 formatter.close_section();
1245
1246 formatter.close_section();
1247}
1248
1249int RGWDeleteObj_ObjStore_SWIFT::verify_permission()
1250{
1251 op_ret = RGWDeleteObj_ObjStore::verify_permission();
1252
1253 /* We have to differentiate error codes depending on whether user is
1254 * anonymous (401 Unauthorized) or he doesn't have necessary permissions
1255 * (403 Forbidden). */
1256 if (s->auth.identity->is_anonymous() && op_ret == -EACCES) {
1257 return -EPERM;
1258 } else {
1259 return op_ret;
1260 }
1261}
1262
1263int RGWDeleteObj_ObjStore_SWIFT::get_params()
1264{
1265 const string& mm = s->info.args.get("multipart-manifest");
1266 multipart_delete = (mm.compare("delete") == 0);
1267
1268 return RGWDeleteObj_ObjStore::get_params();
1269}
1270
1271void RGWDeleteObj_ObjStore_SWIFT::send_response()
1272{
1273 int r = op_ret;
1274
1275 if (multipart_delete) {
1276 r = 0;
1277 } else if(!r) {
1278 r = STATUS_NO_CONTENT;
1279 }
1280
1281 set_req_state_err(s, r);
1282 dump_errno(s);
1283
1284 if (multipart_delete) {
1285 end_header(s, this /* RGWOp */, nullptr /* contype */,
1286 CHUNKED_TRANSFER_ENCODING);
1287
1288 if (deleter) {
1289 bulkdelete_respond(deleter->get_num_deleted(),
1290 deleter->get_num_unfound(),
1291 deleter->get_failures(),
1292 s->prot_flags,
1293 *s->formatter);
1294 } else if (-ENOENT == op_ret) {
1295 bulkdelete_respond(0, 1, {}, s->prot_flags, *s->formatter);
1296 } else {
1297 RGWBulkDelete::acct_path_t path;
1298 path.bucket_name = s->bucket_name;
1299 path.obj_key = s->object;
1300
1301 RGWBulkDelete::fail_desc_t fail_desc;
1302 fail_desc.err = op_ret;
1303 fail_desc.path = path;
1304
1305 bulkdelete_respond(0, 0, { fail_desc }, s->prot_flags, *s->formatter);
1306 }
1307 } else {
1308 end_header(s, this);
1309 }
1310
1311 rgw_flush_formatter_and_reset(s, s->formatter);
1312
1313}
1314
1315static void get_contype_from_attrs(map<string, bufferlist>& attrs,
1316 string& content_type)
1317{
1318 map<string, bufferlist>::iterator iter = attrs.find(RGW_ATTR_CONTENT_TYPE);
1319 if (iter != attrs.end()) {
11fdf7f2 1320 content_type = rgw_bl_str(iter->second);
7c673cae
FG
1321 }
1322}
1323
9f95a23c 1324static void dump_object_metadata(const DoutPrefixProvider* dpp, struct req_state * const s,
11fdf7f2 1325 const map<string, bufferlist>& attrs)
7c673cae
FG
1326{
1327 map<string, string> response_attrs;
1328
1329 for (auto kv : attrs) {
1330 const char * name = kv.first.c_str();
1331 const auto aiter = rgw_to_http_attrs.find(name);
1332
1333 if (aiter != std::end(rgw_to_http_attrs)) {
11fdf7f2 1334 response_attrs[aiter->second] = rgw_bl_str(kv.second);
31f18b77 1335 } else if (strcmp(name, RGW_ATTR_SLO_UINDICATOR) == 0) {
11fdf7f2 1336 // this attr has an extra length prefix from encode() in prior versions
31f18b77 1337 dump_header(s, "X-Object-Meta-Static-Large-Object", "True");
7c673cae
FG
1338 } else if (strncmp(name, RGW_ATTR_META_PREFIX,
1339 sizeof(RGW_ATTR_META_PREFIX)-1) == 0) {
1340 name += sizeof(RGW_ATTR_META_PREFIX) - 1;
1341 dump_header_prefixed(s, "X-Object-Meta-",
1342 camelcase_dash_http_attr(name), kv.second);
1343 }
1344 }
1345
1346 /* Handle override and fallback for Content-Disposition HTTP header.
1347 * At the moment this will be used only by TempURL of the Swift API. */
1348 const auto cditer = rgw_to_http_attrs.find(RGW_ATTR_CONTENT_DISP);
1349 if (cditer != std::end(rgw_to_http_attrs)) {
1350 const auto& name = cditer->second;
1351
1352 if (!s->content_disp.override.empty()) {
1353 response_attrs[name] = s->content_disp.override;
1354 } else if (!s->content_disp.fallback.empty()
1355 && response_attrs.find(name) == std::end(response_attrs)) {
1356 response_attrs[name] = s->content_disp.fallback;
1357 }
1358 }
1359
1360 for (const auto kv : response_attrs) {
1361 dump_header(s, kv.first, kv.second);
1362 }
1363
1364 const auto iter = attrs.find(RGW_ATTR_DELETE_AT);
1365 if (iter != std::end(attrs)) {
1366 utime_t delete_at;
1367 try {
11fdf7f2 1368 decode(delete_at, iter->second);
7c673cae
FG
1369 if (!delete_at.is_zero()) {
1370 dump_header(s, "X-Delete-At", delete_at.sec());
1371 }
1372 } catch (buffer::error& err) {
9f95a23c 1373 ldpp_dout(dpp, 0) << "ERROR: cannot decode object's " RGW_ATTR_DELETE_AT
7c673cae
FG
1374 " attr, ignoring"
1375 << dendl;
1376 }
1377 }
1378}
1379
1380int RGWCopyObj_ObjStore_SWIFT::init_dest_policy()
1381{
9f95a23c 1382 dest_policy.create_default(s->user->get_id(), s->user->get_display_name());
7c673cae
FG
1383
1384 return 0;
1385}
1386
1387int RGWCopyObj_ObjStore_SWIFT::get_params()
1388{
1389 if_mod = s->info.env->get("HTTP_IF_MODIFIED_SINCE");
1390 if_unmod = s->info.env->get("HTTP_IF_UNMODIFIED_SINCE");
1391 if_match = s->info.env->get("HTTP_COPY_IF_MATCH");
1392 if_nomatch = s->info.env->get("HTTP_COPY_IF_NONE_MATCH");
1393
1394 src_tenant_name = s->src_tenant_name;
1395 src_bucket_name = s->src_bucket_name;
1396 src_object = s->src_object;
1397 dest_tenant_name = s->bucket_tenant;
1398 dest_bucket_name = s->bucket_name;
1399 dest_object = s->object.name;
1400
1401 const char * const fresh_meta = s->info.env->get("HTTP_X_FRESH_METADATA");
1402 if (fresh_meta && strcasecmp(fresh_meta, "TRUE") == 0) {
1403 attrs_mod = RGWRados::ATTRSMOD_REPLACE;
1404 } else {
1405 attrs_mod = RGWRados::ATTRSMOD_MERGE;
1406 }
1407
1408 int r = get_delete_at_param(s, delete_at);
1409 if (r < 0) {
9f95a23c 1410 ldpp_dout(this, 5) << "ERROR: failed to get Delete-At param" << dendl;
7c673cae
FG
1411 return r;
1412 }
1413
1414 return 0;
1415}
1416
1417void RGWCopyObj_ObjStore_SWIFT::send_partial_response(off_t ofs)
1418{
1419 if (! sent_header) {
1420 if (! op_ret)
1421 op_ret = STATUS_CREATED;
1422 set_req_state_err(s, op_ret);
1423 dump_errno(s);
1424 end_header(s, this);
1425
1426 /* Send progress information. Note that this diverge from the original swift
1427 * spec. We do this in order to keep connection alive.
1428 */
1429 if (op_ret == 0) {
1430 s->formatter->open_array_section("progress");
1431 }
1432 sent_header = true;
1433 } else {
1434 s->formatter->dump_int("ofs", (uint64_t)ofs);
1435 }
1436 rgw_flush_formatter(s, s->formatter);
1437}
1438
1439void RGWCopyObj_ObjStore_SWIFT::dump_copy_info()
1440{
1441 /* Dump X-Copied-From. */
1442 dump_header(s, "X-Copied-From", url_encode(src_bucket.name) +
1443 "/" + url_encode(src_object.name));
1444
1445 /* Dump X-Copied-From-Account. */
1446 /* XXX tenant */
9f95a23c 1447 dump_header(s, "X-Copied-From-Account", url_encode(s->user->get_id().id));
7c673cae
FG
1448
1449 /* Dump X-Copied-From-Last-Modified. */
1450 dump_time_header(s, "X-Copied-From-Last-Modified", src_mtime);
1451}
1452
1453void RGWCopyObj_ObjStore_SWIFT::send_response()
1454{
1455 if (! sent_header) {
1456 string content_type;
1457 if (! op_ret)
1458 op_ret = STATUS_CREATED;
1459 set_req_state_err(s, op_ret);
1460 dump_errno(s);
1461 dump_etag(s, etag);
1462 dump_last_modified(s, mtime);
1463 dump_copy_info();
1464 get_contype_from_attrs(attrs, content_type);
9f95a23c 1465 dump_object_metadata(this, s, attrs);
7c673cae
FG
1466 end_header(s, this, !content_type.empty() ? content_type.c_str()
1467 : "binary/octet-stream");
1468 } else {
1469 s->formatter->close_section();
1470 rgw_flush_formatter(s, s->formatter);
1471 }
1472}
1473
1474int RGWGetObj_ObjStore_SWIFT::verify_permission()
1475{
1476 op_ret = RGWGetObj_ObjStore::verify_permission();
1477
1478 /* We have to differentiate error codes depending on whether user is
1479 * anonymous (401 Unauthorized) or he doesn't have necessary permissions
1480 * (403 Forbidden). */
1481 if (s->auth.identity->is_anonymous() && op_ret == -EACCES) {
1482 return -EPERM;
1483 } else {
1484 return op_ret;
1485 }
1486}
1487
1488int RGWGetObj_ObjStore_SWIFT::get_params()
1489{
1490 const string& mm = s->info.args.get("multipart-manifest");
1491 skip_manifest = (mm.compare("get") == 0);
1492
1493 return RGWGetObj_ObjStore::get_params();
1494}
1495
1496int RGWGetObj_ObjStore_SWIFT::send_response_data_error()
1497{
1498 std::string error_content;
1499 op_ret = error_handler(op_ret, &error_content);
1500 if (! op_ret) {
1501 /* The error handler has taken care of the error. */
1502 return 0;
1503 }
1504
1505 bufferlist error_bl;
1506 error_bl.append(error_content);
1507 return send_response_data(error_bl, 0, error_bl.length());
1508}
1509
1510int RGWGetObj_ObjStore_SWIFT::send_response_data(bufferlist& bl,
1511 const off_t bl_ofs,
1512 const off_t bl_len)
1513{
1514 string content_type;
1515
1516 if (sent_header) {
1517 goto send_data;
1518 }
1519
1520 if (custom_http_ret) {
1521 set_req_state_err(s, 0);
1522 dump_errno(s, custom_http_ret);
1523 } else {
1524 set_req_state_err(s, (partial_content && !op_ret) ? STATUS_PARTIAL_CONTENT
1525 : op_ret);
1526 dump_errno(s);
1527
31f18b77 1528 if (s->is_err()) {
7c673cae
FG
1529 end_header(s, NULL);
1530 return 0;
1531 }
1532 }
1533
1534 if (range_str) {
1535 dump_range(s, ofs, end, s->obj_size);
1536 }
1537
31f18b77 1538 if (s->is_err()) {
7c673cae
FG
1539 end_header(s, NULL);
1540 return 0;
1541 }
1542
1543 dump_content_length(s, total_len);
1544 dump_last_modified(s, lastmod);
1545 dump_header(s, "X-Timestamp", utime_t(lastmod));
1546 if (is_slo) {
1547 dump_header(s, "X-Static-Large-Object", "True");
1548 }
1549
1550 if (! op_ret) {
1551 if (! lo_etag.empty()) {
1552 dump_etag(s, lo_etag, true /* quoted */);
1553 } else {
1554 auto iter = attrs.find(RGW_ATTR_ETAG);
1555 if (iter != attrs.end()) {
11fdf7f2 1556 dump_etag(s, iter->second.to_str());
7c673cae
FG
1557 }
1558 }
1559
1560 get_contype_from_attrs(attrs, content_type);
9f95a23c 1561 dump_object_metadata(this, s, attrs);
7c673cae
FG
1562 }
1563
1564 end_header(s, this, !content_type.empty() ? content_type.c_str()
1565 : "binary/octet-stream");
1566
1567 sent_header = true;
1568
1569send_data:
1570 if (get_data && !op_ret) {
1571 const auto r = dump_body(s, bl.c_str() + bl_ofs, bl_len);
1572 if (r < 0) {
1573 return r;
1574 }
1575 }
1576 rgw_flush_formatter_and_reset(s, s->formatter);
1577
1578 return 0;
1579}
1580
1581void RGWOptionsCORS_ObjStore_SWIFT::send_response()
1582{
1583 string hdrs, exp_hdrs;
1584 uint32_t max_age = CORS_MAX_AGE_INVALID;
1585 /*EACCES means, there is no CORS registered yet for the bucket
1586 *ENOENT means, there is no match of the Origin in the list of CORSRule
1587 */
1588 if (op_ret == -ENOENT)
1589 op_ret = -EACCES;
1590 if (op_ret < 0) {
1591 set_req_state_err(s, op_ret);
1592 dump_errno(s);
1593 end_header(s, NULL);
1594 return;
1595 }
1596 get_response_params(hdrs, exp_hdrs, &max_age);
1597 dump_errno(s);
1598 dump_access_control(s, origin, req_meth, hdrs.c_str(), exp_hdrs.c_str(),
1599 max_age);
1600 end_header(s, NULL);
1601}
1602
1603int RGWBulkDelete_ObjStore_SWIFT::get_data(
1604 list<RGWBulkDelete::acct_path_t>& items, bool * const is_truncated)
1605{
1606 constexpr size_t MAX_LINE_SIZE = 2048;
1607
1608 RGWClientIOStreamBuf ciosb(static_cast<RGWRestfulIO&>(*(s->cio)),
1609 size_t(s->cct->_conf->rgw_max_chunk_size));
1610 istream cioin(&ciosb);
1611
1612 char buf[MAX_LINE_SIZE];
1613 while (cioin.getline(buf, sizeof(buf))) {
1614 string path_str(buf);
1615
9f95a23c 1616 ldpp_dout(this, 20) << "extracted Bulk Delete entry: " << path_str << dendl;
7c673cae
FG
1617
1618 RGWBulkDelete::acct_path_t path;
1619
1620 /* We need to skip all slashes at the beginning in order to preserve
1621 * compliance with Swift. */
1622 const size_t start_pos = path_str.find_first_not_of('/');
1623
1624 if (string::npos != start_pos) {
1625 /* Seperator is the first slash after the leading ones. */
1626 const size_t sep_pos = path_str.find('/', start_pos);
1627
1628 if (string::npos != sep_pos) {
31f18b77
FG
1629 path.bucket_name = url_decode(path_str.substr(start_pos,
1630 sep_pos - start_pos));
1631 path.obj_key = url_decode(path_str.substr(sep_pos + 1));
7c673cae
FG
1632 } else {
1633 /* It's guaranteed here that bucket name is at least one character
1634 * long and is different than slash. */
31f18b77 1635 path.bucket_name = url_decode(path_str.substr(start_pos));
7c673cae
FG
1636 }
1637
1638 items.push_back(path);
1639 }
1640
1641 if (items.size() == MAX_CHUNK_ENTRIES) {
1642 *is_truncated = true;
1643 return 0;
1644 }
1645 }
1646
1647 *is_truncated = false;
1648 return 0;
1649}
1650
1651void RGWBulkDelete_ObjStore_SWIFT::send_response()
1652{
1653 set_req_state_err(s, op_ret);
1654 dump_errno(s);
1655 end_header(s, this /* RGWOp */, nullptr /* contype */,
1656 CHUNKED_TRANSFER_ENCODING);
1657
1658 bulkdelete_respond(deleter->get_num_deleted(),
1659 deleter->get_num_unfound(),
1660 deleter->get_failures(),
1661 s->prot_flags,
1662 *s->formatter);
1663 rgw_flush_formatter_and_reset(s, s->formatter);
1664}
1665
1666
1667std::unique_ptr<RGWBulkUploadOp::StreamGetter>
1668RGWBulkUploadOp_ObjStore_SWIFT::create_stream()
1669{
1670 class SwiftStreamGetter : public StreamGetter {
9f95a23c 1671 const DoutPrefixProvider* dpp;
7c673cae
FG
1672 const size_t conlen;
1673 size_t curpos;
1674 req_state* const s;
1675
1676 public:
9f95a23c
TL
1677 SwiftStreamGetter(const DoutPrefixProvider* dpp, req_state* const s, const size_t conlen)
1678 : dpp(dpp),
1679 conlen(conlen),
7c673cae
FG
1680 curpos(0),
1681 s(s) {
1682 }
1683
1684 ssize_t get_at_most(size_t want, ceph::bufferlist& dst) override {
1685 /* maximum requested by a caller */
1686 /* data provided by client */
1687 /* RadosGW's limit. */
1688 const size_t max_chunk_size = \
1689 static_cast<size_t>(s->cct->_conf->rgw_max_chunk_size);
1690 const size_t max_to_read = std::min({ want, conlen - curpos, max_chunk_size });
1691
9f95a23c 1692 ldpp_dout(dpp, 20) << "bulk_upload: get_at_most max_to_read="
7c673cae
FG
1693 << max_to_read
1694 << ", dst.c_str()=" << reinterpret_cast<intptr_t>(dst.c_str()) << dendl;
1695
1696 bufferptr bp(max_to_read);
1697 const auto read_len = recv_body(s, bp.c_str(), max_to_read);
1698 dst.append(bp, 0, read_len);
1699 //const auto read_len = recv_body(s, dst.c_str(), max_to_read);
1700 if (read_len < 0) {
1701 return read_len;
1702 }
1703
1704 curpos += read_len;
1705 return curpos > s->cct->_conf->rgw_max_put_size ? -ERR_TOO_LARGE
1706 : read_len;
1707 }
1708
1709 ssize_t get_exactly(size_t want, ceph::bufferlist& dst) override {
9f95a23c 1710 ldpp_dout(dpp, 20) << "bulk_upload: get_exactly want=" << want << dendl;
7c673cae
FG
1711
1712 /* FIXME: do this in a loop. */
1713 const auto ret = get_at_most(want, dst);
9f95a23c 1714 ldpp_dout(dpp, 20) << "bulk_upload: get_exactly ret=" << ret << dendl;
7c673cae
FG
1715 if (ret < 0) {
1716 return ret;
1717 } else if (static_cast<size_t>(ret) != want) {
1718 return -EINVAL;
1719 } else {
1720 return want;
1721 }
1722 }
1723 };
1724
1725 if (! s->length) {
1726 op_ret = -EINVAL;
1727 return nullptr;
1728 } else {
9f95a23c 1729 ldpp_dout(this, 20) << "bulk upload: create_stream for length="
7c673cae
FG
1730 << s->length << dendl;
1731
1732 const size_t conlen = atoll(s->length);
9f95a23c 1733 return std::unique_ptr<SwiftStreamGetter>(new SwiftStreamGetter(this, s, conlen));
7c673cae
FG
1734 }
1735}
1736
1737void RGWBulkUploadOp_ObjStore_SWIFT::send_response()
1738{
1739 set_req_state_err(s, op_ret);
1740 dump_errno(s);
1741 end_header(s, this /* RGWOp */, nullptr /* contype */,
1742 CHUNKED_TRANSFER_ENCODING);
1743 rgw_flush_formatter_and_reset(s, s->formatter);
1744
1745 s->formatter->open_object_section("delete");
1746
1747 std::string resp_status;
1748 std::string resp_body;
1749
1750 if (! failures.empty()) {
1751 rgw_err err;
1752
1753 const auto last_err = { failures.back().err };
1754 if (boost::algorithm::contains(last_err, terminal_errors)) {
1755 /* The terminal errors are affecting the status of the whole upload. */
1756 set_req_state_err(err, failures.back().err, s->prot_flags);
1757 } else {
1758 set_req_state_err(err, ERR_INVALID_REQUEST, s->prot_flags);
1759 }
1760
1761 dump_errno(err, resp_status);
1762 } else if (0 == num_created && failures.empty()) {
1763 /* Nothing created, nothing failed. This means the archive contained no
1764 * entity we could understand (regular file or directory). We need to
1765 * send 400 Bad Request to an HTTP client in the internal status field. */
1766 dump_errno(400, resp_status);
1767 resp_body = "Invalid Tar File: No Valid Files";
1768 } else {
1769 /* 200 OK */
1770 dump_errno(201, resp_status);
1771 }
1772
1773 encode_json("Number Files Created", num_created, s->formatter);
1774 encode_json("Response Body", resp_body, s->formatter);
1775 encode_json("Response Status", resp_status, s->formatter);
1776
1777 s->formatter->open_array_section("Errors");
1778 for (const auto& fail_desc : failures) {
1779 s->formatter->open_array_section("object");
1780
1781 encode_json("Name", fail_desc.path, s->formatter);
1782
1783 rgw_err err;
1784 set_req_state_err(err, fail_desc.err, s->prot_flags);
1785 std::string status;
1786 dump_errno(err, status);
1787 encode_json("Status", status, s->formatter);
1788
1789 s->formatter->close_section();
1790 }
1791 s->formatter->close_section();
1792
1793 s->formatter->close_section();
1794 rgw_flush_formatter_and_reset(s, s->formatter);
1795}
1796
1797
1798void RGWGetCrossDomainPolicy_ObjStore_SWIFT::send_response()
1799{
1800 set_req_state_err(s, op_ret);
1801 dump_errno(s);
1802 end_header(s, this, "application/xml");
1803
1804 std::stringstream ss;
1805
1806 ss << R"(<?xml version="1.0"?>)" << "\n"
1807 << R"(<!DOCTYPE cross-domain-policy SYSTEM )"
1808 << R"("http://www.adobe.com/xml/dtds/cross-domain-policy.dtd" >)" << "\n"
1809 << R"(<cross-domain-policy>)" << "\n"
11fdf7f2 1810 << g_conf()->rgw_cross_domain_policy << "\n"
7c673cae
FG
1811 << R"(</cross-domain-policy>)";
1812
1813 dump_body(s, ss.str());
1814}
1815
1816void RGWGetHealthCheck_ObjStore_SWIFT::send_response()
1817{
1818 set_req_state_err(s, op_ret);
1819 dump_errno(s);
1820 end_header(s, this, "application/xml");
1821
1822 if (op_ret) {
1823 static constexpr char DISABLED[] = "DISABLED BY FILE";
1824 dump_body(s, DISABLED, strlen(DISABLED));
1825 }
1826}
1827
1828const vector<pair<string, RGWInfo_ObjStore_SWIFT::info>> RGWInfo_ObjStore_SWIFT::swift_info =
1829{
1830 {"bulk_delete", {false, nullptr}},
1831 {"container_quotas", {false, nullptr}},
1832 {"swift", {false, RGWInfo_ObjStore_SWIFT::list_swift_data}},
1833 {"tempurl", { false, RGWInfo_ObjStore_SWIFT::list_tempurl_data}},
1834 {"slo", {false, RGWInfo_ObjStore_SWIFT::list_slo_data}},
1835 {"account_quotas", {false, nullptr}},
1836 {"staticweb", {false, nullptr}},
224ce89b 1837 {"tempauth", {false, RGWInfo_ObjStore_SWIFT::list_tempauth_data}},
7c673cae
FG
1838};
1839
1840void RGWInfo_ObjStore_SWIFT::execute()
1841{
1842 bool is_admin_info_enabled = false;
1843
1844 const string& swiftinfo_sig = s->info.args.get("swiftinfo_sig");
1845 const string& swiftinfo_expires = s->info.args.get("swiftinfo_expires");
1846
1847 if (!swiftinfo_sig.empty() &&
1848 !swiftinfo_expires.empty() &&
9f95a23c 1849 !is_expired(swiftinfo_expires, this)) {
7c673cae
FG
1850 is_admin_info_enabled = true;
1851 }
1852
1853 s->formatter->open_object_section("info");
1854
1855 for (const auto& pair : swift_info) {
1856 if(!is_admin_info_enabled && pair.second.is_admin_info)
1857 continue;
1858
1859 if (!pair.second.list_data) {
1860 s->formatter->open_object_section((pair.first).c_str());
1861 s->formatter->close_section();
1862 }
1863 else {
9f95a23c 1864 pair.second.list_data(*(s->formatter), s->cct->_conf, *store->getRados());
7c673cae
FG
1865 }
1866 }
1867
1868 s->formatter->close_section();
1869}
1870
1871void RGWInfo_ObjStore_SWIFT::send_response()
1872{
1873 if (op_ret < 0) {
1874 op_ret = STATUS_NO_CONTENT;
1875 }
1876 set_req_state_err(s, op_ret);
1877 dump_errno(s);
1878 end_header(s, this);
1879 rgw_flush_formatter_and_reset(s, s->formatter);
1880}
1881
1882void RGWInfo_ObjStore_SWIFT::list_swift_data(Formatter& formatter,
11fdf7f2 1883 const ConfigProxy& config,
7c673cae
FG
1884 RGWRados& store)
1885{
1886 formatter.open_object_section("swift");
11fdf7f2 1887 formatter.dump_int("max_file_size", config->rgw_max_put_size);
7c673cae
FG
1888 formatter.dump_int("container_listing_limit", RGW_LIST_BUCKETS_LIMIT_MAX);
1889
1890 string ceph_version(CEPH_GIT_NICE_VER);
1891 formatter.dump_string("version", ceph_version);
3efd9988
FG
1892
1893 const size_t max_attr_name_len = \
11fdf7f2 1894 g_conf().get_val<Option::size_t>("rgw_max_attr_name_len");
3efd9988
FG
1895 if (max_attr_name_len) {
1896 const size_t meta_name_limit = \
1897 max_attr_name_len - strlen(RGW_ATTR_PREFIX RGW_AMZ_META_PREFIX);
1898 formatter.dump_int("max_meta_name_length", meta_name_limit);
1899 }
1900
11fdf7f2 1901 const size_t meta_value_limit = g_conf().get_val<Option::size_t>("rgw_max_attr_size");
3efd9988
FG
1902 if (meta_value_limit) {
1903 formatter.dump_int("max_meta_value_length", meta_value_limit);
1904 }
1905
1906 const size_t meta_num_limit = \
11fdf7f2 1907 g_conf().get_val<uint64_t>("rgw_max_attrs_num_in_req");
3efd9988
FG
1908 if (meta_num_limit) {
1909 formatter.dump_int("max_meta_count", meta_num_limit);
1910 }
7c673cae
FG
1911
1912 formatter.open_array_section("policies");
11fdf7f2 1913 const RGWZoneGroup& zonegroup = store.svc.zone->get_zonegroup();
7c673cae
FG
1914
1915 for (const auto& placement_targets : zonegroup.placement_targets) {
1916 formatter.open_object_section("policy");
11fdf7f2 1917 if (placement_targets.second.name.compare(zonegroup.default_placement.name) == 0)
7c673cae
FG
1918 formatter.dump_bool("default", true);
1919 formatter.dump_string("name", placement_targets.second.name.c_str());
1920 formatter.close_section();
1921 }
1922 formatter.close_section();
1923
1924 formatter.dump_int("max_object_name_size", RGWHandler_REST::MAX_OBJ_NAME_LEN);
1925 formatter.dump_bool("strict_cors_mode", true);
1926 formatter.dump_int("max_container_name_length", RGWHandler_REST::MAX_BUCKET_NAME_LEN);
1927 formatter.close_section();
1928}
1929
224ce89b 1930void RGWInfo_ObjStore_SWIFT::list_tempauth_data(Formatter& formatter,
11fdf7f2 1931 const ConfigProxy& config,
224ce89b
WB
1932 RGWRados& store)
1933{
1934 formatter.open_object_section("tempauth");
1935 formatter.dump_bool("account_acls", true);
1936 formatter.close_section();
1937}
7c673cae 1938void RGWInfo_ObjStore_SWIFT::list_tempurl_data(Formatter& formatter,
11fdf7f2 1939 const ConfigProxy& config,
7c673cae
FG
1940 RGWRados& store)
1941{
1942 formatter.open_object_section("tempurl");
1943 formatter.open_array_section("methods");
1944 formatter.dump_string("methodname", "GET");
1945 formatter.dump_string("methodname", "HEAD");
1946 formatter.dump_string("methodname", "PUT");
1947 formatter.dump_string("methodname", "POST");
1948 formatter.dump_string("methodname", "DELETE");
1949 formatter.close_section();
1950 formatter.close_section();
1951}
1952
1953void RGWInfo_ObjStore_SWIFT::list_slo_data(Formatter& formatter,
11fdf7f2 1954 const ConfigProxy& config,
7c673cae
FG
1955 RGWRados& store)
1956{
1957 formatter.open_object_section("slo");
11fdf7f2 1958 formatter.dump_int("max_manifest_segments", config->rgw_max_slo_entries);
7c673cae
FG
1959 formatter.close_section();
1960}
1961
9f95a23c 1962bool RGWInfo_ObjStore_SWIFT::is_expired(const std::string& expires, const DoutPrefixProvider *dpp)
7c673cae
FG
1963{
1964 string err;
1965 const utime_t now = ceph_clock_now();
1966 const uint64_t expiration = (uint64_t)strict_strtoll(expires.c_str(),
1967 10, &err);
1968 if (!err.empty()) {
9f95a23c 1969 ldpp_dout(dpp, 5) << "failed to parse siginfo_expires: " << err << dendl;
7c673cae
FG
1970 return true;
1971 }
1972
1973 if (expiration <= (uint64_t)now.sec()) {
9f95a23c 1974 ldpp_dout(dpp, 5) << "siginfo expired: " << expiration << " <= " << now.sec() << dendl;
7c673cae
FG
1975 return true;
1976 }
1977
1978 return false;
1979}
1980
1981
9f95a23c 1982void RGWFormPost::init(rgw::sal::RGWRadosStore* const store,
7c673cae
FG
1983 req_state* const s,
1984 RGWHandler* const dialect_handler)
1985{
1986 prefix = std::move(s->object.name);
1987 s->object = rgw_obj_key();
1988
1989 return RGWPostObj_ObjStore::init(store, s, dialect_handler);
1990}
1991
1992std::size_t RGWFormPost::get_max_file_size() /*const*/
1993{
1994 std::string max_str = get_part_str(ctrl_parts, "max_file_size", "0");
1995
1996 std::string err;
1997 const std::size_t max_file_size =
1998 static_cast<uint64_t>(strict_strtoll(max_str.c_str(), 10, &err));
1999
2000 if (! err.empty()) {
9f95a23c 2001 ldpp_dout(this, 5) << "failed to parse FormPost's max_file_size: " << err
7c673cae
FG
2002 << dendl;
2003 return 0;
2004 }
2005
2006 return max_file_size;
2007}
2008
2009bool RGWFormPost::is_non_expired()
2010{
2011 std::string expires = get_part_str(ctrl_parts, "expires", "0");
2012
2013 std::string err;
2014 const uint64_t expires_timestamp =
2015 static_cast<uint64_t>(strict_strtoll(expires.c_str(), 10, &err));
2016
2017 if (! err.empty()) {
9f95a23c 2018 ldpp_dout(this, 5) << "failed to parse FormPost's expires: " << err << dendl;
7c673cae
FG
2019 return false;
2020 }
2021
2022 const utime_t now = ceph_clock_now();
2023 if (expires_timestamp <= static_cast<uint64_t>(now.sec())) {
9f95a23c 2024 ldpp_dout(this, 5) << "FormPost form expired: "
7c673cae
FG
2025 << expires_timestamp << " <= " << now.sec() << dendl;
2026 return false;
2027 }
2028
2029 return true;
2030}
2031
2032bool RGWFormPost::is_integral()
2033{
2034 const std::string form_signature = get_part_str(ctrl_parts, "signature");
2035
31f18b77 2036 try {
9f95a23c 2037 get_owner_info(s, s->user->get_info());
31f18b77
FG
2038 s->auth.identity = rgw::auth::transform_old_authinfo(s);
2039 } catch (...) {
9f95a23c 2040 ldpp_dout(this, 5) << "cannot get user_info of account's owner" << dendl;
31f18b77
FG
2041 return false;
2042 }
2043
9f95a23c 2044 for (const auto& kv : s->user->get_info().temp_url_keys) {
7c673cae
FG
2045 const int temp_url_key_num = kv.first;
2046 const string& temp_url_key = kv.second;
2047
2048 if (temp_url_key.empty()) {
2049 continue;
2050 }
2051
2052 SignatureHelper sig_helper;
2053 sig_helper.calc(temp_url_key,
2054 s->info.request_uri,
2055 get_part_str(ctrl_parts, "redirect"),
2056 get_part_str(ctrl_parts, "max_file_size", "0"),
2057 get_part_str(ctrl_parts, "max_file_count", "0"),
2058 get_part_str(ctrl_parts, "expires", "0"));
2059
2060 const auto local_sig = sig_helper.get_signature();
2061
9f95a23c 2062 ldpp_dout(this, 20) << "FormPost signature [" << temp_url_key_num << "]"
7c673cae
FG
2063 << " (calculated): " << local_sig << dendl;
2064
2065 if (sig_helper.is_equal_to(form_signature)) {
2066 return true;
2067 } else {
9f95a23c 2068 ldpp_dout(this, 5) << "FormPost's signature mismatch: "
7c673cae
FG
2069 << local_sig << " != " << form_signature << dendl;
2070 }
2071 }
2072
2073 return false;
2074}
2075
31f18b77
FG
2076void RGWFormPost::get_owner_info(const req_state* const s,
2077 RGWUserInfo& owner_info) const
2078{
2079 /* We cannot use req_state::bucket_name because it isn't available
2080 * now. It will be initialized in RGWHandler_REST_SWIFT::postauth_init(). */
2081 const string& bucket_name = s->init_state.url_bucket;
2082
9f95a23c
TL
2083 auto user_ctl = store->ctl()->user;
2084
31f18b77
FG
2085 /* TempURL in Formpost only requires that bucket name is specified. */
2086 if (bucket_name.empty()) {
2087 throw -EPERM;
2088 }
2089
2090 string bucket_tenant;
2091 if (!s->account_name.empty()) {
2092 RGWUserInfo uinfo;
2093 bool found = false;
2094
2095 const rgw_user uid(s->account_name);
2096 if (uid.tenant.empty()) {
2097 const rgw_user tenanted_uid(uid.id, uid.id);
2098
9f95a23c 2099 if (user_ctl->get_info_by_uid(tenanted_uid, &uinfo, s->yield) >= 0) {
31f18b77
FG
2100 /* Succeeded. */
2101 bucket_tenant = uinfo.user_id.tenant;
2102 found = true;
2103 }
2104 }
2105
9f95a23c 2106 if (!found && user_ctl->get_info_by_uid(uid, &uinfo, s->yield) < 0) {
31f18b77
FG
2107 throw -EPERM;
2108 } else {
2109 bucket_tenant = uinfo.user_id.tenant;
2110 }
2111 }
2112
2113 /* Need to get user info of bucket owner. */
2114 RGWBucketInfo bucket_info;
9f95a23c 2115 int ret = store->getRados()->get_bucket_info(store->svc(),
31f18b77 2116 bucket_tenant, bucket_name,
9f95a23c 2117 bucket_info, nullptr, s->yield);
31f18b77
FG
2118 if (ret < 0) {
2119 throw ret;
2120 }
2121
9f95a23c 2122 ldpp_dout(this, 20) << "temp url user (bucket owner): " << bucket_info.owner
31f18b77
FG
2123 << dendl;
2124
9f95a23c 2125 if (user_ctl->get_info_by_uid(bucket_info.owner, &owner_info, s->yield) < 0) {
31f18b77
FG
2126 throw -EPERM;
2127 }
2128}
2129
7c673cae
FG
2130int RGWFormPost::get_params()
2131{
2132 /* The parentt class extracts boundary info from the Content-Type. */
2133 int ret = RGWPostObj_ObjStore::get_params();
2134 if (ret < 0) {
2135 return ret;
2136 }
2137
9f95a23c 2138 policy.create_default(s->user->get_id(), s->user->get_display_name());
7c673cae
FG
2139
2140 /* Let's start parsing the HTTP body by parsing each form part step-
2141 * by-step till encountering the first part with file data. */
2142 do {
2143 struct post_form_part part;
2144 ret = read_form_part_header(&part, stream_done);
2145 if (ret < 0) {
2146 return ret;
2147 }
2148
11fdf7f2 2149 if (s->cct->_conf->subsys.should_gather<ceph_subsys_rgw, 20>()) {
9f95a23c 2150 ldpp_dout(this, 20) << "read part header -- part.name="
7c673cae
FG
2151 << part.name << dendl;
2152
2153 for (const auto& pair : part.fields) {
9f95a23c
TL
2154 ldpp_dout(this, 20) << "field.name=" << pair.first << dendl;
2155 ldpp_dout(this, 20) << "field.val=" << pair.second.val << dendl;
2156 ldpp_dout(this, 20) << "field.params:" << dendl;
7c673cae
FG
2157
2158 for (const auto& param_pair : pair.second.params) {
9f95a23c 2159 ldpp_dout(this, 20) << " " << param_pair.first
7c673cae
FG
2160 << " -> " << param_pair.second << dendl;
2161 }
2162 }
2163 }
2164
2165 if (stream_done) {
2166 /* Unexpected here. */
2167 err_msg = "Malformed request";
2168 return -EINVAL;
2169 }
2170
2171 const auto field_iter = part.fields.find("Content-Disposition");
2172 if (std::end(part.fields) != field_iter &&
2173 std::end(field_iter->second.params) != field_iter->second.params.find("filename")) {
2174 /* First data part ahead. */
2175 current_data_part = std::move(part);
2176
2177 /* Stop the iteration. We can assume that all control parts have been
2178 * already parsed. The rest of HTTP body should contain data parts
2179 * only. They will be picked up by ::get_data(). */
2180 break;
2181 } else {
2182 /* Control part ahead. Receive, parse and store for later usage. */
2183 bool boundary;
2184 ret = read_data(part.data, s->cct->_conf->rgw_max_chunk_size,
2185 boundary, stream_done);
2186 if (ret < 0) {
2187 return ret;
2188 } else if (! boundary) {
2189 err_msg = "Couldn't find boundary";
2190 return -EINVAL;
2191 }
2192
2193 ctrl_parts[part.name] = std::move(part);
2194 }
2195 } while (! stream_done);
2196
2197 min_len = 0;
2198 max_len = get_max_file_size();
2199
2200 if (! current_data_part) {
2201 err_msg = "FormPost: no files to process";
2202 return -EINVAL;
2203 }
2204
2205 if (! is_non_expired()) {
2206 err_msg = "FormPost: Form Expired";
2207 return -EPERM;
2208 }
2209
2210 if (! is_integral()) {
2211 err_msg = "FormPost: Invalid Signature";
2212 return -EPERM;
2213 }
2214
2215 return 0;
2216}
2217
2218std::string RGWFormPost::get_current_filename() const
2219{
2220 try {
2221 const auto& field = current_data_part->fields.at("Content-Disposition");
2222 const auto iter = field.params.find("filename");
2223
2224 if (std::end(field.params) != iter) {
2225 return prefix + iter->second;
2226 }
2227 } catch (std::out_of_range&) {
2228 /* NOP */;
2229 }
2230
2231 return prefix;
2232}
2233
2234std::string RGWFormPost::get_current_content_type() const
2235{
2236 try {
2237 const auto& field = current_data_part->fields.at("Content-Type");
2238 return field.val;
2239 } catch (std::out_of_range&) {
2240 /* NOP */;
2241 }
2242
2243 return std::string();
2244}
2245
2246bool RGWFormPost::is_next_file_to_upload()
2247{
2248 if (! stream_done) {
2249 /* We have at least one additional part in the body. */
2250 struct post_form_part part;
2251 int r = read_form_part_header(&part, stream_done);
2252 if (r < 0) {
2253 return false;
2254 }
2255
2256 const auto field_iter = part.fields.find("Content-Disposition");
2257 if (std::end(part.fields) != field_iter) {
2258 const auto& params = field_iter->second.params;
31f18b77 2259 const auto& filename_iter = params.find("filename");
7c673cae 2260
31f18b77 2261 if (std::end(params) != filename_iter && ! filename_iter->second.empty()) {
7c673cae
FG
2262 current_data_part = std::move(part);
2263 return true;
2264 }
2265 }
2266 }
2267
2268 return false;
2269}
2270
2271int RGWFormPost::get_data(ceph::bufferlist& bl, bool& again)
2272{
2273 bool boundary;
2274
2275 int r = read_data(bl, s->cct->_conf->rgw_max_chunk_size,
2276 boundary, stream_done);
2277 if (r < 0) {
2278 return r;
2279 }
2280
2281 /* Tell RGWPostObj::execute() that it has some data to put. */
2282 again = !boundary;
2283
2284 return bl.length();
2285}
2286
2287void RGWFormPost::send_response()
2288{
2289 std::string redirect = get_part_str(ctrl_parts, "redirect");
2290 if (! redirect.empty()) {
2291 op_ret = STATUS_REDIRECT;
2292 }
2293
2294 set_req_state_err(s, op_ret);
31f18b77 2295 s->err.err_code = err_msg;
7c673cae
FG
2296 dump_errno(s);
2297 if (! redirect.empty()) {
2298 dump_redirect(s, redirect);
2299 }
2300 end_header(s, this);
2301}
2302
2303bool RGWFormPost::is_formpost_req(req_state* const s)
2304{
2305 std::string content_type;
2306 std::map<std::string, std::string> params;
2307
2308 parse_boundary_params(s->info.env->get("CONTENT_TYPE", ""),
2309 content_type, params);
2310
2311 return boost::algorithm::iequals(content_type, "multipart/form-data") &&
2312 params.count("boundary") > 0;
2313}
2314
2315
2316RGWOp *RGWHandler_REST_Service_SWIFT::op_get()
2317{
2318 return new RGWListBuckets_ObjStore_SWIFT;
2319}
2320
2321RGWOp *RGWHandler_REST_Service_SWIFT::op_head()
2322{
2323 return new RGWStatAccount_ObjStore_SWIFT;
2324}
2325
2326RGWOp *RGWHandler_REST_Service_SWIFT::op_put()
2327{
2328 if (s->info.args.exists("extract-archive")) {
2329 return new RGWBulkUploadOp_ObjStore_SWIFT;
2330 }
2331 return nullptr;
2332}
2333
2334RGWOp *RGWHandler_REST_Service_SWIFT::op_post()
2335{
2336 if (s->info.args.exists("bulk-delete")) {
2337 return new RGWBulkDelete_ObjStore_SWIFT;
2338 }
2339 return new RGWPutMetadataAccount_ObjStore_SWIFT;
2340}
2341
2342RGWOp *RGWHandler_REST_Service_SWIFT::op_delete()
2343{
2344 if (s->info.args.exists("bulk-delete")) {
2345 return new RGWBulkDelete_ObjStore_SWIFT;
2346 }
2347 return NULL;
2348}
2349
2350int RGWSwiftWebsiteHandler::serve_errordoc(const int http_ret,
2351 const std::string error_doc)
2352{
2353 /* Try to throw it all away. */
2354 s->formatter->reset();
2355
2356 class RGWGetErrorPage : public RGWGetObj_ObjStore_SWIFT {
2357 public:
9f95a23c 2358 RGWGetErrorPage(rgw::sal::RGWRadosStore* const store,
7c673cae
FG
2359 RGWHandler_REST* const handler,
2360 req_state* const s,
2361 const int http_ret) {
2362 /* Calling a virtual from the base class is safe as the subobject should
2363 * be properly initialized and we haven't overridden the init method. */
2364 init(store, s, handler);
2365 set_get_data(true);
2366 set_custom_http_response(http_ret);
2367 }
2368
2369 int error_handler(const int err_no,
2370 std::string* const error_content) override {
2371 /* Enforce that any error generated while getting the error page will
2372 * not be send to a client. This allows us to recover from the double
2373 * fault situation by sending the original message. */
2374 return 0;
2375 }
2376 } get_errpage_op(store, handler, s, http_ret);
2377
2378 s->object = std::to_string(http_ret) + error_doc;
2379
2380 RGWOp* newop = &get_errpage_op;
2381 RGWRequest req(0);
2382 return rgw_process_authenticated(handler, newop, &req, s, true);
2383}
2384
2385int RGWSwiftWebsiteHandler::error_handler(const int err_no,
2386 std::string* const error_content)
2387{
2388 const auto& ws_conf = s->bucket_info.website_conf;
2389
2390 if (can_be_website_req() && ! ws_conf.error_doc.empty()) {
31f18b77
FG
2391 set_req_state_err(s, err_no);
2392 return serve_errordoc(s->err.http_ret, ws_conf.error_doc);
7c673cae
FG
2393 }
2394
2395 /* Let's go to the default, no-op handler. */
2396 return err_no;
2397}
2398
2399bool RGWSwiftWebsiteHandler::is_web_mode() const
2400{
2401 const boost::string_ref webmode = s->info.env->get("HTTP_X_WEB_MODE", "");
2402 return boost::algorithm::iequals(webmode, "true");
2403}
2404
2405bool RGWSwiftWebsiteHandler::can_be_website_req() const
2406{
2407 /* Static website works only with the GET or HEAD method. Nothing more. */
2408 static const std::set<boost::string_ref> ws_methods = { "GET", "HEAD" };
2409 if (ws_methods.count(s->info.method) == 0) {
2410 return false;
2411 }
2412
2413 /* We also need to handle early failures from the auth system. In such cases
2414 * req_state::auth.identity may be empty. Let's treat that the same way as
2415 * the anonymous access. */
2416 if (! s->auth.identity) {
2417 return true;
2418 }
2419
2420 /* Swift serves websites only for anonymous requests unless client explicitly
2421 * requested this behaviour by supplying X-Web-Mode HTTP header set to true. */
2422 if (s->auth.identity->is_anonymous() || is_web_mode()) {
2423 return true;
2424 }
2425
2426 return false;
2427}
2428
2429RGWOp* RGWSwiftWebsiteHandler::get_ws_redirect_op()
2430{
2431 class RGWMovedPermanently: public RGWOp {
2432 const std::string location;
2433 public:
11fdf7f2 2434 explicit RGWMovedPermanently(const std::string& location)
7c673cae
FG
2435 : location(location) {
2436 }
2437
2438 int verify_permission() override {
2439 return 0;
2440 }
2441
2442 void execute() override {
2443 op_ret = -ERR_PERMANENT_REDIRECT;
2444 return;
2445 }
2446
2447 void send_response() override {
2448 set_req_state_err(s, op_ret);
2449 dump_errno(s);
2450 dump_content_length(s, 0);
2451 dump_redirect(s, location);
2452 end_header(s, this);
2453 }
2454
11fdf7f2 2455 const char* name() const override {
7c673cae
FG
2456 return "RGWMovedPermanently";
2457 }
2458 };
2459
2460 return new RGWMovedPermanently(s->info.request_uri + '/');
2461}
2462
2463RGWOp* RGWSwiftWebsiteHandler::get_ws_index_op()
2464{
2465 /* Retarget to get obj on requested index file. */
2466 if (! s->object.empty()) {
2467 s->object = s->object.name +
2468 s->bucket_info.website_conf.get_index_doc();
2469 } else {
2470 s->object = s->bucket_info.website_conf.get_index_doc();
2471 }
2472
2473 auto getop = new RGWGetObj_ObjStore_SWIFT;
2474 getop->set_get_data(boost::algorithm::equals("GET", s->info.method));
2475
2476 return getop;
2477}
2478
2479RGWOp* RGWSwiftWebsiteHandler::get_ws_listing_op()
2480{
2481 class RGWWebsiteListing : public RGWListBucket_ObjStore_SWIFT {
2482 const std::string prefix_override;
2483
2484 int get_params() override {
2485 prefix = prefix_override;
2486 max = default_max;
2487 delimiter = "/";
2488 return 0;
2489 }
2490
2491 void send_response() override {
2492 /* Generate the header now. */
2493 set_req_state_err(s, op_ret);
2494 dump_errno(s);
2495 dump_container_metadata(s, bucket, bucket_quota,
2496 s->bucket_info.website_conf);
2497 end_header(s, this, "text/html");
2498 if (op_ret < 0) {
2499 return;
2500 }
2501
2502 /* Now it's the time to start generating HTML bucket listing.
2503 * All the crazy stuff with crafting tags will be delegated to
2504 * RGWSwiftWebsiteListingFormatter. */
2505 std::stringstream ss;
2506 RGWSwiftWebsiteListingFormatter htmler(ss, prefix);
2507
2508 const auto& ws_conf = s->bucket_info.website_conf;
2509 htmler.generate_header(s->decoded_uri,
2510 ws_conf.listing_css_doc);
2511
2512 for (const auto& pair : common_prefixes) {
2513 std::string subdir_name = pair.first;
2514 if (! subdir_name.empty()) {
2515 /* To be compliant with Swift we need to remove the trailing
2516 * slash. */
2517 subdir_name.pop_back();
2518 }
2519
2520 htmler.dump_subdir(subdir_name);
2521 }
2522
2523 for (const rgw_bucket_dir_entry& obj : objs) {
2524 if (! common_prefixes.count(obj.key.name + '/')) {
2525 htmler.dump_object(obj);
2526 }
2527 }
2528
2529 htmler.generate_footer();
2530 dump_body(s, ss.str());
2531 }
2532 public:
2533 /* Taking prefix_override by value to leverage std::string r-value ref
2534 * ctor and thus avoid extra memory copying/increasing ref counter. */
11fdf7f2 2535 explicit RGWWebsiteListing(std::string prefix_override)
7c673cae
FG
2536 : prefix_override(std::move(prefix_override)) {
2537 }
2538 };
2539
2540 std::string prefix = std::move(s->object.name);
2541 s->object = rgw_obj_key();
2542
2543 return new RGWWebsiteListing(std::move(prefix));
2544}
2545
2546bool RGWSwiftWebsiteHandler::is_web_dir() const
2547{
31f18b77 2548 std::string subdir_name = url_decode(s->object.name);
7c673cae
FG
2549
2550 /* Remove character from the subdir name if it is "/". */
2551 if (subdir_name.empty()) {
2552 return false;
2553 } else if (subdir_name.back() == '/') {
2554 subdir_name.pop_back();
f7e2c81a
TL
2555 if (subdir_name.empty()) {
2556 return false;
2557 }
7c673cae
FG
2558 }
2559
31f18b77 2560 rgw_obj obj(s->bucket, std::move(subdir_name));
7c673cae
FG
2561
2562 /* First, get attrset of the object we'll try to retrieve. */
2563 RGWObjectCtx& obj_ctx = *static_cast<RGWObjectCtx *>(s->obj_ctx);
11fdf7f2
TL
2564 obj_ctx.set_atomic(obj);
2565 obj_ctx.set_prefetch_data(obj);
7c673cae
FG
2566
2567 RGWObjState* state = nullptr;
9f95a23c 2568 if (store->getRados()->get_obj_state(&obj_ctx, s->bucket_info, obj, &state, false, s->yield) < 0) {
7c673cae
FG
2569 return false;
2570 }
2571
2572 /* A nonexistent object cannot be a considered as a marker representing
2573 * the emulation of catalog in FS hierarchy. */
2574 if (! state->exists) {
2575 return false;
2576 }
2577
2578 /* Decode the content type. */
2579 std::string content_type;
2580 get_contype_from_attrs(state->attrset, content_type);
2581
2582 const auto& ws_conf = s->bucket_info.website_conf;
2583 const std::string subdir_marker = ws_conf.subdir_marker.empty()
2584 ? "application/directory"
2585 : ws_conf.subdir_marker;
2586 return subdir_marker == content_type && state->size <= 1;
2587}
2588
9f95a23c 2589bool RGWSwiftWebsiteHandler::is_index_present(const std::string& index) const
7c673cae
FG
2590{
2591 rgw_obj obj(s->bucket, index);
2592
2593 RGWObjectCtx& obj_ctx = *static_cast<RGWObjectCtx *>(s->obj_ctx);
11fdf7f2
TL
2594 obj_ctx.set_atomic(obj);
2595 obj_ctx.set_prefetch_data(obj);
7c673cae
FG
2596
2597 RGWObjState* state = nullptr;
9f95a23c 2598 if (store->getRados()->get_obj_state(&obj_ctx, s->bucket_info, obj, &state, false, s->yield) < 0) {
7c673cae
FG
2599 return false;
2600 }
2601
2602 /* A nonexistent object cannot be a considered as a viable index. We will
2603 * try to list the bucket or - if this is impossible - return an error. */
2604 return state->exists;
2605}
2606
2607int RGWSwiftWebsiteHandler::retarget_bucket(RGWOp* op, RGWOp** new_op)
2608{
9f95a23c 2609 ldpp_dout(s, 10) << "Starting retarget" << dendl;
7c673cae
FG
2610 RGWOp* op_override = nullptr;
2611
2612 /* In Swift static web content is served if the request is anonymous or
2613 * has X-Web-Mode HTTP header specified to true. */
2614 if (can_be_website_req()) {
2615 const auto& ws_conf = s->bucket_info.website_conf;
2616 const auto& index = s->bucket_info.website_conf.get_index_doc();
2617
2618 if (s->decoded_uri.back() != '/') {
2619 op_override = get_ws_redirect_op();
2620 } else if (! index.empty() && is_index_present(index)) {
2621 op_override = get_ws_index_op();
2622 } else if (ws_conf.listing_enabled) {
2623 op_override = get_ws_listing_op();
2624 }
2625 }
2626
2627 if (op_override) {
2628 handler->put_op(op);
2629 op_override->init(store, s, handler);
2630
2631 *new_op = op_override;
2632 } else {
2633 *new_op = op;
2634 }
2635
2636 /* Return 404 Not Found is the request has web mode enforced but we static web
2637 * wasn't able to serve it accordingly. */
2638 return ! op_override && is_web_mode() ? -ENOENT : 0;
2639}
2640
2641int RGWSwiftWebsiteHandler::retarget_object(RGWOp* op, RGWOp** new_op)
2642{
9f95a23c 2643 ldpp_dout(s, 10) << "Starting object retarget" << dendl;
7c673cae
FG
2644 RGWOp* op_override = nullptr;
2645
2646 /* In Swift static web content is served if the request is anonymous or
2647 * has X-Web-Mode HTTP header specified to true. */
2648 if (can_be_website_req() && is_web_dir()) {
2649 const auto& ws_conf = s->bucket_info.website_conf;
2650 const auto& index = s->bucket_info.website_conf.get_index_doc();
2651
2652 if (s->decoded_uri.back() != '/') {
2653 op_override = get_ws_redirect_op();
2654 } else if (! index.empty() && is_index_present(index)) {
2655 op_override = get_ws_index_op();
2656 } else if (ws_conf.listing_enabled) {
2657 op_override = get_ws_listing_op();
2658 }
2659 } else {
2660 /* A regular request or the specified object isn't a subdirectory marker.
2661 * We don't need any re-targeting. Error handling (like sending a custom
2662 * error page) will be performed by error_handler of the actual RGWOp. */
2663 return 0;
2664 }
2665
2666 if (op_override) {
2667 handler->put_op(op);
2668 op_override->init(store, s, handler);
2669
2670 *new_op = op_override;
2671 } else {
2672 *new_op = op;
2673 }
2674
2675 /* Return 404 Not Found if we aren't able to re-target for subdir marker. */
2676 return ! op_override ? -ENOENT : 0;
2677}
2678
2679
2680RGWOp *RGWHandler_REST_Bucket_SWIFT::get_obj_op(bool get_data)
2681{
2682 if (is_acl_op()) {
2683 return new RGWGetACLs_ObjStore_SWIFT;
2684 }
2685
2686 if (get_data)
2687 return new RGWListBucket_ObjStore_SWIFT;
2688 else
2689 return new RGWStatBucket_ObjStore_SWIFT;
2690}
2691
2692RGWOp *RGWHandler_REST_Bucket_SWIFT::op_get()
2693{
2694 return get_obj_op(true);
2695}
2696
2697RGWOp *RGWHandler_REST_Bucket_SWIFT::op_head()
2698{
2699 return get_obj_op(false);
2700}
2701
2702RGWOp *RGWHandler_REST_Bucket_SWIFT::op_put()
2703{
2704 if (is_acl_op()) {
2705 return new RGWPutACLs_ObjStore_SWIFT;
2706 }
2707 if(s->info.args.exists("extract-archive")) {
2708 return new RGWBulkUploadOp_ObjStore_SWIFT;
2709 }
2710 return new RGWCreateBucket_ObjStore_SWIFT;
2711}
2712
2713RGWOp *RGWHandler_REST_Bucket_SWIFT::op_delete()
2714{
2715 return new RGWDeleteBucket_ObjStore_SWIFT;
2716}
2717
2718RGWOp *RGWHandler_REST_Bucket_SWIFT::op_post()
2719{
2720 if (RGWFormPost::is_formpost_req(s)) {
2721 return new RGWFormPost;
2722 } else {
2723 return new RGWPutMetadataBucket_ObjStore_SWIFT;
2724 }
2725}
2726
2727RGWOp *RGWHandler_REST_Bucket_SWIFT::op_options()
2728{
2729 return new RGWOptionsCORS_ObjStore_SWIFT;
2730}
2731
2732
2733RGWOp *RGWHandler_REST_Obj_SWIFT::get_obj_op(bool get_data)
2734{
2735 if (is_acl_op()) {
2736 return new RGWGetACLs_ObjStore_SWIFT;
2737 }
2738
2739 RGWGetObj_ObjStore_SWIFT *get_obj_op = new RGWGetObj_ObjStore_SWIFT;
2740 get_obj_op->set_get_data(get_data);
2741 return get_obj_op;
2742}
2743
2744RGWOp *RGWHandler_REST_Obj_SWIFT::op_get()
2745{
2746 return get_obj_op(true);
2747}
2748
2749RGWOp *RGWHandler_REST_Obj_SWIFT::op_head()
2750{
2751 return get_obj_op(false);
2752}
2753
2754RGWOp *RGWHandler_REST_Obj_SWIFT::op_put()
2755{
2756 if (is_acl_op()) {
2757 return new RGWPutACLs_ObjStore_SWIFT;
2758 }
2759 if(s->info.args.exists("extract-archive")) {
2760 return new RGWBulkUploadOp_ObjStore_SWIFT;
2761 }
2762 if (s->init_state.src_bucket.empty())
2763 return new RGWPutObj_ObjStore_SWIFT;
2764 else
2765 return new RGWCopyObj_ObjStore_SWIFT;
2766}
2767
2768RGWOp *RGWHandler_REST_Obj_SWIFT::op_delete()
2769{
2770 return new RGWDeleteObj_ObjStore_SWIFT;
2771}
2772
2773RGWOp *RGWHandler_REST_Obj_SWIFT::op_post()
2774{
2775 if (RGWFormPost::is_formpost_req(s)) {
2776 return new RGWFormPost;
2777 } else {
2778 return new RGWPutMetadataObject_ObjStore_SWIFT;
2779 }
2780}
2781
2782RGWOp *RGWHandler_REST_Obj_SWIFT::op_copy()
2783{
2784 return new RGWCopyObj_ObjStore_SWIFT;
2785}
2786
2787RGWOp *RGWHandler_REST_Obj_SWIFT::op_options()
2788{
2789 return new RGWOptionsCORS_ObjStore_SWIFT;
2790}
2791
2792
11fdf7f2 2793int RGWHandler_REST_SWIFT::authorize(const DoutPrefixProvider *dpp)
7c673cae 2794{
11fdf7f2 2795 return rgw::auth::Strategy::apply(dpp, auth_strategy, s);
7c673cae
FG
2796}
2797
2798int RGWHandler_REST_SWIFT::postauth_init()
2799{
2800 struct req_init_state* t = &s->init_state;
2801
2802 /* XXX Stub this until Swift Auth sets account into URL. */
9f95a23c 2803 s->bucket_tenant = s->user->get_tenant();
7c673cae
FG
2804 s->bucket_name = t->url_bucket;
2805
2806 dout(10) << "s->object=" <<
2807 (!s->object.empty() ? s->object : rgw_obj_key("<NULL>"))
2808 << " s->bucket="
2809 << rgw_make_bucket_entry_name(s->bucket_tenant, s->bucket_name)
2810 << dendl;
2811
2812 int ret;
d2e6a577 2813 ret = rgw_validate_tenant_name(s->bucket_tenant);
7c673cae
FG
2814 if (ret)
2815 return ret;
2816 ret = validate_bucket_name(s->bucket_name);
2817 if (ret)
2818 return ret;
2819 ret = validate_object_name(s->object.name);
2820 if (ret)
2821 return ret;
2822
2823 if (!t->src_bucket.empty()) {
2824 /*
2825 * We don't allow cross-tenant copy at present. It requires account
2826 * names in the URL for Swift.
2827 */
9f95a23c 2828 s->src_tenant_name = s->user->get_tenant();
7c673cae
FG
2829 s->src_bucket_name = t->src_bucket;
2830
2831 ret = validate_bucket_name(s->src_bucket_name);
2832 if (ret < 0) {
2833 return ret;
2834 }
2835 ret = validate_object_name(s->src_object.name);
2836 if (ret < 0) {
2837 return ret;
2838 }
2839 }
2840
2841 return 0;
2842}
2843
2844int RGWHandler_REST_SWIFT::validate_bucket_name(const string& bucket)
2845{
3efd9988 2846 const size_t len = bucket.size();
7c673cae 2847
3efd9988
FG
2848 if (len > MAX_BUCKET_NAME_LEN) {
2849 /* Bucket Name too long. Generate custom error message and bind it
2850 * to an R-value reference. */
2851 const auto msg = boost::str(
2852 boost::format("Container name length of %lld longer than %lld")
2853 % len % int(MAX_BUCKET_NAME_LEN));
2854 set_req_state_err(s, ERR_INVALID_BUCKET_NAME, msg);
2855 return -ERR_INVALID_BUCKET_NAME;
2856 }
2857
2858 const auto ret = RGWHandler_REST::validate_bucket_name(bucket);
2859 if (ret < 0) {
2860 return ret;
2861 }
7c673cae
FG
2862
2863 if (len == 0)
2864 return 0;
2865
2866 if (bucket[0] == '.')
2867 return -ERR_INVALID_BUCKET_NAME;
2868
2869 if (check_utf8(bucket.c_str(), len))
2870 return -ERR_INVALID_UTF8;
2871
2872 const char *s = bucket.c_str();
2873
3efd9988 2874 for (size_t i = 0; i < len; ++i, ++s) {
7c673cae
FG
2875 if (*(unsigned char *)s == 0xff)
2876 return -ERR_INVALID_BUCKET_NAME;
11fdf7f2
TL
2877 if (*(unsigned char *)s == '/')
2878 return -ERR_INVALID_BUCKET_NAME;
7c673cae
FG
2879 }
2880
2881 return 0;
2882}
2883
2884static void next_tok(string& str, string& tok, char delim)
2885{
2886 if (str.size() == 0) {
2887 tok = "";
2888 return;
2889 }
2890 tok = str;
2891 int pos = str.find(delim);
2892 if (pos > 0) {
2893 tok = str.substr(0, pos);
2894 str = str.substr(pos + 1);
2895 } else {
2896 str = "";
2897 }
2898}
2899
2900int RGWHandler_REST_SWIFT::init_from_header(struct req_state* const s,
2901 const std::string& frontend_prefix)
2902{
2903 string req;
2904 string first;
2905
2906 s->prot_flags |= RGW_REST_SWIFT;
2907
2908 char reqbuf[frontend_prefix.length() + s->decoded_uri.length() + 1];
2909 sprintf(reqbuf, "%s%s", frontend_prefix.c_str(), s->decoded_uri.c_str());
2910 const char *req_name = reqbuf;
2911
2912 const char *p;
2913
2914 if (*req_name == '?') {
2915 p = req_name;
2916 } else {
2917 p = s->info.request_params.c_str();
2918 }
2919
2920 s->info.args.set(p);
2921 s->info.args.parse();
2922
2923 /* Skip the leading slash of URL hierarchy. */
2924 if (req_name[0] != '/') {
2925 return 0;
2926 } else {
2927 req_name++;
2928 }
2929
2930 if ('\0' == req_name[0]) {
11fdf7f2 2931 return g_conf()->rgw_swift_url_prefix == "/" ? -ERR_BAD_URL : 0;
7c673cae
FG
2932 }
2933
2934 req = req_name;
2935
2936 size_t pos = req.find('/');
11fdf7f2
TL
2937 if (std::string::npos != pos && g_conf()->rgw_swift_url_prefix != "/") {
2938 bool cut_url = g_conf()->rgw_swift_url_prefix.length();
7c673cae
FG
2939 first = req.substr(0, pos);
2940
11fdf7f2 2941 if (first.compare(g_conf()->rgw_swift_url_prefix) == 0) {
7c673cae
FG
2942 if (cut_url) {
2943 /* Rewind to the "v1/..." part. */
2944 next_tok(req, first, '/');
2945 }
2946 }
11fdf7f2 2947 } else if (req.compare(g_conf()->rgw_swift_url_prefix) == 0) {
7c673cae
FG
2948 s->formatter = new RGWFormatter_Plain;
2949 return -ERR_BAD_URL;
2950 } else {
2951 first = req;
2952 }
2953
2954 std::string tenant_path;
11fdf7f2 2955 if (! g_conf()->rgw_swift_tenant_name.empty()) {
7c673cae 2956 tenant_path = "/AUTH_";
11fdf7f2 2957 tenant_path.append(g_conf()->rgw_swift_tenant_name);
7c673cae
FG
2958 }
2959
2960 /* verify that the request_uri conforms with what's expected */
11fdf7f2 2961 char buf[g_conf()->rgw_swift_url_prefix.length() + 16 + tenant_path.length()];
7c673cae 2962 int blen;
11fdf7f2 2963 if (g_conf()->rgw_swift_url_prefix == "/") {
7c673cae
FG
2964 blen = sprintf(buf, "/v1%s", tenant_path.c_str());
2965 } else {
2966 blen = sprintf(buf, "/%s/v1%s",
11fdf7f2 2967 g_conf()->rgw_swift_url_prefix.c_str(), tenant_path.c_str());
7c673cae
FG
2968 }
2969
2970 if (strncmp(reqbuf, buf, blen) != 0) {
2971 return -ENOENT;
2972 }
2973
2974 int ret = allocate_formatter(s, RGW_FORMAT_PLAIN, true);
2975 if (ret < 0)
2976 return ret;
2977
2978 string ver;
2979
2980 next_tok(req, ver, '/');
2981
11fdf7f2 2982 if (!tenant_path.empty() || g_conf()->rgw_swift_account_in_url) {
7c673cae
FG
2983 string account_name;
2984 next_tok(req, account_name, '/');
2985
2986 /* Erase all pre-defined prefixes like "AUTH_" or "KEY_". */
2987 const vector<string> skipped_prefixes = { "AUTH_", "KEY_" };
2988
2989 for (const auto pfx : skipped_prefixes) {
2990 const size_t comp_len = min(account_name.length(), pfx.length());
2991 if (account_name.compare(0, comp_len, pfx) == 0) {
2992 /* Prefix is present. Drop it. */
2993 account_name = account_name.substr(comp_len);
2994 break;
2995 }
2996 }
2997
2998 if (account_name.empty()) {
2999 return -ERR_PRECONDITION_FAILED;
3000 } else {
3001 s->account_name = account_name;
3002 }
3003 }
3004
3005 next_tok(req, first, '/');
3006
3007 dout(10) << "ver=" << ver << " first=" << first << " req=" << req << dendl;
3008 if (first.size() == 0)
3009 return 0;
3010
3011 s->info.effective_uri = "/" + first;
3012
3013 // Save bucket to tide us over until token is parsed.
3014 s->init_state.url_bucket = first;
3015
3016 if (req.size()) {
3017 s->object =
3018 rgw_obj_key(req, s->info.env->get("HTTP_X_OBJECT_VERSION_ID", "")); /* rgw swift extension */
3019 s->info.effective_uri.append("/" + s->object.name);
3020 }
3021
3022 return 0;
3023}
3024
9f95a23c 3025int RGWHandler_REST_SWIFT::init(rgw::sal::RGWRadosStore* store, struct req_state* s,
7c673cae
FG
3026 rgw::io::BasicClient *cio)
3027{
3028 struct req_init_state *t = &s->init_state;
3029
3030 s->dialect = "swift";
3031
494da23a 3032 std::string copy_source = s->info.env->get("HTTP_X_COPY_FROM", "");
3a9019d9 3033 if (! copy_source.empty()) {
7c673cae
FG
3034 bool result = RGWCopyObj::parse_copy_location(copy_source, t->src_bucket,
3035 s->src_object);
3036 if (!result)
3037 return -ERR_BAD_URL;
3038 }
3039
3040 if (s->op == OP_COPY) {
494da23a 3041 std::string req_dest = s->info.env->get("HTTP_DESTINATION", "");
3a9019d9 3042 if (req_dest.empty())
7c673cae
FG
3043 return -ERR_BAD_URL;
3044
3a9019d9 3045 std::string dest_bucket_name;
7c673cae
FG
3046 rgw_obj_key dest_obj_key;
3047 bool result =
3048 RGWCopyObj::parse_copy_location(req_dest, dest_bucket_name,
3049 dest_obj_key);
3050 if (!result)
3051 return -ERR_BAD_URL;
3052
3a9019d9 3053 std::string dest_object = dest_obj_key.name;
7c673cae
FG
3054
3055 /* convert COPY operation into PUT */
3056 t->src_bucket = t->url_bucket;
3057 s->src_object = s->object;
3058 t->url_bucket = dest_bucket_name;
3059 s->object = rgw_obj_key(dest_object);
3060 s->op = OP_PUT;
3061 }
3062
11fdf7f2
TL
3063 s->info.storage_class = s->info.env->get("HTTP_X_OBJECT_STORAGE_CLASS", "");
3064
7c673cae
FG
3065 return RGWHandler_REST::init(store, s, cio);
3066}
3067
3068RGWHandler_REST*
3069RGWRESTMgr_SWIFT::get_handler(struct req_state* const s,
3070 const rgw::auth::StrategyRegistry& auth_registry,
3071 const std::string& frontend_prefix)
3072{
3073 int ret = RGWHandler_REST_SWIFT::init_from_header(s, frontend_prefix);
3074 if (ret < 0) {
9f95a23c 3075 ldpp_dout(s, 10) << "init_from_header returned err=" << ret << dendl;
7c673cae
FG
3076 return nullptr;
3077 }
3078
3079 const auto& auth_strategy = auth_registry.get_swift();
3080
3081 if (s->init_state.url_bucket.empty()) {
3082 return new RGWHandler_REST_Service_SWIFT(auth_strategy);
3083 }
3084
3085 if (s->object.empty()) {
3086 return new RGWHandler_REST_Bucket_SWIFT(auth_strategy);
3087 }
3088
3089 return new RGWHandler_REST_Obj_SWIFT(auth_strategy);
3090}
3091
3092RGWHandler_REST* RGWRESTMgr_SWIFT_Info::get_handler(
3093 struct req_state* const s,
3094 const rgw::auth::StrategyRegistry& auth_registry,
3095 const std::string& frontend_prefix)
3096{
3097 s->prot_flags |= RGW_REST_SWIFT;
3098 const auto& auth_strategy = auth_registry.get_swift();
3099 return new RGWHandler_REST_SWIFT_Info(auth_strategy);
3100}