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