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