]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_rest.cc
cd7e730d895546b1428dd6983dbf7f748df45e7c
[ceph.git] / ceph / src / rgw / rgw_rest.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
3
4
5 #include <errno.h>
6 #include <limits.h>
7
8 #include <boost/algorithm/string.hpp>
9 #include <boost/tokenizer.hpp>
10 #include "common/Formatter.h"
11 #include "common/HTMLFormatter.h"
12 #include "common/utf8.h"
13 #include "include/str_list.h"
14 #include "rgw_common.h"
15 #include "rgw_rados.h"
16 #include "rgw_zone.h"
17 #include "rgw_auth_s3.h"
18 #include "rgw_formats.h"
19 #include "rgw_op.h"
20 #include "rgw_rest.h"
21 #include "rgw_rest_swift.h"
22 #include "rgw_rest_s3.h"
23 #include "rgw_swift_auth.h"
24 #include "rgw_cors_s3.h"
25 #include "rgw_perf_counters.h"
26
27 #include "rgw_client_io.h"
28 #include "rgw_resolve.h"
29 #include "rgw_sal_rados.h"
30
31 #include <numeric>
32
33 #define dout_subsys ceph_subsys_rgw
34
35 struct rgw_http_status_code {
36 int code;
37 const char *name;
38 };
39
40 const static struct rgw_http_status_code http_codes[] = {
41 { 100, "Continue" },
42 { 200, "OK" },
43 { 201, "Created" },
44 { 202, "Accepted" },
45 { 204, "No Content" },
46 { 205, "Reset Content" },
47 { 206, "Partial Content" },
48 { 207, "Multi Status" },
49 { 208, "Already Reported" },
50 { 300, "Multiple Choices" },
51 { 301, "Moved Permanently" },
52 { 302, "Found" },
53 { 303, "See Other" },
54 { 304, "Not Modified" },
55 { 305, "User Proxy" },
56 { 306, "Switch Proxy" },
57 { 307, "Temporary Redirect" },
58 { 308, "Permanent Redirect" },
59 { 400, "Bad Request" },
60 { 401, "Unauthorized" },
61 { 402, "Payment Required" },
62 { 403, "Forbidden" },
63 { 404, "Not Found" },
64 { 405, "Method Not Allowed" },
65 { 406, "Not Acceptable" },
66 { 407, "Proxy Authentication Required" },
67 { 408, "Request Timeout" },
68 { 409, "Conflict" },
69 { 410, "Gone" },
70 { 411, "Length Required" },
71 { 412, "Precondition Failed" },
72 { 413, "Request Entity Too Large" },
73 { 414, "Request-URI Too Long" },
74 { 415, "Unsupported Media Type" },
75 { 416, "Requested Range Not Satisfiable" },
76 { 417, "Expectation Failed" },
77 { 422, "Unprocessable Entity" },
78 { 498, "Rate Limited"},
79 { 500, "Internal Server Error" },
80 { 501, "Not Implemented" },
81 { 503, "Slow Down"},
82 { 0, NULL },
83 };
84
85 struct rgw_http_attr {
86 const char *rgw_attr;
87 const char *http_attr;
88 };
89
90 /*
91 * mapping between rgw object attrs and output http fields
92 */
93 static const struct rgw_http_attr base_rgw_to_http_attrs[] = {
94 { RGW_ATTR_CONTENT_LANG, "Content-Language" },
95 { RGW_ATTR_EXPIRES, "Expires" },
96 { RGW_ATTR_CACHE_CONTROL, "Cache-Control" },
97 { RGW_ATTR_CONTENT_DISP, "Content-Disposition" },
98 { RGW_ATTR_CONTENT_ENC, "Content-Encoding" },
99 { RGW_ATTR_USER_MANIFEST, "X-Object-Manifest" },
100 { RGW_ATTR_X_ROBOTS_TAG , "X-Robots-Tag" },
101 { RGW_ATTR_STORAGE_CLASS , "X-Amz-Storage-Class" },
102 /* RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION header depends on access mode:
103 * S3 endpoint: x-amz-website-redirect-location
104 * S3Website endpoint: Location
105 */
106 { RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION, "x-amz-website-redirect-location" },
107 };
108
109
110 struct generic_attr {
111 const char *http_header;
112 const char *rgw_attr;
113 };
114
115 /*
116 * mapping between http env fields and rgw object attrs
117 */
118 static const struct generic_attr generic_attrs[] = {
119 { "CONTENT_TYPE", RGW_ATTR_CONTENT_TYPE },
120 { "HTTP_CONTENT_LANGUAGE", RGW_ATTR_CONTENT_LANG },
121 { "HTTP_EXPIRES", RGW_ATTR_EXPIRES },
122 { "HTTP_CACHE_CONTROL", RGW_ATTR_CACHE_CONTROL },
123 { "HTTP_CONTENT_DISPOSITION", RGW_ATTR_CONTENT_DISP },
124 { "HTTP_CONTENT_ENCODING", RGW_ATTR_CONTENT_ENC },
125 { "HTTP_X_ROBOTS_TAG", RGW_ATTR_X_ROBOTS_TAG },
126 };
127
128 map<string, string> rgw_to_http_attrs;
129 static map<string, string> generic_attrs_map;
130 map<int, const char *> http_status_names;
131
132 /*
133 * make attrs look_like_this
134 * converts dashes to underscores
135 */
136 string lowercase_underscore_http_attr(const string& orig)
137 {
138 const char *s = orig.c_str();
139 char buf[orig.size() + 1];
140 buf[orig.size()] = '\0';
141
142 for (size_t i = 0; i < orig.size(); ++i, ++s) {
143 switch (*s) {
144 case '-':
145 buf[i] = '_';
146 break;
147 default:
148 buf[i] = tolower(*s);
149 }
150 }
151 return string(buf);
152 }
153
154 /*
155 * make attrs LOOK_LIKE_THIS
156 * converts dashes to underscores
157 */
158 string uppercase_underscore_http_attr(const string& orig)
159 {
160 const char *s = orig.c_str();
161 char buf[orig.size() + 1];
162 buf[orig.size()] = '\0';
163
164 for (size_t i = 0; i < orig.size(); ++i, ++s) {
165 switch (*s) {
166 case '-':
167 buf[i] = '_';
168 break;
169 default:
170 buf[i] = toupper(*s);
171 }
172 }
173 return string(buf);
174 }
175
176 /* avoid duplicate hostnames in hostnames lists */
177 static set<string> hostnames_set;
178 static set<string> hostnames_s3website_set;
179
180 void rgw_rest_init(CephContext *cct, const RGWZoneGroup& zone_group)
181 {
182 for (const auto& rgw2http : base_rgw_to_http_attrs) {
183 rgw_to_http_attrs[rgw2http.rgw_attr] = rgw2http.http_attr;
184 }
185
186 for (const auto& http2rgw : generic_attrs) {
187 generic_attrs_map[http2rgw.http_header] = http2rgw.rgw_attr;
188 }
189
190 list<string> extended_http_attrs;
191 get_str_list(cct->_conf->rgw_extended_http_attrs, extended_http_attrs);
192
193 list<string>::iterator iter;
194 for (iter = extended_http_attrs.begin(); iter != extended_http_attrs.end(); ++iter) {
195 string rgw_attr = RGW_ATTR_PREFIX;
196 rgw_attr.append(lowercase_underscore_http_attr(*iter));
197
198 rgw_to_http_attrs[rgw_attr] = camelcase_dash_http_attr(*iter);
199
200 string http_header = "HTTP_";
201 http_header.append(uppercase_underscore_http_attr(*iter));
202
203 generic_attrs_map[http_header] = rgw_attr;
204 }
205
206 for (const struct rgw_http_status_code *h = http_codes; h->code; h++) {
207 http_status_names[h->code] = h->name;
208 }
209
210 hostnames_set.insert(cct->_conf->rgw_dns_name);
211 hostnames_set.insert(zone_group.hostnames.begin(), zone_group.hostnames.end());
212 hostnames_set.erase(""); // filter out empty hostnames
213 ldout(cct, 20) << "RGW hostnames: " << hostnames_set << dendl;
214 /* TODO: We should have a sanity check that no hostname matches the end of
215 * any other hostname, otherwise we will get ambigious results from
216 * rgw_find_host_in_domains.
217 * Eg:
218 * Hostnames: [A, B.A]
219 * Inputs: [Z.A, X.B.A]
220 * Z.A clearly splits to subdomain=Z, domain=Z
221 * X.B.A ambigously splits to both {X, B.A} and {X.B, A}
222 */
223
224 hostnames_s3website_set.insert(cct->_conf->rgw_dns_s3website_name);
225 hostnames_s3website_set.insert(zone_group.hostnames_s3website.begin(), zone_group.hostnames_s3website.end());
226 hostnames_s3website_set.erase(""); // filter out empty hostnames
227 ldout(cct, 20) << "RGW S3website hostnames: " << hostnames_s3website_set << dendl;
228 /* TODO: we should repeat the hostnames_set sanity check here
229 * and ALSO decide about overlap, if any
230 */
231 }
232
233 static bool str_ends_with_nocase(const string& s, const string& suffix, size_t *pos)
234 {
235 size_t len = suffix.size();
236 if (len > (size_t)s.size()) {
237 return false;
238 }
239
240 ssize_t p = s.size() - len;
241 if (pos) {
242 *pos = p;
243 }
244
245 return boost::algorithm::iends_with(s, suffix);
246 }
247
248 static bool rgw_find_host_in_domains(const string& host, string *domain, string *subdomain,
249 const set<string>& valid_hostnames_set)
250 {
251 set<string>::iterator iter;
252 /** TODO, Future optimization
253 * store hostnames_set elements _reversed_, and look for a prefix match,
254 * which is much faster than a suffix match.
255 */
256 for (iter = valid_hostnames_set.begin(); iter != valid_hostnames_set.end(); ++iter) {
257 size_t pos;
258 if (!str_ends_with_nocase(host, *iter, &pos))
259 continue;
260
261 if (pos == 0) {
262 *domain = host;
263 subdomain->clear();
264 } else {
265 if (host[pos - 1] != '.') {
266 continue;
267 }
268
269 *domain = host.substr(pos);
270 *subdomain = host.substr(0, pos - 1);
271 }
272 return true;
273 }
274 return false;
275 }
276
277 static void dump_status(struct req_state *s, int status,
278 const char *status_name)
279 {
280 s->formatter->set_status(status, status_name);
281 try {
282 RESTFUL_IO(s)->send_status(status, status_name);
283 } catch (rgw::io::Exception& e) {
284 ldout(s->cct, 0) << "ERROR: s->cio->send_status() returned err="
285 << e.what() << dendl;
286 }
287 }
288
289 void rgw_flush_formatter_and_reset(struct req_state *s, Formatter *formatter)
290 {
291 std::ostringstream oss;
292 formatter->output_footer();
293 formatter->flush(oss);
294 std::string outs(oss.str());
295 if (!outs.empty() && s->op != OP_HEAD) {
296 dump_body(s, outs);
297 }
298
299 s->formatter->reset();
300 }
301
302 void rgw_flush_formatter(struct req_state *s, Formatter *formatter)
303 {
304 std::ostringstream oss;
305 formatter->flush(oss);
306 std::string outs(oss.str());
307 if (!outs.empty() && s->op != OP_HEAD) {
308 dump_body(s, outs);
309 }
310 }
311
312 void dump_errno(int http_ret, string& out) {
313 stringstream ss;
314
315 ss << http_ret << " " << http_status_names[http_ret];
316 out = ss.str();
317 }
318
319 void dump_errno(const struct rgw_err &err, string& out) {
320 dump_errno(err.http_ret, out);
321 }
322
323 void dump_errno(struct req_state *s)
324 {
325 dump_status(s, s->err.http_ret, http_status_names[s->err.http_ret]);
326 }
327
328 void dump_errno(struct req_state *s, int http_ret)
329 {
330 dump_status(s, http_ret, http_status_names[http_ret]);
331 }
332
333 void dump_header(struct req_state* const s,
334 const std::string_view& name,
335 const std::string_view& val)
336 {
337 try {
338 RESTFUL_IO(s)->send_header(name, val);
339 } catch (rgw::io::Exception& e) {
340 ldout(s->cct, 0) << "ERROR: s->cio->send_header() returned err="
341 << e.what() << dendl;
342 }
343 }
344
345 void dump_header(struct req_state* const s,
346 const std::string_view& name,
347 ceph::buffer::list& bl)
348 {
349 return dump_header(s, name, rgw_sanitized_hdrval(bl));
350 }
351
352 void dump_header(struct req_state* const s,
353 const std::string_view& name,
354 const long long val)
355 {
356 char buf[32];
357 const auto len = snprintf(buf, sizeof(buf), "%lld", val);
358
359 return dump_header(s, name, std::string_view(buf, len));
360 }
361
362 void dump_header(struct req_state* const s,
363 const std::string_view& name,
364 const utime_t& ut)
365 {
366 char buf[32];
367 const auto len = snprintf(buf, sizeof(buf), "%lld.%05d",
368 static_cast<long long>(ut.sec()),
369 static_cast<int>(ut.usec() / 10));
370
371 return dump_header(s, name, std::string_view(buf, len));
372 }
373
374 void dump_content_length(struct req_state* const s, const uint64_t len)
375 {
376 try {
377 RESTFUL_IO(s)->send_content_length(len);
378 } catch (rgw::io::Exception& e) {
379 ldout(s->cct, 0) << "ERROR: s->cio->send_content_length() returned err="
380 << e.what() << dendl;
381 }
382 dump_header(s, "Accept-Ranges", "bytes");
383 }
384
385 static void dump_chunked_encoding(struct req_state* const s)
386 {
387 try {
388 RESTFUL_IO(s)->send_chunked_transfer_encoding();
389 } catch (rgw::io::Exception& e) {
390 ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->send_chunked_transfer_encoding()"
391 << " returned err=" << e.what() << dendl;
392 }
393 }
394
395 void dump_etag(struct req_state* const s,
396 const std::string_view& etag,
397 const bool quoted)
398 {
399 if (etag.empty()) {
400 return;
401 }
402
403 if (s->prot_flags & RGW_REST_SWIFT && ! quoted) {
404 return dump_header(s, "etag", etag);
405 } else {
406 return dump_header_quoted(s, "ETag", etag);
407 }
408 }
409
410 void dump_bucket_from_state(struct req_state *s)
411 {
412 if (g_conf()->rgw_expose_bucket && ! s->bucket_name.empty()) {
413 if (! s->bucket_tenant.empty()) {
414 dump_header(s, "Bucket",
415 url_encode(s->bucket_tenant + "/" + s->bucket_name));
416 } else {
417 dump_header(s, "Bucket", url_encode(s->bucket_name));
418 }
419 }
420 }
421
422 void dump_redirect(struct req_state * const s, const std::string& redirect)
423 {
424 return dump_header_if_nonempty(s, "Location", redirect);
425 }
426
427 static size_t dump_time_header_impl(char (&timestr)[TIME_BUF_SIZE],
428 const real_time t)
429 {
430 const utime_t ut(t);
431 time_t secs = static_cast<time_t>(ut.sec());
432
433 struct tm result;
434 const struct tm * const tmp = gmtime_r(&secs, &result);
435 if (tmp == nullptr) {
436 return 0;
437 }
438
439 return strftime(timestr, sizeof(timestr),
440 "%a, %d %b %Y %H:%M:%S %Z", tmp);
441 }
442
443 void dump_time_header(struct req_state *s, const char *name, real_time t)
444 {
445 char timestr[TIME_BUF_SIZE];
446
447 const size_t len = dump_time_header_impl(timestr, t);
448 if (len == 0) {
449 return;
450 }
451
452 return dump_header(s, name, std::string_view(timestr, len));
453 }
454
455 std::string dump_time_to_str(const real_time& t)
456 {
457 char timestr[TIME_BUF_SIZE];
458 dump_time_header_impl(timestr, t);
459
460 return timestr;
461 }
462
463
464 void dump_last_modified(struct req_state *s, real_time t)
465 {
466 dump_time_header(s, "Last-Modified", t);
467 }
468
469 void dump_epoch_header(struct req_state *s, const char *name, real_time t)
470 {
471 utime_t ut(t);
472 char buf[65];
473 const auto len = snprintf(buf, sizeof(buf), "%lld.%09lld",
474 (long long)ut.sec(),
475 (long long)ut.nsec());
476
477 return dump_header(s, name, std::string_view(buf, len));
478 }
479
480 void dump_time(struct req_state *s, const char *name, real_time *t)
481 {
482 char buf[TIME_BUF_SIZE];
483 rgw_to_iso8601(*t, buf, sizeof(buf));
484
485 s->formatter->dump_string(name, buf);
486 }
487
488 void dump_owner(struct req_state *s, const rgw_user& id, string& name,
489 const char *section)
490 {
491 if (!section)
492 section = "Owner";
493 s->formatter->open_object_section(section);
494 s->formatter->dump_string("ID", id.to_str());
495 s->formatter->dump_string("DisplayName", name);
496 s->formatter->close_section();
497 }
498
499 void dump_access_control(struct req_state *s, const char *origin,
500 const char *meth,
501 const char *hdr, const char *exp_hdr,
502 uint32_t max_age) {
503 if (origin && (origin[0] != '\0')) {
504 dump_header(s, "Access-Control-Allow-Origin", origin);
505 /* If the server specifies an origin host rather than "*",
506 * then it must also include Origin in the Vary response header
507 * to indicate to clients that server responses will differ
508 * based on the value of the Origin request header.
509 */
510 if (strcmp(origin, "*") != 0) {
511 dump_header(s, "Vary", "Origin");
512 }
513
514 if (meth && (meth[0] != '\0')) {
515 dump_header(s, "Access-Control-Allow-Methods", meth);
516 }
517 if (hdr && (hdr[0] != '\0')) {
518 dump_header(s, "Access-Control-Allow-Headers", hdr);
519 }
520 if (exp_hdr && (exp_hdr[0] != '\0')) {
521 dump_header(s, "Access-Control-Expose-Headers", exp_hdr);
522 }
523 if (max_age != CORS_MAX_AGE_INVALID) {
524 dump_header(s, "Access-Control-Max-Age", max_age);
525 }
526 }
527 }
528
529 void dump_access_control(req_state *s, RGWOp *op)
530 {
531 string origin;
532 string method;
533 string header;
534 string exp_header;
535 unsigned max_age = CORS_MAX_AGE_INVALID;
536
537 if (!op->generate_cors_headers(origin, method, header, exp_header, &max_age))
538 return;
539
540 dump_access_control(s, origin.c_str(), method.c_str(), header.c_str(),
541 exp_header.c_str(), max_age);
542 }
543
544 void dump_start(struct req_state *s)
545 {
546 if (!s->content_started) {
547 s->formatter->output_header();
548 s->content_started = true;
549 }
550 }
551
552 void dump_trans_id(req_state *s)
553 {
554 if (s->prot_flags & RGW_REST_SWIFT) {
555 dump_header(s, "X-Trans-Id", s->trans_id);
556 dump_header(s, "X-Openstack-Request-Id", s->trans_id);
557 } else if (s->trans_id.length()) {
558 dump_header(s, "x-amz-request-id", s->trans_id);
559 }
560 }
561
562 void end_header(struct req_state* s, RGWOp* op, const char *content_type,
563 const int64_t proposed_content_length, bool force_content_type,
564 bool force_no_error)
565 {
566 string ctype;
567
568 dump_trans_id(s);
569
570 if ((!s->is_err()) && s->bucket &&
571 (s->bucket->get_info().owner != s->user->get_id()) &&
572 (s->bucket->get_info().requester_pays)) {
573 dump_header(s, "x-amz-request-charged", "requester");
574 }
575
576 if (op) {
577 dump_access_control(s, op);
578 }
579
580 if (s->prot_flags & RGW_REST_SWIFT && !content_type) {
581 force_content_type = true;
582 }
583
584 /* do not send content type if content length is zero
585 and the content type was not set by the user */
586 if (force_content_type ||
587 (!content_type && s->formatter->get_len() != 0) || s->is_err()){
588 switch (s->format) {
589 case RGW_FORMAT_XML:
590 ctype = "application/xml";
591 break;
592 case RGW_FORMAT_JSON:
593 ctype = "application/json";
594 break;
595 case RGW_FORMAT_HTML:
596 ctype = "text/html";
597 break;
598 default:
599 ctype = "text/plain";
600 break;
601 }
602 if (s->prot_flags & RGW_REST_SWIFT)
603 ctype.append("; charset=utf-8");
604 content_type = ctype.c_str();
605 }
606 if (!force_no_error && s->is_err()) {
607 dump_start(s);
608 dump(s);
609 dump_content_length(s, s->formatter->get_len());
610 } else {
611 if (proposed_content_length == CHUNKED_TRANSFER_ENCODING) {
612 dump_chunked_encoding(s);
613 } else if (proposed_content_length != NO_CONTENT_LENGTH) {
614 dump_content_length(s, proposed_content_length);
615 }
616 }
617
618 if (content_type) {
619 dump_header(s, "Content-Type", content_type);
620 }
621 dump_header_if_nonempty(s, "Server", g_conf()->rgw_service_provider_name);
622
623 try {
624 RESTFUL_IO(s)->complete_header();
625 } catch (rgw::io::Exception& e) {
626 ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->complete_header() returned err="
627 << e.what() << dendl;
628 }
629
630 ACCOUNTING_IO(s)->set_account(true);
631 rgw_flush_formatter_and_reset(s, s->formatter);
632 }
633
634 static void build_redirect_url(req_state *s, const string& redirect_base, string *redirect_url)
635 {
636 string& dest_uri = *redirect_url;
637
638 dest_uri = redirect_base;
639 /*
640 * reqest_uri is always start with slash, so we need to remove
641 * the unnecessary slash at the end of dest_uri.
642 */
643 if (dest_uri[dest_uri.size() - 1] == '/') {
644 dest_uri = dest_uri.substr(0, dest_uri.size() - 1);
645 }
646 dest_uri += s->info.request_uri;
647 dest_uri += "?";
648 dest_uri += s->info.request_params;
649 }
650
651 void abort_early(struct req_state *s, RGWOp* op, int err_no,
652 RGWHandler* handler, optional_yield y)
653 {
654 string error_content("");
655 if (!s->formatter) {
656 s->formatter = new JSONFormatter;
657 s->format = RGW_FORMAT_JSON;
658 }
659
660 // op->error_handler is responsible for calling it's handler error_handler
661 if (op != NULL) {
662 int new_err_no;
663 new_err_no = op->error_handler(err_no, &error_content, y);
664 ldout(s->cct, 1) << "op->ERRORHANDLER: err_no=" << err_no
665 << " new_err_no=" << new_err_no << dendl;
666 err_no = new_err_no;
667 } else if (handler != NULL) {
668 int new_err_no;
669 new_err_no = handler->error_handler(err_no, &error_content, y);
670 ldout(s->cct, 1) << "handler->ERRORHANDLER: err_no=" << err_no
671 << " new_err_no=" << new_err_no << dendl;
672 err_no = new_err_no;
673 }
674
675 // If the error handler(s) above dealt with it completely, they should have
676 // returned 0. If non-zero, we need to continue here.
677 if (err_no) {
678 // Watch out, we might have a custom error state already set!
679 if (!s->err.http_ret || s->err.http_ret == 200) {
680 set_req_state_err(s, err_no);
681 }
682
683 if (s->err.http_ret == 404 && !s->redirect_zone_endpoint.empty()) {
684 s->err.http_ret = 301;
685 err_no = -ERR_PERMANENT_REDIRECT;
686 build_redirect_url(s, s->redirect_zone_endpoint, &s->redirect);
687 }
688
689 dump_errno(s);
690 dump_bucket_from_state(s);
691 if (err_no == -ERR_PERMANENT_REDIRECT || err_no == -ERR_WEBSITE_REDIRECT) {
692 string dest_uri;
693 if (!s->redirect.empty()) {
694 dest_uri = s->redirect;
695 } else if (!s->zonegroup_endpoint.empty()) {
696 build_redirect_url(s, s->zonegroup_endpoint, &dest_uri);
697 }
698
699 if (!dest_uri.empty()) {
700 dump_redirect(s, dest_uri);
701 }
702 }
703
704 if (!error_content.empty()) {
705 /*
706 * TODO we must add all error entries as headers here:
707 * when having a working errordoc, then the s3 error fields are
708 * rendered as HTTP headers, e.g.:
709 * x-amz-error-code: NoSuchKey
710 * x-amz-error-message: The specified key does not exist.
711 * x-amz-error-detail-Key: foo
712 */
713 end_header(s, op, NULL, error_content.size(), false, true);
714 RESTFUL_IO(s)->send_body(error_content.c_str(), error_content.size());
715 } else {
716 end_header(s, op);
717 }
718 }
719 perfcounter->inc(l_rgw_failed_req);
720 }
721
722 void dump_continue(struct req_state * const s)
723 {
724 try {
725 RESTFUL_IO(s)->send_100_continue();
726 } catch (rgw::io::Exception& e) {
727 ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->send_100_continue() returned err="
728 << e.what() << dendl;
729 }
730 }
731
732 void dump_range(struct req_state* const s,
733 const uint64_t ofs,
734 const uint64_t end,
735 const uint64_t total)
736 {
737 /* dumping range into temp buffer first, as libfcgi will fail to digest
738 * %lld */
739 char range_buf[128];
740 size_t len;
741
742 if (! total) {
743 len = snprintf(range_buf, sizeof(range_buf), "bytes */%lld",
744 static_cast<long long>(total));
745 } else {
746 len = snprintf(range_buf, sizeof(range_buf), "bytes %lld-%lld/%lld",
747 static_cast<long long>(ofs),
748 static_cast<long long>(end),
749 static_cast<long long>(total));
750 }
751
752 return dump_header(s, "Content-Range", std::string_view(range_buf, len));
753 }
754
755
756 int dump_body(struct req_state* const s,
757 const char* const buf,
758 const size_t len)
759 {
760 try {
761 return RESTFUL_IO(s)->send_body(buf, len);
762 } catch (rgw::io::Exception& e) {
763 return -e.code().value();
764 }
765 }
766
767 int dump_body(struct req_state* const s, /* const */ ceph::buffer::list& bl)
768 {
769 return dump_body(s, bl.c_str(), bl.length());
770 }
771
772 int dump_body(struct req_state* const s, const std::string& str)
773 {
774 return dump_body(s, str.c_str(), str.length());
775 }
776
777 int recv_body(struct req_state* const s,
778 char* const buf,
779 const size_t max)
780 {
781 try {
782 return RESTFUL_IO(s)->recv_body(buf, max);
783 } catch (rgw::io::Exception& e) {
784 return -e.code().value();
785 }
786 }
787
788 int RGWGetObj_ObjStore::get_params(optional_yield y)
789 {
790 range_str = s->info.env->get("HTTP_RANGE");
791 if_mod = s->info.env->get("HTTP_IF_MODIFIED_SINCE");
792 if_unmod = s->info.env->get("HTTP_IF_UNMODIFIED_SINCE");
793 if_match = s->info.env->get("HTTP_IF_MATCH");
794 if_nomatch = s->info.env->get("HTTP_IF_NONE_MATCH");
795
796 if (s->system_request) {
797 mod_zone_id = s->info.env->get_int("HTTP_DEST_ZONE_SHORT_ID", 0);
798 mod_pg_ver = s->info.env->get_int("HTTP_DEST_PG_VER", 0);
799 rgwx_stat = s->info.args.exists(RGW_SYS_PARAM_PREFIX "stat");
800 get_data &= (!rgwx_stat);
801 }
802
803 if (s->info.args.exists(GET_TORRENT)) {
804 return torrent.get_params();
805 }
806 return 0;
807 }
808
809 int RESTArgs::get_string(struct req_state *s, const string& name,
810 const string& def_val, string *val, bool *existed)
811 {
812 bool exists;
813 *val = s->info.args.get(name, &exists);
814
815 if (existed)
816 *existed = exists;
817
818 if (!exists) {
819 *val = def_val;
820 return 0;
821 }
822
823 return 0;
824 }
825
826 int RESTArgs::get_uint64(struct req_state *s, const string& name,
827 uint64_t def_val, uint64_t *val, bool *existed)
828 {
829 bool exists;
830 string sval = s->info.args.get(name, &exists);
831
832 if (existed)
833 *existed = exists;
834
835 if (!exists) {
836 *val = def_val;
837 return 0;
838 }
839
840 int r = stringtoull(sval, val);
841 if (r < 0)
842 return r;
843
844 return 0;
845 }
846
847 int RESTArgs::get_int64(struct req_state *s, const string& name,
848 int64_t def_val, int64_t *val, bool *existed)
849 {
850 bool exists;
851 string sval = s->info.args.get(name, &exists);
852
853 if (existed)
854 *existed = exists;
855
856 if (!exists) {
857 *val = def_val;
858 return 0;
859 }
860
861 int r = stringtoll(sval, val);
862 if (r < 0)
863 return r;
864
865 return 0;
866 }
867
868 int RESTArgs::get_uint32(struct req_state *s, const string& name,
869 uint32_t def_val, uint32_t *val, bool *existed)
870 {
871 bool exists;
872 string sval = s->info.args.get(name, &exists);
873
874 if (existed)
875 *existed = exists;
876
877 if (!exists) {
878 *val = def_val;
879 return 0;
880 }
881
882 int r = stringtoul(sval, val);
883 if (r < 0)
884 return r;
885
886 return 0;
887 }
888
889 int RESTArgs::get_int32(struct req_state *s, const string& name,
890 int32_t def_val, int32_t *val, bool *existed)
891 {
892 bool exists;
893 string sval = s->info.args.get(name, &exists);
894
895 if (existed)
896 *existed = exists;
897
898 if (!exists) {
899 *val = def_val;
900 return 0;
901 }
902
903 int r = stringtol(sval, val);
904 if (r < 0)
905 return r;
906
907 return 0;
908 }
909
910 int RESTArgs::get_time(struct req_state *s, const string& name,
911 const utime_t& def_val, utime_t *val, bool *existed)
912 {
913 bool exists;
914 string sval = s->info.args.get(name, &exists);
915
916 if (existed)
917 *existed = exists;
918
919 if (!exists) {
920 *val = def_val;
921 return 0;
922 }
923
924 uint64_t epoch, nsec;
925
926 int r = utime_t::parse_date(sval, &epoch, &nsec);
927 if (r < 0)
928 return r;
929
930 *val = utime_t(epoch, nsec);
931
932 return 0;
933 }
934
935 int RESTArgs::get_epoch(struct req_state *s, const string& name, uint64_t def_val, uint64_t *epoch, bool *existed)
936 {
937 bool exists;
938 string date = s->info.args.get(name, &exists);
939
940 if (existed)
941 *existed = exists;
942
943 if (!exists) {
944 *epoch = def_val;
945 return 0;
946 }
947
948 int r = utime_t::parse_date(date, epoch, NULL);
949 if (r < 0)
950 return r;
951
952 return 0;
953 }
954
955 int RESTArgs::get_bool(struct req_state *s, const string& name, bool def_val, bool *val, bool *existed)
956 {
957 bool exists;
958 string sval = s->info.args.get(name, &exists);
959
960 if (existed)
961 *existed = exists;
962
963 if (!exists) {
964 *val = def_val;
965 return 0;
966 }
967
968 const char *str = sval.c_str();
969
970 if (sval.empty() ||
971 strcasecmp(str, "true") == 0 ||
972 sval.compare("1") == 0) {
973 *val = true;
974 return 0;
975 }
976
977 if (strcasecmp(str, "false") != 0 &&
978 sval.compare("0") != 0) {
979 *val = def_val;
980 return -EINVAL;
981 }
982
983 *val = false;
984 return 0;
985 }
986
987
988 void RGWRESTFlusher::do_start(int ret)
989 {
990 set_req_state_err(s, ret); /* no going back from here */
991 dump_errno(s);
992 dump_start(s);
993 end_header(s, op);
994 rgw_flush_formatter_and_reset(s, s->formatter);
995 }
996
997 void RGWRESTFlusher::do_flush()
998 {
999 rgw_flush_formatter(s, s->formatter);
1000 }
1001
1002 int RGWPutObj_ObjStore::verify_params()
1003 {
1004 if (s->length) {
1005 off_t len = atoll(s->length);
1006 if (len > (off_t)(s->cct->_conf->rgw_max_put_size)) {
1007 return -ERR_TOO_LARGE;
1008 }
1009 }
1010
1011 return 0;
1012 }
1013
1014 int RGWPutObj_ObjStore::get_params(optional_yield y)
1015 {
1016 /* start gettorrent */
1017 if (s->cct->_conf->rgw_torrent_flag)
1018 {
1019 int ret = 0;
1020 ret = torrent.get_params();
1021 ldout(s->cct, 5) << "NOTICE: open produce torrent file " << dendl;
1022 if (ret < 0)
1023 {
1024 return ret;
1025 }
1026 torrent.set_info_name(s->object->get_name());
1027 }
1028 /* end gettorrent */
1029 supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5");
1030
1031 return 0;
1032 }
1033
1034 int RGWPutObj_ObjStore::get_data(bufferlist& bl)
1035 {
1036 size_t cl;
1037 uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size;
1038 if (s->length) {
1039 cl = atoll(s->length) - ofs;
1040 if (cl > chunk_size)
1041 cl = chunk_size;
1042 } else {
1043 cl = chunk_size;
1044 }
1045
1046 int len = 0;
1047 {
1048 ACCOUNTING_IO(s)->set_account(true);
1049 bufferptr bp(cl);
1050
1051 const auto read_len = recv_body(s, bp.c_str(), cl);
1052 if (read_len < 0) {
1053 return read_len;
1054 }
1055
1056 len = read_len;
1057 bl.append(bp, 0, len);
1058
1059 ACCOUNTING_IO(s)->set_account(false);
1060 }
1061
1062 if ((uint64_t)ofs + len > s->cct->_conf->rgw_max_put_size) {
1063 return -ERR_TOO_LARGE;
1064 }
1065
1066 return len;
1067 }
1068
1069
1070 /*
1071 * parses params in the format: 'first; param1=foo; param2=bar'
1072 */
1073 void RGWPostObj_ObjStore::parse_boundary_params(const std::string& params_str,
1074 std::string& first,
1075 std::map<std::string,
1076 std::string>& params)
1077 {
1078 size_t pos = params_str.find(';');
1079 if (std::string::npos == pos) {
1080 first = rgw_trim_whitespace(params_str);
1081 return;
1082 }
1083
1084 first = rgw_trim_whitespace(params_str.substr(0, pos));
1085 pos++;
1086
1087 while (pos < params_str.size()) {
1088 size_t end = params_str.find(';', pos);
1089 if (std::string::npos == end) {
1090 end = params_str.size();
1091 }
1092
1093 std::string param = params_str.substr(pos, end - pos);
1094 size_t eqpos = param.find('=');
1095
1096 if (std::string::npos != eqpos) {
1097 std::string param_name = rgw_trim_whitespace(param.substr(0, eqpos));
1098 std::string val = rgw_trim_quotes(param.substr(eqpos + 1));
1099 params[std::move(param_name)] = std::move(val);
1100 } else {
1101 params[rgw_trim_whitespace(param)] = "";
1102 }
1103
1104 pos = end + 1;
1105 }
1106 }
1107
1108 int RGWPostObj_ObjStore::parse_part_field(const std::string& line,
1109 std::string& field_name, /* out */
1110 post_part_field& field) /* out */
1111 {
1112 size_t pos = line.find(':');
1113 if (pos == string::npos)
1114 return -EINVAL;
1115
1116 field_name = line.substr(0, pos);
1117 if (pos >= line.size() - 1)
1118 return 0;
1119
1120 parse_boundary_params(line.substr(pos + 1), field.val, field.params);
1121
1122 return 0;
1123 }
1124
1125 static bool is_crlf(const char *s)
1126 {
1127 return (*s == '\r' && *(s + 1) == '\n');
1128 }
1129
1130 /*
1131 * find the index of the boundary, if exists, or optionally the next end of line
1132 * also returns how many bytes to skip
1133 */
1134 static int index_of(ceph::bufferlist& bl,
1135 uint64_t max_len,
1136 const std::string& str,
1137 const bool check_crlf,
1138 bool& reached_boundary,
1139 int& skip)
1140 {
1141 reached_boundary = false;
1142 skip = 0;
1143
1144 if (str.size() < 2) // we assume boundary is at least 2 chars (makes it easier with crlf checks)
1145 return -EINVAL;
1146
1147 if (bl.length() < str.size())
1148 return -1;
1149
1150 const char *buf = bl.c_str();
1151 const char *s = str.c_str();
1152
1153 if (max_len > bl.length())
1154 max_len = bl.length();
1155
1156 for (uint64_t i = 0; i < max_len; i++, buf++) {
1157 if (check_crlf &&
1158 i >= 1 &&
1159 is_crlf(buf - 1)) {
1160 return i + 1; // skip the crlf
1161 }
1162 if ((i < max_len - str.size() + 1) &&
1163 (buf[0] == s[0] && buf[1] == s[1]) &&
1164 (strncmp(buf, s, str.size()) == 0)) {
1165 reached_boundary = true;
1166 skip = str.size();
1167
1168 /* oh, great, now we need to swallow the preceding crlf
1169 * if exists
1170 */
1171 if ((i >= 2) &&
1172 is_crlf(buf - 2)) {
1173 i -= 2;
1174 skip += 2;
1175 }
1176 return i;
1177 }
1178 }
1179
1180 return -1;
1181 }
1182
1183 int RGWPostObj_ObjStore::read_with_boundary(ceph::bufferlist& bl,
1184 uint64_t max,
1185 const bool check_crlf,
1186 bool& reached_boundary,
1187 bool& done)
1188 {
1189 uint64_t cl = max + 2 + boundary.size();
1190
1191 if (max > in_data.length()) {
1192 uint64_t need_to_read = cl - in_data.length();
1193
1194 bufferptr bp(need_to_read);
1195
1196 const auto read_len = recv_body(s, bp.c_str(), need_to_read);
1197 if (read_len < 0) {
1198 return read_len;
1199 }
1200 in_data.append(bp, 0, read_len);
1201 }
1202
1203 done = false;
1204 int skip;
1205 const int index = index_of(in_data, cl, boundary, check_crlf,
1206 reached_boundary, skip);
1207 if (index >= 0) {
1208 max = index;
1209 }
1210
1211 if (max > in_data.length()) {
1212 max = in_data.length();
1213 }
1214
1215 bl.substr_of(in_data, 0, max);
1216
1217 ceph::bufferlist new_read_data;
1218
1219 /*
1220 * now we need to skip boundary for next time, also skip any crlf, or
1221 * check to see if it's the last final boundary (marked with "--" at the end
1222 */
1223 if (reached_boundary) {
1224 int left = in_data.length() - max;
1225 if (left < skip + 2) {
1226 int need = skip + 2 - left;
1227 bufferptr boundary_bp(need);
1228 const int r = recv_body(s, boundary_bp.c_str(), need);
1229 if (r < 0) {
1230 return r;
1231 }
1232 in_data.append(boundary_bp);
1233 }
1234 max += skip; // skip boundary for next time
1235 if (in_data.length() >= max + 2) {
1236 const char *data = in_data.c_str();
1237 if (is_crlf(data + max)) {
1238 max += 2;
1239 } else {
1240 if (*(data + max) == '-' &&
1241 *(data + max + 1) == '-') {
1242 done = true;
1243 max += 2;
1244 }
1245 }
1246 }
1247 }
1248
1249 new_read_data.substr_of(in_data, max, in_data.length() - max);
1250 in_data = new_read_data;
1251
1252 return 0;
1253 }
1254
1255 int RGWPostObj_ObjStore::read_line(ceph::bufferlist& bl,
1256 const uint64_t max,
1257 bool& reached_boundary,
1258 bool& done)
1259 {
1260 return read_with_boundary(bl, max, true, reached_boundary, done);
1261 }
1262
1263 int RGWPostObj_ObjStore::read_data(ceph::bufferlist& bl,
1264 const uint64_t max,
1265 bool& reached_boundary,
1266 bool& done)
1267 {
1268 return read_with_boundary(bl, max, false, reached_boundary, done);
1269 }
1270
1271
1272 int RGWPostObj_ObjStore::read_form_part_header(struct post_form_part* const part,
1273 bool& done)
1274 {
1275 bufferlist bl;
1276 bool reached_boundary;
1277 uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size;
1278 int r = read_line(bl, chunk_size, reached_boundary, done);
1279 if (r < 0) {
1280 return r;
1281 }
1282
1283 if (done) {
1284 return 0;
1285 }
1286
1287 if (reached_boundary) { // skip the first boundary
1288 r = read_line(bl, chunk_size, reached_boundary, done);
1289 if (r < 0) {
1290 return r;
1291 } else if (done) {
1292 return 0;
1293 }
1294 }
1295
1296 while (true) {
1297 /*
1298 * iterate through fields
1299 */
1300 std::string line = rgw_trim_whitespace(string(bl.c_str(), bl.length()));
1301
1302 if (line.empty()) {
1303 break;
1304 }
1305
1306 struct post_part_field field;
1307
1308 string field_name;
1309 r = parse_part_field(line, field_name, field);
1310 if (r < 0) {
1311 return r;
1312 }
1313
1314 part->fields[field_name] = field;
1315
1316 if (stringcasecmp(field_name, "Content-Disposition") == 0) {
1317 part->name = field.params["name"];
1318 }
1319
1320 if (reached_boundary) {
1321 break;
1322 }
1323
1324 r = read_line(bl, chunk_size, reached_boundary, done);
1325 if (r < 0) {
1326 return r;
1327 }
1328 }
1329
1330 return 0;
1331 }
1332
1333 bool RGWPostObj_ObjStore::part_str(parts_collection_t& parts,
1334 const std::string& name,
1335 std::string* val)
1336 {
1337 const auto iter = parts.find(name);
1338 if (std::end(parts) == iter) {
1339 return false;
1340 }
1341
1342 ceph::bufferlist& data = iter->second.data;
1343 std::string str = string(data.c_str(), data.length());
1344 *val = rgw_trim_whitespace(str);
1345 return true;
1346 }
1347
1348 std::string RGWPostObj_ObjStore::get_part_str(parts_collection_t& parts,
1349 const std::string& name,
1350 const std::string& def_val)
1351 {
1352 std::string val;
1353
1354 if (part_str(parts, name, &val)) {
1355 return val;
1356 } else {
1357 return rgw_trim_whitespace(def_val);
1358 }
1359 }
1360
1361 bool RGWPostObj_ObjStore::part_bl(parts_collection_t& parts,
1362 const std::string& name,
1363 ceph::bufferlist* pbl)
1364 {
1365 const auto iter = parts.find(name);
1366 if (std::end(parts) == iter) {
1367 return false;
1368 }
1369
1370 *pbl = iter->second.data;
1371 return true;
1372 }
1373
1374 int RGWPostObj_ObjStore::verify_params()
1375 {
1376 /* check that we have enough memory to store the object
1377 note that this test isn't exact and may fail unintentionally
1378 for large requests is */
1379 if (!s->length) {
1380 return -ERR_LENGTH_REQUIRED;
1381 }
1382 off_t len = atoll(s->length);
1383 if (len > (off_t)(s->cct->_conf->rgw_max_put_size)) {
1384 return -ERR_TOO_LARGE;
1385 }
1386
1387 supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5");
1388
1389 return 0;
1390 }
1391
1392 int RGWPostObj_ObjStore::get_params(optional_yield y)
1393 {
1394 if (s->expect_cont) {
1395 /* OK, here it really gets ugly. With POST, the params are embedded in the
1396 * request body, so we need to continue before being able to actually look
1397 * at them. This diverts from the usual request flow. */
1398 dump_continue(s);
1399 s->expect_cont = false;
1400 }
1401
1402 std::string req_content_type_str = s->info.env->get("CONTENT_TYPE", "");
1403 std::string req_content_type;
1404 std::map<std::string, std::string> params;
1405 parse_boundary_params(req_content_type_str, req_content_type, params);
1406
1407 if (req_content_type.compare("multipart/form-data") != 0) {
1408 err_msg = "Request Content-Type is not multipart/form-data";
1409 return -EINVAL;
1410 }
1411
1412 if (s->cct->_conf->subsys.should_gather<ceph_subsys_rgw, 20>()) {
1413 ldout(s->cct, 20) << "request content_type_str="
1414 << req_content_type_str << dendl;
1415 ldout(s->cct, 20) << "request content_type params:" << dendl;
1416
1417 for (const auto& pair : params) {
1418 ldout(s->cct, 20) << " " << pair.first << " -> " << pair.second
1419 << dendl;
1420 }
1421 }
1422
1423 const auto iter = params.find("boundary");
1424 if (std::end(params) == iter) {
1425 err_msg = "Missing multipart boundary specification";
1426 return -EINVAL;
1427 }
1428
1429 /* Create the boundary. */
1430 boundary = "--";
1431 boundary.append(iter->second);
1432
1433 return 0;
1434 }
1435
1436
1437 int RGWPutACLs_ObjStore::get_params(optional_yield y)
1438 {
1439 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1440 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false);
1441 ldout(s->cct, 0) << "RGWPutACLs_ObjStore::get_params read data is: " << data.c_str() << dendl;
1442 return op_ret;
1443 }
1444
1445 int RGWPutLC_ObjStore::get_params(optional_yield y)
1446 {
1447 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1448 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false);
1449 return op_ret;
1450 }
1451
1452 int RGWPutBucketObjectLock_ObjStore::get_params(optional_yield y)
1453 {
1454 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1455 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false);
1456 return op_ret;
1457 }
1458
1459 int RGWPutObjLegalHold_ObjStore::get_params(optional_yield y)
1460 {
1461 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1462 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false);
1463 return op_ret;
1464 }
1465
1466
1467 static std::tuple<int, bufferlist> read_all_chunked_input(req_state *s, const uint64_t max_read)
1468 {
1469 #define READ_CHUNK 4096
1470 #define MAX_READ_CHUNK (128 * 1024)
1471 int need_to_read = READ_CHUNK;
1472 int total = need_to_read;
1473 bufferlist bl;
1474
1475 int read_len = 0, len = 0;
1476 do {
1477 bufferptr bp(need_to_read + 1);
1478 read_len = recv_body(s, bp.c_str(), need_to_read);
1479 if (read_len < 0) {
1480 return std::make_tuple(read_len, std::move(bl));
1481 }
1482
1483 bp.c_str()[read_len] = '\0';
1484 bp.set_length(read_len);
1485 bl.append(bp);
1486 len += read_len;
1487
1488 if (read_len == need_to_read) {
1489 if (need_to_read < MAX_READ_CHUNK)
1490 need_to_read *= 2;
1491
1492 if ((unsigned)total > max_read) {
1493 return std::make_tuple(-ERANGE, std::move(bl));
1494 }
1495 total += need_to_read;
1496 } else {
1497 break;
1498 }
1499 } while (true);
1500
1501 return std::make_tuple(0, std::move(bl));
1502 }
1503
1504 std::tuple<int, bufferlist > rgw_rest_read_all_input(struct req_state *s,
1505 const uint64_t max_len,
1506 const bool allow_chunked)
1507 {
1508 size_t cl = 0;
1509 int len = 0;
1510 bufferlist bl;
1511
1512 if (s->length)
1513 cl = atoll(s->length);
1514 else if (!allow_chunked)
1515 return std::make_tuple(-ERR_LENGTH_REQUIRED, std::move(bl));
1516
1517 if (cl) {
1518 if (cl > (size_t)max_len) {
1519 return std::make_tuple(-ERANGE, std::move(bl));
1520 }
1521
1522 bufferptr bp(cl + 1);
1523
1524 len = recv_body(s, bp.c_str(), cl);
1525 if (len < 0) {
1526 return std::make_tuple(len, std::move(bl));
1527 }
1528
1529 bp.c_str()[len] = '\0';
1530 bp.set_length(len);
1531 bl.append(bp);
1532
1533 } else if (allow_chunked && !s->length) {
1534 const char *encoding = s->info.env->get("HTTP_TRANSFER_ENCODING");
1535 if (!encoding || strcmp(encoding, "chunked") != 0)
1536 return std::make_tuple(-ERR_LENGTH_REQUIRED, std::move(bl));
1537
1538 int ret = 0;
1539 std::tie(ret, bl) = read_all_chunked_input(s, max_len);
1540 if (ret < 0)
1541 return std::make_tuple(ret, std::move(bl));
1542 }
1543
1544 return std::make_tuple(0, std::move(bl));
1545 }
1546
1547 int RGWCompleteMultipart_ObjStore::get_params(optional_yield y)
1548 {
1549 upload_id = s->info.args.get("uploadId");
1550
1551 if (upload_id.empty()) {
1552 op_ret = -ENOTSUP;
1553 return op_ret;
1554 }
1555
1556 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1557 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size);
1558 if (op_ret < 0)
1559 return op_ret;
1560
1561 return 0;
1562 }
1563
1564 int RGWListMultipart_ObjStore::get_params(optional_yield y)
1565 {
1566 upload_id = s->info.args.get("uploadId");
1567
1568 if (upload_id.empty()) {
1569 op_ret = -ENOTSUP;
1570 }
1571 string marker_str = s->info.args.get("part-number-marker");
1572
1573 if (!marker_str.empty()) {
1574 string err;
1575 marker = strict_strtol(marker_str.c_str(), 10, &err);
1576 if (!err.empty()) {
1577 ldout(s->cct, 20) << "bad marker: " << marker << dendl;
1578 op_ret = -EINVAL;
1579 return op_ret;
1580 }
1581 }
1582
1583 string str = s->info.args.get("max-parts");
1584 op_ret = parse_value_and_bound(str, max_parts, 0,
1585 g_conf().get_val<uint64_t>("rgw_max_listing_results"),
1586 max_parts);
1587
1588 return op_ret;
1589 }
1590
1591 int RGWListBucketMultiparts_ObjStore::get_params(optional_yield y)
1592 {
1593 delimiter = s->info.args.get("delimiter");
1594 prefix = s->info.args.get("prefix");
1595 string str = s->info.args.get("max-uploads");
1596 op_ret = parse_value_and_bound(str, max_uploads, 0,
1597 g_conf().get_val<uint64_t>("rgw_max_listing_results"),
1598 default_max);
1599 if (op_ret < 0) {
1600 return op_ret;
1601 }
1602
1603 if (auto encoding_type = s->info.args.get_optional("encoding-type");
1604 encoding_type != boost::none) {
1605 if (strcasecmp(encoding_type->c_str(), "url") != 0) {
1606 op_ret = -EINVAL;
1607 s->err.message="Invalid Encoding Method specified in Request";
1608 return op_ret;
1609 }
1610 encode_url = true;
1611 }
1612
1613 string key_marker = s->info.args.get("key-marker");
1614 string upload_id_marker = s->info.args.get("upload-id-marker");
1615 if (!key_marker.empty())
1616 marker.init(key_marker, upload_id_marker);
1617
1618 return 0;
1619 }
1620
1621 int RGWDeleteMultiObj_ObjStore::get_params(optional_yield y)
1622 {
1623
1624 if (s->bucket_name.empty()) {
1625 op_ret = -EINVAL;
1626 return op_ret;
1627 }
1628
1629 // everything is probably fine, set the bucket
1630 bucket = s->bucket.get();
1631
1632 const auto max_size = s->cct->_conf->rgw_max_put_param_size;
1633 std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false);
1634 return op_ret;
1635 }
1636
1637
1638 void RGWRESTOp::send_response()
1639 {
1640 if (!flusher.did_start()) {
1641 set_req_state_err(s, get_ret());
1642 dump_errno(s);
1643 end_header(s, this);
1644 }
1645 flusher.flush();
1646 }
1647
1648 int RGWRESTOp::verify_permission(optional_yield)
1649 {
1650 return check_caps(s->user->get_info().caps);
1651 }
1652
1653 RGWOp* RGWHandler_REST::get_op(void)
1654 {
1655 RGWOp *op;
1656 switch (s->op) {
1657 case OP_GET:
1658 op = op_get();
1659 break;
1660 case OP_PUT:
1661 op = op_put();
1662 break;
1663 case OP_DELETE:
1664 op = op_delete();
1665 break;
1666 case OP_HEAD:
1667 op = op_head();
1668 break;
1669 case OP_POST:
1670 op = op_post();
1671 break;
1672 case OP_COPY:
1673 op = op_copy();
1674 break;
1675 case OP_OPTIONS:
1676 op = op_options();
1677 break;
1678 default:
1679 return NULL;
1680 }
1681
1682 if (op) {
1683 op->init(store, s, this);
1684 }
1685 return op;
1686 } /* get_op */
1687
1688 void RGWHandler_REST::put_op(RGWOp* op)
1689 {
1690 delete op;
1691 } /* put_op */
1692
1693 int RGWHandler_REST::allocate_formatter(struct req_state *s,
1694 int default_type,
1695 bool configurable)
1696 {
1697 s->format = -1; // set to invalid value to allocation happens anyway
1698 auto type = default_type;
1699 if (configurable) {
1700 string format_str = s->info.args.get("format");
1701 if (format_str.compare("xml") == 0) {
1702 type = RGW_FORMAT_XML;
1703 } else if (format_str.compare("json") == 0) {
1704 type = RGW_FORMAT_JSON;
1705 } else if (format_str.compare("html") == 0) {
1706 type = RGW_FORMAT_HTML;
1707 } else {
1708 const char *accept = s->info.env->get("HTTP_ACCEPT");
1709 if (accept) {
1710 char format_buf[64];
1711 unsigned int i = 0;
1712 for (; i < sizeof(format_buf) - 1 && accept[i] && accept[i] != ';'; ++i) {
1713 format_buf[i] = accept[i];
1714 }
1715 format_buf[i] = 0;
1716 if ((strcmp(format_buf, "text/xml") == 0) || (strcmp(format_buf, "application/xml") == 0)) {
1717 type = RGW_FORMAT_XML;
1718 } else if (strcmp(format_buf, "application/json") == 0) {
1719 type = RGW_FORMAT_JSON;
1720 } else if (strcmp(format_buf, "text/html") == 0) {
1721 type = RGW_FORMAT_HTML;
1722 }
1723 }
1724 }
1725 }
1726 return RGWHandler_REST::reallocate_formatter(s, type);
1727 }
1728
1729 int RGWHandler_REST::reallocate_formatter(struct req_state *s, int type)
1730 {
1731 if (s->format == type) {
1732 // do nothing, just reset
1733 ceph_assert(s->formatter);
1734 s->formatter->reset();
1735 return 0;
1736 }
1737
1738 delete s->formatter;
1739 s->formatter = nullptr;
1740 s->format = type;
1741
1742 const string& mm = s->info.args.get("multipart-manifest");
1743 const bool multipart_delete = (mm.compare("delete") == 0);
1744 const bool swift_bulkupload = s->prot_flags & RGW_REST_SWIFT &&
1745 s->info.args.exists("extract-archive");
1746 switch (s->format) {
1747 case RGW_FORMAT_PLAIN:
1748 {
1749 const bool use_kv_syntax = s->info.args.exists("bulk-delete") ||
1750 multipart_delete || swift_bulkupload;
1751 s->formatter = new RGWFormatter_Plain(use_kv_syntax);
1752 break;
1753 }
1754 case RGW_FORMAT_XML:
1755 {
1756 const bool lowercase_underscore = s->info.args.exists("bulk-delete") ||
1757 multipart_delete || swift_bulkupload;
1758
1759 s->formatter = new XMLFormatter(false, lowercase_underscore);
1760 break;
1761 }
1762 case RGW_FORMAT_JSON:
1763 s->formatter = new JSONFormatter(false);
1764 break;
1765 case RGW_FORMAT_HTML:
1766 s->formatter = new HTMLFormatter(s->prot_flags & RGW_REST_WEBSITE);
1767 break;
1768 default:
1769 return -EINVAL;
1770
1771 };
1772 //s->formatter->reset(); // All formatters should reset on create already
1773
1774 return 0;
1775 }
1776 // This function enforces Amazon's spec for bucket names.
1777 // (The requirements, not the recommendations.)
1778 int RGWHandler_REST::validate_bucket_name(const string& bucket)
1779 {
1780 int len = bucket.size();
1781 if (len < 3) {
1782 if (len == 0) {
1783 // This request doesn't specify a bucket at all
1784 return 0;
1785 }
1786 // Name too short
1787 return -ERR_INVALID_BUCKET_NAME;
1788 }
1789 else if (len > MAX_BUCKET_NAME_LEN) {
1790 // Name too long
1791 return -ERR_INVALID_BUCKET_NAME;
1792 }
1793
1794 const char *s = bucket.c_str();
1795 for (int i = 0; i < len; ++i, ++s) {
1796 if (*(unsigned char *)s == 0xff)
1797 return -ERR_INVALID_BUCKET_NAME;
1798 if (*(unsigned char *)s == '/')
1799 return -ERR_INVALID_BUCKET_NAME;
1800 }
1801
1802 return 0;
1803 }
1804
1805 // "The name for a key is a sequence of Unicode characters whose UTF-8 encoding
1806 // is at most 1024 bytes long."
1807 // However, we can still have control characters and other nasties in there.
1808 // Just as long as they're utf-8 nasties.
1809 int RGWHandler_REST::validate_object_name(const string& object)
1810 {
1811 int len = object.size();
1812 if (len > MAX_OBJ_NAME_LEN) {
1813 // Name too long
1814 return -ERR_INVALID_OBJECT_NAME;
1815 }
1816
1817 if (check_utf8(object.c_str(), len)) {
1818 // Object names must be valid UTF-8.
1819 return -ERR_INVALID_OBJECT_NAME;
1820 }
1821 return 0;
1822 }
1823
1824 static http_op op_from_method(const char *method)
1825 {
1826 if (!method)
1827 return OP_UNKNOWN;
1828 if (strcmp(method, "GET") == 0)
1829 return OP_GET;
1830 if (strcmp(method, "PUT") == 0)
1831 return OP_PUT;
1832 if (strcmp(method, "DELETE") == 0)
1833 return OP_DELETE;
1834 if (strcmp(method, "HEAD") == 0)
1835 return OP_HEAD;
1836 if (strcmp(method, "POST") == 0)
1837 return OP_POST;
1838 if (strcmp(method, "COPY") == 0)
1839 return OP_COPY;
1840 if (strcmp(method, "OPTIONS") == 0)
1841 return OP_OPTIONS;
1842
1843 return OP_UNKNOWN;
1844 }
1845
1846 int RGWHandler_REST::init_permissions(RGWOp* op, optional_yield y)
1847 {
1848 if (op->get_type() == RGW_OP_CREATE_BUCKET) {
1849 // We don't need user policies in case of STS token returned by AssumeRole, hence the check for user type
1850 if (! s->user->get_id().empty() && s->auth.identity->get_identity_type() != TYPE_ROLE) {
1851 try {
1852 map<string, bufferlist> uattrs;
1853 if (auto ret = store->ctl()->user->get_attrs_by_uid(s->user->get_id(), &uattrs, y); ! ret) {
1854 auto user_policies = get_iam_user_policy_from_attr(s->cct, store, uattrs, s->user->get_tenant());
1855 s->iam_user_policies.insert(s->iam_user_policies.end(),
1856 std::make_move_iterator(user_policies.begin()),
1857 std::make_move_iterator(user_policies.end()));
1858
1859 }
1860 } catch (const std::exception& e) {
1861 lderr(s->cct) << "Error reading IAM User Policy: " << e.what() << dendl;
1862 }
1863 }
1864 rgw_build_iam_environment(store, s);
1865 return 0;
1866 }
1867
1868 return do_init_permissions(y);
1869 }
1870
1871 int RGWHandler_REST::read_permissions(RGWOp* op_obj, optional_yield y)
1872 {
1873 bool only_bucket = false;
1874
1875 switch (s->op) {
1876 case OP_HEAD:
1877 case OP_GET:
1878 only_bucket = false;
1879 break;
1880 case OP_PUT:
1881 case OP_POST:
1882 case OP_COPY:
1883 /* is it a 'multi-object delete' request? */
1884 if (s->info.args.exists("delete")) {
1885 only_bucket = true;
1886 break;
1887 }
1888 if (is_obj_update_op()) {
1889 only_bucket = false;
1890 break;
1891 }
1892 /* is it a 'create bucket' request? */
1893 if (op_obj->get_type() == RGW_OP_CREATE_BUCKET)
1894 return 0;
1895
1896 only_bucket = true;
1897 break;
1898 case OP_DELETE:
1899 if (!s->info.args.exists("tagging")){
1900 only_bucket = true;
1901 }
1902 break;
1903 case OP_OPTIONS:
1904 only_bucket = true;
1905 break;
1906 default:
1907 return -EINVAL;
1908 }
1909
1910 return do_read_permissions(op_obj, only_bucket, y);
1911 }
1912
1913 void RGWRESTMgr::register_resource(string resource, RGWRESTMgr *mgr)
1914 {
1915 string r = "/";
1916 r.append(resource);
1917
1918 /* do we have a resource manager registered for this entry point? */
1919 map<string, RGWRESTMgr *>::iterator iter = resource_mgrs.find(r);
1920 if (iter != resource_mgrs.end()) {
1921 delete iter->second;
1922 }
1923 resource_mgrs[r] = mgr;
1924 resources_by_size.insert(pair<size_t, string>(r.size(), r));
1925
1926 /* now build default resource managers for the path (instead of nested entry points)
1927 * e.g., if the entry point is /auth/v1.0/ then we'd want to create a default
1928 * manager for /auth/
1929 */
1930
1931 size_t pos = r.find('/', 1);
1932
1933 while (pos != r.size() - 1 && pos != string::npos) {
1934 string s = r.substr(0, pos);
1935
1936 iter = resource_mgrs.find(s);
1937 if (iter == resource_mgrs.end()) { /* only register it if one does not exist */
1938 resource_mgrs[s] = new RGWRESTMgr; /* a default do-nothing manager */
1939 resources_by_size.insert(pair<size_t, string>(s.size(), s));
1940 }
1941
1942 pos = r.find('/', pos + 1);
1943 }
1944 }
1945
1946 void RGWRESTMgr::register_default_mgr(RGWRESTMgr *mgr)
1947 {
1948 delete default_mgr;
1949 default_mgr = mgr;
1950 }
1951
1952 RGWRESTMgr* RGWRESTMgr::get_resource_mgr(struct req_state* const s,
1953 const std::string& uri,
1954 std::string* const out_uri)
1955 {
1956 *out_uri = uri;
1957
1958 multimap<size_t, string>::reverse_iterator iter;
1959
1960 for (iter = resources_by_size.rbegin(); iter != resources_by_size.rend(); ++iter) {
1961 string& resource = iter->second;
1962 if (uri.compare(0, iter->first, resource) == 0 &&
1963 (uri.size() == iter->first ||
1964 uri[iter->first] == '/')) {
1965 std::string suffix = uri.substr(iter->first);
1966 return resource_mgrs[resource]->get_resource_mgr(s, suffix, out_uri);
1967 }
1968 }
1969
1970 if (default_mgr) {
1971 return default_mgr->get_resource_mgr_as_default(s, uri, out_uri);
1972 }
1973
1974 return this;
1975 }
1976
1977 void RGWREST::register_x_headers(const string& s_headers)
1978 {
1979 std::vector<std::string> hdrs = get_str_vec(s_headers);
1980 for (auto& hdr : hdrs) {
1981 boost::algorithm::to_upper(hdr); // XXX
1982 (void) x_headers.insert(hdr);
1983 }
1984 }
1985
1986 RGWRESTMgr::~RGWRESTMgr()
1987 {
1988 map<string, RGWRESTMgr *>::iterator iter;
1989 for (iter = resource_mgrs.begin(); iter != resource_mgrs.end(); ++iter) {
1990 delete iter->second;
1991 }
1992 delete default_mgr;
1993 }
1994
1995 int64_t parse_content_length(const char *content_length)
1996 {
1997 int64_t len = -1;
1998
1999 if (*content_length == '\0') {
2000 len = 0;
2001 } else {
2002 string err;
2003 len = strict_strtoll(content_length, 10, &err);
2004 if (!err.empty()) {
2005 len = -1;
2006 }
2007 }
2008
2009 return len;
2010 }
2011
2012 int RGWREST::preprocess(struct req_state *s, rgw::io::BasicClient* cio)
2013 {
2014 req_info& info = s->info;
2015
2016 /* save the request uri used to hash on the client side. request_uri may suffer
2017 modifications as part of the bucket encoding in the subdomain calling format.
2018 request_uri_aws4 will be used under aws4 auth */
2019 s->info.request_uri_aws4 = s->info.request_uri;
2020
2021 s->cio = cio;
2022
2023 // We need to know if this RGW instance is running the s3website API with a
2024 // higher priority than regular S3 API, or possibly in place of the regular
2025 // S3 API.
2026 // Map the listing of rgw_enable_apis in REVERSE order, so that items near
2027 // the front of the list have a higher number assigned (and -1 for items not in the list).
2028 list<string> apis;
2029 get_str_list(g_conf()->rgw_enable_apis, apis);
2030 int api_priority_s3 = -1;
2031 int api_priority_s3website = -1;
2032 auto api_s3website_priority_rawpos = std::find(apis.begin(), apis.end(), "s3website");
2033 auto api_s3_priority_rawpos = std::find(apis.begin(), apis.end(), "s3");
2034 if (api_s3_priority_rawpos != apis.end()) {
2035 api_priority_s3 = apis.size() - std::distance(apis.begin(), api_s3_priority_rawpos);
2036 }
2037 if (api_s3website_priority_rawpos != apis.end()) {
2038 api_priority_s3website = apis.size() - std::distance(apis.begin(), api_s3website_priority_rawpos);
2039 }
2040 ldout(s->cct, 10) << "rgw api priority: s3=" << api_priority_s3 << " s3website=" << api_priority_s3website << dendl;
2041 bool s3website_enabled = api_priority_s3website >= 0;
2042
2043 if (info.host.size()) {
2044 ssize_t pos;
2045 if (info.host.find('[') == 0) {
2046 pos = info.host.find(']');
2047 if (pos >=1) {
2048 info.host = info.host.substr(1, pos-1);
2049 }
2050 } else {
2051 pos = info.host.find(':');
2052 if (pos >= 0) {
2053 info.host = info.host.substr(0, pos);
2054 }
2055 }
2056 ldout(s->cct, 10) << "host=" << info.host << dendl;
2057 string domain;
2058 string subdomain;
2059 bool in_hosted_domain_s3website = false;
2060 bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, &subdomain, hostnames_set);
2061
2062 string s3website_domain;
2063 string s3website_subdomain;
2064
2065 if (s3website_enabled) {
2066 in_hosted_domain_s3website = rgw_find_host_in_domains(info.host, &s3website_domain, &s3website_subdomain, hostnames_s3website_set);
2067 if (in_hosted_domain_s3website) {
2068 in_hosted_domain = true; // TODO: should hostnames be a strict superset of hostnames_s3website?
2069 domain = s3website_domain;
2070 subdomain = s3website_subdomain;
2071 }
2072 }
2073
2074 ldout(s->cct, 20)
2075 << "subdomain=" << subdomain
2076 << " domain=" << domain
2077 << " in_hosted_domain=" << in_hosted_domain
2078 << " in_hosted_domain_s3website=" << in_hosted_domain_s3website
2079 << dendl;
2080
2081 if (g_conf()->rgw_resolve_cname
2082 && !in_hosted_domain
2083 && !in_hosted_domain_s3website) {
2084 string cname;
2085 bool found;
2086 int r = rgw_resolver->resolve_cname(info.host, cname, &found);
2087 if (r < 0) {
2088 ldout(s->cct, 0)
2089 << "WARNING: rgw_resolver->resolve_cname() returned r=" << r
2090 << dendl;
2091 }
2092
2093 if (found) {
2094 ldout(s->cct, 5) << "resolved host cname " << info.host << " -> "
2095 << cname << dendl;
2096 in_hosted_domain =
2097 rgw_find_host_in_domains(cname, &domain, &subdomain, hostnames_set);
2098
2099 if (s3website_enabled
2100 && !in_hosted_domain_s3website) {
2101 in_hosted_domain_s3website =
2102 rgw_find_host_in_domains(cname, &s3website_domain,
2103 &s3website_subdomain,
2104 hostnames_s3website_set);
2105 if (in_hosted_domain_s3website) {
2106 in_hosted_domain = true; // TODO: should hostnames be a
2107 // strict superset of hostnames_s3website?
2108 domain = s3website_domain;
2109 subdomain = s3website_subdomain;
2110 }
2111 }
2112
2113 ldout(s->cct, 20)
2114 << "subdomain=" << subdomain
2115 << " domain=" << domain
2116 << " in_hosted_domain=" << in_hosted_domain
2117 << " in_hosted_domain_s3website=" << in_hosted_domain_s3website
2118 << dendl;
2119 }
2120 }
2121
2122 // Handle A/CNAME records that point to the RGW storage, but do match the
2123 // CNAME test above, per issue http://tracker.ceph.com/issues/15975
2124 // If BOTH domain & subdomain variables are empty, then none of the above
2125 // cases matched anything, and we should fall back to using the Host header
2126 // directly as the bucket name.
2127 // As additional checks:
2128 // - if the Host header is an IP, we're using path-style access without DNS
2129 // - Also check that the Host header is a valid bucket name before using it.
2130 // - Don't enable virtual hosting if no hostnames are configured
2131 if (subdomain.empty()
2132 && (domain.empty() || domain != info.host)
2133 && !looks_like_ip_address(info.host.c_str())
2134 && RGWHandler_REST::validate_bucket_name(info.host) == 0
2135 && !(hostnames_set.empty() && hostnames_s3website_set.empty())) {
2136 subdomain.append(info.host);
2137 in_hosted_domain = 1;
2138 }
2139
2140 if (s3website_enabled && api_priority_s3website > api_priority_s3) {
2141 in_hosted_domain_s3website = 1;
2142 }
2143
2144 if (in_hosted_domain_s3website) {
2145 s->prot_flags |= RGW_REST_WEBSITE;
2146 }
2147
2148
2149 if (in_hosted_domain && !subdomain.empty()) {
2150 string encoded_bucket = "/";
2151 encoded_bucket.append(subdomain);
2152 if (s->info.request_uri[0] != '/')
2153 encoded_bucket.append("/");
2154 encoded_bucket.append(s->info.request_uri);
2155 s->info.request_uri = encoded_bucket;
2156 }
2157
2158 if (!domain.empty()) {
2159 s->info.domain = domain;
2160 }
2161
2162 ldout(s->cct, 20)
2163 << "final domain/bucket"
2164 << " subdomain=" << subdomain
2165 << " domain=" << domain
2166 << " in_hosted_domain=" << in_hosted_domain
2167 << " in_hosted_domain_s3website=" << in_hosted_domain_s3website
2168 << " s->info.domain=" << s->info.domain
2169 << " s->info.request_uri=" << s->info.request_uri
2170 << dendl;
2171 }
2172
2173 if (s->info.domain.empty()) {
2174 s->info.domain = s->cct->_conf->rgw_dns_name;
2175 }
2176
2177 s->decoded_uri = url_decode(s->info.request_uri);
2178 /* Validate for being free of the '\0' buried in the middle of the string. */
2179 if (std::strlen(s->decoded_uri.c_str()) != s->decoded_uri.length()) {
2180 return -ERR_ZERO_IN_URL;
2181 }
2182
2183 /* FastCGI specification, section 6.3
2184 * http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S6.3
2185 * ===
2186 * The Authorizer application receives HTTP request information from the Web
2187 * server on the FCGI_PARAMS stream, in the same format as a Responder. The
2188 * Web server does not send CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, and
2189 * SCRIPT_NAME headers.
2190 * ===
2191 * Ergo if we are in Authorizer role, we MUST look at HTTP_CONTENT_LENGTH
2192 * instead of CONTENT_LENGTH for the Content-Length.
2193 *
2194 * There is one slight wrinkle in this, and that's older versions of
2195 * nginx/lighttpd/apache setting BOTH headers. As a result, we have to check
2196 * both headers and can't always simply pick A or B.
2197 */
2198 const char* content_length = info.env->get("CONTENT_LENGTH");
2199 const char* http_content_length = info.env->get("HTTP_CONTENT_LENGTH");
2200 if (!http_content_length != !content_length) {
2201 /* Easy case: one or the other is missing */
2202 s->length = (content_length ? content_length : http_content_length);
2203 } else if (s->cct->_conf->rgw_content_length_compat &&
2204 content_length && http_content_length) {
2205 /* Hard case: Both are set, we have to disambiguate */
2206 int64_t content_length_i, http_content_length_i;
2207
2208 content_length_i = parse_content_length(content_length);
2209 http_content_length_i = parse_content_length(http_content_length);
2210
2211 // Now check them:
2212 if (http_content_length_i < 0) {
2213 // HTTP_CONTENT_LENGTH is invalid, ignore it
2214 } else if (content_length_i < 0) {
2215 // CONTENT_LENGTH is invalid, and HTTP_CONTENT_LENGTH is valid
2216 // Swap entries
2217 content_length = http_content_length;
2218 } else {
2219 // both CONTENT_LENGTH and HTTP_CONTENT_LENGTH are valid
2220 // Let's pick the larger size
2221 if (content_length_i < http_content_length_i) {
2222 // prefer the larger value
2223 content_length = http_content_length;
2224 }
2225 }
2226 s->length = content_length;
2227 // End of: else if (s->cct->_conf->rgw_content_length_compat &&
2228 // content_length &&
2229 // http_content_length)
2230 } else {
2231 /* no content length was defined */
2232 s->length = NULL;
2233 }
2234
2235 if (s->length) {
2236 if (*s->length == '\0') {
2237 s->content_length = 0;
2238 } else {
2239 string err;
2240 s->content_length = strict_strtoll(s->length, 10, &err);
2241 if (!err.empty()) {
2242 ldout(s->cct, 10) << "bad content length, aborting" << dendl;
2243 return -EINVAL;
2244 }
2245 }
2246 }
2247
2248 if (s->content_length < 0) {
2249 ldout(s->cct, 10) << "negative content length, aborting" << dendl;
2250 return -EINVAL;
2251 }
2252
2253 map<string, string>::iterator giter;
2254 for (giter = generic_attrs_map.begin(); giter != generic_attrs_map.end();
2255 ++giter) {
2256 const char *env = info.env->get(giter->first.c_str());
2257 if (env) {
2258 s->generic_attrs[giter->second] = env;
2259 }
2260 }
2261
2262 if (g_conf()->rgw_print_continue) {
2263 const char *expect = info.env->get("HTTP_EXPECT");
2264 s->expect_cont = (expect && !strcasecmp(expect, "100-continue"));
2265 }
2266 s->op = op_from_method(info.method);
2267
2268 info.init_meta_info(&s->has_bad_meta);
2269
2270 return 0;
2271 }
2272
2273 RGWHandler_REST* RGWREST::get_handler(
2274 rgw::sal::RGWRadosStore * const store,
2275 struct req_state* const s,
2276 const rgw::auth::StrategyRegistry& auth_registry,
2277 const std::string& frontend_prefix,
2278 RGWRestfulIO* const rio,
2279 RGWRESTMgr** const pmgr,
2280 int* const init_error
2281 ) {
2282 *init_error = preprocess(s, rio);
2283 if (*init_error < 0) {
2284 return nullptr;
2285 }
2286
2287 RGWRESTMgr *m = mgr.get_manager(s, frontend_prefix, s->decoded_uri,
2288 &s->relative_uri);
2289 if (! m) {
2290 *init_error = -ERR_METHOD_NOT_ALLOWED;
2291 return nullptr;
2292 }
2293
2294 if (pmgr) {
2295 *pmgr = m;
2296 }
2297
2298 RGWHandler_REST* handler = m->get_handler(store, s, auth_registry, frontend_prefix);
2299 if (! handler) {
2300 *init_error = -ERR_METHOD_NOT_ALLOWED;
2301 return NULL;
2302 }
2303 *init_error = handler->init(store, s, rio);
2304 if (*init_error < 0) {
2305 m->put_handler(handler);
2306 return nullptr;
2307 }
2308
2309 return handler;
2310 } /* get stream handler */