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