X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Frgw%2Frgw_rest.cc;fp=src%2Fceph%2Fsrc%2Frgw%2Frgw_rest.cc;h=0000000000000000000000000000000000000000;hb=7da45d65be36d36b880cc55c5036e96c24b53f00;hp=27cdaf1353e6c745c934a19b27af1f04c9f218a1;hpb=691462d09d0987b47e112d6ee8740375df3c51b2;p=stor4nfv.git diff --git a/src/ceph/src/rgw/rgw_rest.cc b/src/ceph/src/rgw/rgw_rest.cc deleted file mode 100644 index 27cdaf1..0000000 --- a/src/ceph/src/rgw/rgw_rest.cc +++ /dev/null @@ -1,2337 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- -// vim: ts=8 sw=2 smarttab - - -#include -#include - -#include -#include "common/Formatter.h" -#include "common/HTMLFormatter.h" -#include "common/utf8.h" -#include "include/str_list.h" -#include "rgw_common.h" -#include "rgw_rados.h" -#include "rgw_formats.h" -#include "rgw_op.h" -#include "rgw_rest.h" -#include "rgw_rest_swift.h" -#include "rgw_rest_s3.h" -#include "rgw_swift_auth.h" -#include "rgw_cors_s3.h" - -#include "rgw_client_io.h" -#include "rgw_resolve.h" - -#include - -#define dout_subsys ceph_subsys_rgw - -struct rgw_http_status_code { - int code; - const char *name; -}; - -const static struct rgw_http_status_code http_codes[] = { - { 100, "Continue" }, - { 200, "OK" }, - { 201, "Created" }, - { 202, "Accepted" }, - { 204, "No Content" }, - { 205, "Reset Content" }, - { 206, "Partial Content" }, - { 207, "Multi Status" }, - { 208, "Already Reported" }, - { 300, "Multiple Choices" }, - { 301, "Moved Permanently" }, - { 302, "Found" }, - { 303, "See Other" }, - { 304, "Not Modified" }, - { 305, "User Proxy" }, - { 306, "Switch Proxy" }, - { 307, "Temporary Redirect" }, - { 308, "Permanent Redirect" }, - { 400, "Bad Request" }, - { 401, "Unauthorized" }, - { 402, "Payment Required" }, - { 403, "Forbidden" }, - { 404, "Not Found" }, - { 405, "Method Not Allowed" }, - { 406, "Not Acceptable" }, - { 407, "Proxy Authentication Required" }, - { 408, "Request Timeout" }, - { 409, "Conflict" }, - { 410, "Gone" }, - { 411, "Length Required" }, - { 412, "Precondition Failed" }, - { 413, "Request Entity Too Large" }, - { 414, "Request-URI Too Long" }, - { 415, "Unsupported Media Type" }, - { 416, "Requested Range Not Satisfiable" }, - { 417, "Expectation Failed" }, - { 422, "Unprocessable Entity" }, - { 500, "Internal Server Error" }, - { 501, "Not Implemented" }, - { 0, NULL }, -}; - -struct rgw_http_attr { - const char *rgw_attr; - const char *http_attr; -}; - -/* - * mapping between rgw object attrs and output http fields - */ -static const struct rgw_http_attr base_rgw_to_http_attrs[] = { - { RGW_ATTR_CONTENT_LANG, "Content-Language" }, - { RGW_ATTR_EXPIRES, "Expires" }, - { RGW_ATTR_CACHE_CONTROL, "Cache-Control" }, - { RGW_ATTR_CONTENT_DISP, "Content-Disposition" }, - { RGW_ATTR_CONTENT_ENC, "Content-Encoding" }, - { RGW_ATTR_USER_MANIFEST, "X-Object-Manifest" }, - { RGW_ATTR_X_ROBOTS_TAG , "X-Robots-Tag" }, - /* RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION header depends on access mode: - * S3 endpoint: x-amz-website-redirect-location - * S3Website endpoint: Location - */ - { RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION, "x-amz-website-redirect-location" }, -}; - - -struct generic_attr { - const char *http_header; - const char *rgw_attr; -}; - -/* - * mapping between http env fields and rgw object attrs - */ -static const struct generic_attr generic_attrs[] = { - { "CONTENT_TYPE", RGW_ATTR_CONTENT_TYPE }, - { "HTTP_CONTENT_LANGUAGE", RGW_ATTR_CONTENT_LANG }, - { "HTTP_EXPIRES", RGW_ATTR_EXPIRES }, - { "HTTP_CACHE_CONTROL", RGW_ATTR_CACHE_CONTROL }, - { "HTTP_CONTENT_DISPOSITION", RGW_ATTR_CONTENT_DISP }, - { "HTTP_CONTENT_ENCODING", RGW_ATTR_CONTENT_ENC }, - { "HTTP_X_ROBOTS_TAG", RGW_ATTR_X_ROBOTS_TAG }, -}; - -map rgw_to_http_attrs; -static map generic_attrs_map; -map http_status_names; - -/* - * make attrs look_like_this - * converts dashes to underscores - */ -string lowercase_underscore_http_attr(const string& orig) -{ - const char *s = orig.c_str(); - char buf[orig.size() + 1]; - buf[orig.size()] = '\0'; - - for (size_t i = 0; i < orig.size(); ++i, ++s) { - switch (*s) { - case '-': - buf[i] = '_'; - break; - default: - buf[i] = tolower(*s); - } - } - return string(buf); -} - -/* - * make attrs LOOK_LIKE_THIS - * converts dashes to underscores - */ -string uppercase_underscore_http_attr(const string& orig) -{ - const char *s = orig.c_str(); - char buf[orig.size() + 1]; - buf[orig.size()] = '\0'; - - for (size_t i = 0; i < orig.size(); ++i, ++s) { - switch (*s) { - case '-': - buf[i] = '_'; - break; - default: - buf[i] = toupper(*s); - } - } - return string(buf); -} - -/* - * make attrs look-like-this - * converts underscores to dashes - */ -string lowercase_dash_http_attr(const string& orig) -{ - const char *s = orig.c_str(); - char buf[orig.size() + 1]; - buf[orig.size()] = '\0'; - - for (size_t i = 0; i < orig.size(); ++i, ++s) { - switch (*s) { - case '_': - buf[i] = '-'; - break; - default: - buf[i] = tolower(*s); - } - } - return string(buf); -} - -/* - * make attrs Look-Like-This - * converts underscores to dashes - */ -string camelcase_dash_http_attr(const string& orig) -{ - const char *s = orig.c_str(); - char buf[orig.size() + 1]; - buf[orig.size()] = '\0'; - - bool last_sep = true; - - for (size_t i = 0; i < orig.size(); ++i, ++s) { - switch (*s) { - case '_': - case '-': - buf[i] = '-'; - last_sep = true; - break; - default: - if (last_sep) { - buf[i] = toupper(*s); - } else { - buf[i] = tolower(*s); - } - last_sep = false; - } - } - return string(buf); -} - -/* avoid duplicate hostnames in hostnames lists */ -static set hostnames_set; -static set hostnames_s3website_set; - -void rgw_rest_init(CephContext *cct, RGWRados *store, RGWZoneGroup& zone_group) -{ - store->init_host_id(); - - for (const auto& rgw2http : base_rgw_to_http_attrs) { - rgw_to_http_attrs[rgw2http.rgw_attr] = rgw2http.http_attr; - } - - for (const auto& http2rgw : generic_attrs) { - generic_attrs_map[http2rgw.http_header] = http2rgw.rgw_attr; - } - - list extended_http_attrs; - get_str_list(cct->_conf->rgw_extended_http_attrs, extended_http_attrs); - - list::iterator iter; - for (iter = extended_http_attrs.begin(); iter != extended_http_attrs.end(); ++iter) { - string rgw_attr = RGW_ATTR_PREFIX; - rgw_attr.append(lowercase_underscore_http_attr(*iter)); - - rgw_to_http_attrs[rgw_attr] = camelcase_dash_http_attr(*iter); - - string http_header = "HTTP_"; - http_header.append(uppercase_underscore_http_attr(*iter)); - - generic_attrs_map[http_header] = rgw_attr; - } - - for (const struct rgw_http_status_code *h = http_codes; h->code; h++) { - http_status_names[h->code] = h->name; - } - - hostnames_set.insert(cct->_conf->rgw_dns_name); - hostnames_set.insert(zone_group.hostnames.begin(), zone_group.hostnames.end()); - hostnames_set.erase(""); // filter out empty hostnames - ldout(cct, 20) << "RGW hostnames: " << hostnames_set << dendl; - /* TODO: We should have a sanity check that no hostname matches the end of - * any other hostname, otherwise we will get ambigious results from - * rgw_find_host_in_domains. - * Eg: - * Hostnames: [A, B.A] - * Inputs: [Z.A, X.B.A] - * Z.A clearly splits to subdomain=Z, domain=Z - * X.B.A ambigously splits to both {X, B.A} and {X.B, A} - */ - - hostnames_s3website_set.insert(cct->_conf->rgw_dns_s3website_name); - hostnames_s3website_set.insert(zone_group.hostnames_s3website.begin(), zone_group.hostnames_s3website.end()); - hostnames_s3website_set.erase(""); // filter out empty hostnames - ldout(cct, 20) << "RGW S3website hostnames: " << hostnames_s3website_set << dendl; - /* TODO: we should repeat the hostnames_set sanity check here - * and ALSO decide about overlap, if any - */ -} - -static bool str_ends_with(const string& s, const string& suffix, size_t *pos) -{ - size_t len = suffix.size(); - if (len > (size_t)s.size()) { - return false; - } - - ssize_t p = s.size() - len; - if (pos) { - *pos = p; - } - - return s.compare(p, len, suffix) == 0; -} - -static bool rgw_find_host_in_domains(const string& host, string *domain, string *subdomain, set valid_hostnames_set) -{ - set::iterator iter; - /** TODO, Future optimization - * store hostnames_set elements _reversed_, and look for a prefix match, - * which is much faster than a suffix match. - */ - for (iter = valid_hostnames_set.begin(); iter != valid_hostnames_set.end(); ++iter) { - size_t pos; - if (!str_ends_with(host, *iter, &pos)) - continue; - - if (pos == 0) { - *domain = host; - subdomain->clear(); - } else { - if (host[pos - 1] != '.') { - continue; - } - - *domain = host.substr(pos); - *subdomain = host.substr(0, pos - 1); - } - return true; - } - return false; -} - -static void dump_status(struct req_state *s, int status, - const char *status_name) -{ - s->formatter->set_status(status, status_name); - try { - RESTFUL_IO(s)->send_status(status, status_name); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: s->cio->send_status() returned err=" - << e.what() << dendl; - } -} - -void rgw_flush_formatter_and_reset(struct req_state *s, Formatter *formatter) -{ - std::ostringstream oss; - formatter->output_footer(); - formatter->flush(oss); - std::string outs(oss.str()); - if (!outs.empty() && s->op != OP_HEAD) { - dump_body(s, outs); - } - - s->formatter->reset(); -} - -void rgw_flush_formatter(struct req_state *s, Formatter *formatter) -{ - std::ostringstream oss; - formatter->flush(oss); - std::string outs(oss.str()); - if (!outs.empty() && s->op != OP_HEAD) { - dump_body(s, outs); - } -} - -void dump_errno(int http_ret, string& out) { - stringstream ss; - - ss << http_ret << " " << http_status_names[http_ret]; - out = ss.str(); -} - -void dump_errno(const struct rgw_err &err, string& out) { - dump_errno(err.http_ret, out); -} - -void dump_errno(struct req_state *s) -{ - dump_status(s, s->err.http_ret, http_status_names[s->err.http_ret]); -} - -void dump_errno(struct req_state *s, int http_ret) -{ - dump_status(s, http_ret, http_status_names[http_ret]); -} - -void dump_header(struct req_state* const s, - const boost::string_ref& name, - const boost::string_ref& val) -{ - try { - RESTFUL_IO(s)->send_header(name, val); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: s->cio->send_header() returned err=" - << e.what() << dendl; - } -} - -static inline boost::string_ref get_sanitized_hdrval(ceph::buffer::list& raw) -{ - /* std::string and thus boost::string_ref ARE OBLIGED to carry multiple - * 0x00 and count them to the length of a string. We need to take that - * into consideration and sanitize the size of a ceph::buffer::list used - * to store metadata values (x-amz-meta-*, X-Container-Meta-*, etags). - * Otherwise we might send 0x00 to clients. */ - const char* const data = raw.c_str(); - size_t len = raw.length(); - - if (len && data[len - 1] == '\0') { - /* That's the case - the null byte has been included at the last position - * of the bufferlist. We need to restore the proper string length we'll - * pass to string_ref. */ - len--; - } - - return boost::string_ref(data, len); -} - -void dump_header(struct req_state* const s, - const boost::string_ref& name, - ceph::buffer::list& bl) -{ - return dump_header(s, name, get_sanitized_hdrval(bl)); -} - -void dump_header(struct req_state* const s, - const boost::string_ref& name, - const long long val) -{ - char buf[32]; - const auto len = snprintf(buf, sizeof(buf), "%lld", val); - - return dump_header(s, name, boost::string_ref(buf, len)); -} - -void dump_header(struct req_state* const s, - const boost::string_ref& name, - const utime_t& ut) -{ - char buf[32]; - const auto len = snprintf(buf, sizeof(buf), "%lld.%05d", - static_cast(ut.sec()), - static_cast(ut.usec() / 10)); - - return dump_header(s, name, boost::string_ref(buf, len)); -} - -void dump_content_length(struct req_state* const s, const uint64_t len) -{ - try { - RESTFUL_IO(s)->send_content_length(len); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: s->cio->send_content_length() returned err=" - << e.what() << dendl; - } - dump_header(s, "Accept-Ranges", "bytes"); -} - -static void dump_chunked_encoding(struct req_state* const s) -{ - try { - RESTFUL_IO(s)->send_chunked_transfer_encoding(); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->send_chunked_transfer_encoding()" - << " returned err=" << e.what() << dendl; - } -} - -void dump_etag(struct req_state* const s, - const boost::string_ref& etag, - const bool quoted) -{ - if (etag.empty()) { - return; - } - - if (s->prot_flags & RGW_REST_SWIFT && ! quoted) { - return dump_header(s, "etag", etag); - } else { - return dump_header_quoted(s, "ETag", etag); - } -} - -void dump_etag(struct req_state* const s, - ceph::buffer::list& bl_etag, - const bool quoted) -{ - return dump_etag(s, get_sanitized_hdrval(bl_etag), quoted); -} - -void dump_bucket_from_state(struct req_state *s) -{ - if (g_conf->rgw_expose_bucket && ! s->bucket_name.empty()) { - if (! s->bucket_tenant.empty()) { - dump_header(s, "Bucket", - url_encode(s->bucket_tenant + "/" + s->bucket_name)); - } else { - dump_header(s, "Bucket", url_encode(s->bucket_name)); - } - } -} - -void dump_uri_from_state(struct req_state *s) -{ - if (strcmp(s->info.request_uri.c_str(), "/") == 0) { - - string location = "http://"; - string server = s->info.env->get("SERVER_NAME", ""); - location.append(server); - location += "/"; - if (!s->bucket_name.empty()) { - if (!s->bucket_tenant.empty()) { - location += s->bucket_tenant; - location += ":"; - } - location += s->bucket_name; - location += "/"; - if (!s->object.empty()) { - location += s->object.name; - dump_header(s, "Location", location); - } - } - } else { - dump_header_quoted(s, "Location", s->info.request_uri); - } -} - -void dump_redirect(struct req_state * const s, const std::string& redirect) -{ - return dump_header_if_nonempty(s, "Location", redirect); -} - -static size_t dump_time_header_impl(char (×tr)[TIME_BUF_SIZE], - const real_time t) -{ - const utime_t ut(t); - time_t secs = static_cast(ut.sec()); - - struct tm result; - const struct tm * const tmp = gmtime_r(&secs, &result); - if (tmp == nullptr) { - return 0; - } - - return strftime(timestr, sizeof(timestr), - "%a, %d %b %Y %H:%M:%S %Z", tmp); -} - -void dump_time_header(struct req_state *s, const char *name, real_time t) -{ - char timestr[TIME_BUF_SIZE]; - - const size_t len = dump_time_header_impl(timestr, t); - if (len == 0) { - return; - } - - return dump_header(s, name, boost::string_ref(timestr, len)); -} - -std::string dump_time_to_str(const real_time& t) -{ - char timestr[TIME_BUF_SIZE]; - dump_time_header_impl(timestr, t); - - return timestr; -} - - -void dump_last_modified(struct req_state *s, real_time t) -{ - dump_time_header(s, "Last-Modified", t); -} - -void dump_epoch_header(struct req_state *s, const char *name, real_time t) -{ - utime_t ut(t); - char buf[65]; - const auto len = snprintf(buf, sizeof(buf), "%lld.%09lld", - (long long)ut.sec(), - (long long)ut.nsec()); - - return dump_header(s, name, boost::string_ref(buf, len)); -} - -void dump_time(struct req_state *s, const char *name, real_time *t) -{ - char buf[TIME_BUF_SIZE]; - rgw_to_iso8601(*t, buf, sizeof(buf)); - - s->formatter->dump_string(name, buf); -} - -void dump_owner(struct req_state *s, const rgw_user& id, string& name, - const char *section) -{ - if (!section) - section = "Owner"; - s->formatter->open_object_section(section); - s->formatter->dump_string("ID", id.to_str()); - s->formatter->dump_string("DisplayName", name); - s->formatter->close_section(); -} - -void dump_access_control(struct req_state *s, const char *origin, - const char *meth, - const char *hdr, const char *exp_hdr, - uint32_t max_age) { - if (origin && (origin[0] != '\0')) { - dump_header(s, "Access-Control-Allow-Origin", origin); - /* If the server specifies an origin host rather than "*", - * then it must also include Origin in the Vary response header - * to indicate to clients that server responses will differ - * based on the value of the Origin request header. - */ - if (strcmp(origin, "*") != 0) { - dump_header(s, "Vary", "Origin"); - } - - if (meth && (meth[0] != '\0')) { - dump_header(s, "Access-Control-Allow-Methods", meth); - } - if (hdr && (hdr[0] != '\0')) { - dump_header(s, "Access-Control-Allow-Headers", hdr); - } - if (exp_hdr && (exp_hdr[0] != '\0')) { - dump_header(s, "Access-Control-Expose-Headers", exp_hdr); - } - if (max_age != CORS_MAX_AGE_INVALID) { - dump_header(s, "Access-Control-Max-Age", max_age); - } - } -} - -void dump_access_control(req_state *s, RGWOp *op) -{ - string origin; - string method; - string header; - string exp_header; - unsigned max_age = CORS_MAX_AGE_INVALID; - - if (!op->generate_cors_headers(origin, method, header, exp_header, &max_age)) - return; - - dump_access_control(s, origin.c_str(), method.c_str(), header.c_str(), - exp_header.c_str(), max_age); -} - -void dump_start(struct req_state *s) -{ - if (!s->content_started) { - s->formatter->output_header(); - s->content_started = true; - } -} - -void dump_trans_id(req_state *s) -{ - if (s->prot_flags & RGW_REST_SWIFT) { - dump_header(s, "X-Trans-Id", s->trans_id); - dump_header(s, "X-Openstack-Request-Id", s->trans_id); - } else if (s->trans_id.length()) { - dump_header(s, "x-amz-request-id", s->trans_id); - } -} - -void end_header(struct req_state* s, RGWOp* op, const char *content_type, - const int64_t proposed_content_length, bool force_content_type, - bool force_no_error) -{ - string ctype; - - dump_trans_id(s); - - if ((!s->is_err()) && - (s->bucket_info.owner != s->user->user_id) && - (s->bucket_info.requester_pays)) { - dump_header(s, "x-amz-request-charged", "requester"); - } - - if (op) { - dump_access_control(s, op); - } - - if (s->prot_flags & RGW_REST_SWIFT && !content_type) { - force_content_type = true; - } - - /* do not send content type if content length is zero - and the content type was not set by the user */ - if (force_content_type || - (!content_type && s->formatter->get_len() != 0) || s->is_err()){ - switch (s->format) { - case RGW_FORMAT_XML: - ctype = "application/xml"; - break; - case RGW_FORMAT_JSON: - ctype = "application/json"; - break; - case RGW_FORMAT_HTML: - ctype = "text/html"; - break; - default: - ctype = "text/plain"; - break; - } - if (s->prot_flags & RGW_REST_SWIFT) - ctype.append("; charset=utf-8"); - content_type = ctype.c_str(); - } - if (!force_no_error && s->is_err()) { - dump_start(s); - dump(s); - dump_content_length(s, s->formatter->get_len()); - } else { - if (proposed_content_length == CHUNKED_TRANSFER_ENCODING) { - dump_chunked_encoding(s); - } else if (proposed_content_length != NO_CONTENT_LENGTH) { - dump_content_length(s, proposed_content_length); - } - } - - if (content_type) { - dump_header(s, "Content-Type", content_type); - } - - try { - RESTFUL_IO(s)->complete_header(); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->complete_header() returned err=" - << e.what() << dendl; - } - - ACCOUNTING_IO(s)->set_account(true); - rgw_flush_formatter_and_reset(s, s->formatter); -} - -void abort_early(struct req_state *s, RGWOp* op, int err_no, - RGWHandler* handler) -{ - string error_content(""); - if (!s->formatter) { - s->formatter = new JSONFormatter; - s->format = RGW_FORMAT_JSON; - } - - // op->error_handler is responsible for calling it's handler error_handler - if (op != NULL) { - int new_err_no; - new_err_no = op->error_handler(err_no, &error_content); - ldout(s->cct, 20) << "op->ERRORHANDLER: err_no=" << err_no - << " new_err_no=" << new_err_no << dendl; - err_no = new_err_no; - } else if (handler != NULL) { - int new_err_no; - new_err_no = handler->error_handler(err_no, &error_content); - ldout(s->cct, 20) << "handler->ERRORHANDLER: err_no=" << err_no - << " new_err_no=" << new_err_no << dendl; - err_no = new_err_no; - } - - // If the error handler(s) above dealt with it completely, they should have - // returned 0. If non-zero, we need to continue here. - if (err_no) { - // Watch out, we might have a custom error state already set! - if (!s->err.http_ret || s->err.http_ret == 200) { - set_req_state_err(s, err_no); - } - dump_errno(s); - dump_bucket_from_state(s); - if (err_no == -ERR_PERMANENT_REDIRECT || err_no == -ERR_WEBSITE_REDIRECT) { - string dest_uri; - if (!s->redirect.empty()) { - dest_uri = s->redirect; - } else if (!s->zonegroup_endpoint.empty()) { - dest_uri = s->zonegroup_endpoint; - /* - * reqest_uri is always start with slash, so we need to remove - * the unnecessary slash at the end of dest_uri. - */ - if (dest_uri[dest_uri.size() - 1] == '/') { - dest_uri = dest_uri.substr(0, dest_uri.size() - 1); - } - dest_uri += s->info.request_uri; - dest_uri += "?"; - dest_uri += s->info.request_params; - } - - if (!dest_uri.empty()) { - dump_redirect(s, dest_uri); - } - } - - if (!error_content.empty()) { - /* - * TODO we must add all error entries as headers here: - * when having a working errordoc, then the s3 error fields are - * rendered as HTTP headers, e.g.: - * x-amz-error-code: NoSuchKey - * x-amz-error-message: The specified key does not exist. - * x-amz-error-detail-Key: foo - */ - end_header(s, op, NULL, error_content.size(), false, true); - RESTFUL_IO(s)->send_body(error_content.c_str(), error_content.size()); - } else { - end_header(s, op); - } - } - perfcounter->inc(l_rgw_failed_req); -} - -void dump_continue(struct req_state * const s) -{ - try { - RESTFUL_IO(s)->send_100_continue(); - } catch (rgw::io::Exception& e) { - ldout(s->cct, 0) << "ERROR: RESTFUL_IO(s)->send_100_continue() returned err=" - << e.what() << dendl; - } -} - -void dump_range(struct req_state* const s, - const uint64_t ofs, - const uint64_t end, - const uint64_t total) -{ - /* dumping range into temp buffer first, as libfcgi will fail to digest - * %lld */ - char range_buf[128]; - size_t len; - - if (! total) { - len = snprintf(range_buf, sizeof(range_buf), "bytes */%lld", - static_cast(total)); - } else { - len = snprintf(range_buf, sizeof(range_buf), "bytes %lld-%lld/%lld", - static_cast(ofs), - static_cast(end), - static_cast(total)); - } - - return dump_header(s, "Content-Range", boost::string_ref(range_buf, len)); -} - - -int dump_body(struct req_state* const s, - const char* const buf, - const size_t len) -{ - try { - return RESTFUL_IO(s)->send_body(buf, len); - } catch (rgw::io::Exception& e) { - return -e.code().value(); - } -} - -int dump_body(struct req_state* const s, /* const */ ceph::buffer::list& bl) -{ - return dump_body(s, bl.c_str(), bl.length()); -} - -int dump_body(struct req_state* const s, const std::string& str) -{ - return dump_body(s, str.c_str(), str.length()); -} - -int recv_body(struct req_state* const s, - char* const buf, - const size_t max) -{ - try { - return RESTFUL_IO(s)->recv_body(buf, max); - } catch (rgw::io::Exception& e) { - return -e.code().value(); - } -} - -int RGWGetObj_ObjStore::get_params() -{ - range_str = s->info.env->get("HTTP_RANGE"); - if_mod = s->info.env->get("HTTP_IF_MODIFIED_SINCE"); - if_unmod = s->info.env->get("HTTP_IF_UNMODIFIED_SINCE"); - if_match = s->info.env->get("HTTP_IF_MATCH"); - if_nomatch = s->info.env->get("HTTP_IF_NONE_MATCH"); - - if (s->system_request) { - mod_zone_id = s->info.env->get_int("HTTP_DEST_ZONE_SHORT_ID", 0); - mod_pg_ver = s->info.env->get_int("HTTP_DEST_PG_VER", 0); - rgwx_stat = s->info.args.exists(RGW_SYS_PARAM_PREFIX "stat"); - get_data &= (!rgwx_stat); - } - - /* start gettorrent */ - bool is_torrent = s->info.args.exists(GET_TORRENT); - bool torrent_flag = s->cct->_conf->rgw_torrent_flag; - if (torrent_flag && is_torrent) - { - int ret = 0; - ret = torrent.get_params(); - if (ret < 0) - { - return ret; - } - } - /* end gettorrent */ - - return 0; -} - -int RESTArgs::get_string(struct req_state *s, const string& name, - const string& def_val, string *val, bool *existed) -{ - bool exists; - *val = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - return 0; -} - -int RESTArgs::get_uint64(struct req_state *s, const string& name, - uint64_t def_val, uint64_t *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - int r = stringtoull(sval, val); - if (r < 0) - return r; - - return 0; -} - -int RESTArgs::get_int64(struct req_state *s, const string& name, - int64_t def_val, int64_t *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - int r = stringtoll(sval, val); - if (r < 0) - return r; - - return 0; -} - -int RESTArgs::get_uint32(struct req_state *s, const string& name, - uint32_t def_val, uint32_t *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - int r = stringtoul(sval, val); - if (r < 0) - return r; - - return 0; -} - -int RESTArgs::get_int32(struct req_state *s, const string& name, - int32_t def_val, int32_t *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - int r = stringtol(sval, val); - if (r < 0) - return r; - - return 0; -} - -int RESTArgs::get_time(struct req_state *s, const string& name, - const utime_t& def_val, utime_t *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - uint64_t epoch, nsec; - - int r = utime_t::parse_date(sval, &epoch, &nsec); - if (r < 0) - return r; - - *val = utime_t(epoch, nsec); - - return 0; -} - -int RESTArgs::get_epoch(struct req_state *s, const string& name, uint64_t def_val, uint64_t *epoch, bool *existed) -{ - bool exists; - string date = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *epoch = def_val; - return 0; - } - - int r = utime_t::parse_date(date, epoch, NULL); - if (r < 0) - return r; - - return 0; -} - -int RESTArgs::get_bool(struct req_state *s, const string& name, bool def_val, bool *val, bool *existed) -{ - bool exists; - string sval = s->info.args.get(name, &exists); - - if (existed) - *existed = exists; - - if (!exists) { - *val = def_val; - return 0; - } - - const char *str = sval.c_str(); - - if (sval.empty() || - strcasecmp(str, "true") == 0 || - sval.compare("1") == 0) { - *val = true; - return 0; - } - - if (strcasecmp(str, "false") != 0 && - sval.compare("0") != 0) { - *val = def_val; - return -EINVAL; - } - - *val = false; - return 0; -} - - -void RGWRESTFlusher::do_start(int ret) -{ - set_req_state_err(s, ret); /* no going back from here */ - dump_errno(s); - dump_start(s); - end_header(s, op); - rgw_flush_formatter_and_reset(s, s->formatter); -} - -void RGWRESTFlusher::do_flush() -{ - rgw_flush_formatter(s, s->formatter); -} - -int RGWPutObj_ObjStore::verify_params() -{ - if (s->length) { - off_t len = atoll(s->length); - if (len > (off_t)(s->cct->_conf->rgw_max_put_size)) { - return -ERR_TOO_LARGE; - } - } - - return 0; -} - -int RGWPutObj_ObjStore::get_params() -{ - /* start gettorrent */ - if (s->cct->_conf->rgw_torrent_flag) - { - int ret = 0; - ret = torrent.get_params(); - ldout(s->cct, 5) << "NOTICE: open produce torrent file " << dendl; - if (ret < 0) - { - return ret; - } - torrent.set_info_name((s->object).name); - } - /* end gettorrent */ - supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5"); - - return 0; -} - -int RGWPutObj_ObjStore::get_data(bufferlist& bl) -{ - size_t cl; - uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; - if (s->length) { - cl = atoll(s->length) - ofs; - if (cl > chunk_size) - cl = chunk_size; - } else { - cl = chunk_size; - } - - int len = 0; - { - ACCOUNTING_IO(s)->set_account(true); - bufferptr bp(cl); - - const auto read_len = recv_body(s, bp.c_str(), cl); - if (read_len < 0) { - return read_len; - } - - len = read_len; - bl.append(bp, 0, len); - - ACCOUNTING_IO(s)->set_account(false); - } - - if ((uint64_t)ofs + len > s->cct->_conf->rgw_max_put_size) { - return -ERR_TOO_LARGE; - } - - if (!ofs) - supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5"); - - return len; -} - - -/* - * parses params in the format: 'first; param1=foo; param2=bar' - */ -void RGWPostObj_ObjStore::parse_boundary_params(const std::string& params_str, - std::string& first, - std::map& params) -{ - size_t pos = params_str.find(';'); - if (std::string::npos == pos) { - first = rgw_trim_whitespace(params_str); - return; - } - - first = rgw_trim_whitespace(params_str.substr(0, pos)); - pos++; - - while (pos < params_str.size()) { - size_t end = params_str.find(';', pos); - if (std::string::npos == end) { - end = params_str.size(); - } - - std::string param = params_str.substr(pos, end - pos); - size_t eqpos = param.find('='); - - if (std::string::npos != eqpos) { - std::string param_name = rgw_trim_whitespace(param.substr(0, eqpos)); - std::string val = rgw_trim_quotes(param.substr(eqpos + 1)); - params[std::move(param_name)] = std::move(val); - } else { - params[rgw_trim_whitespace(param)] = ""; - } - - pos = end + 1; - } -} - -int RGWPostObj_ObjStore::parse_part_field(const std::string& line, - std::string& field_name, /* out */ - post_part_field& field) /* out */ -{ - size_t pos = line.find(':'); - if (pos == string::npos) - return -EINVAL; - - field_name = line.substr(0, pos); - if (pos >= line.size() - 1) - return 0; - - parse_boundary_params(line.substr(pos + 1), field.val, field.params); - - return 0; -} - -static bool is_crlf(const char *s) -{ - return (*s == '\r' && *(s + 1) == '\n'); -} - -/* - * find the index of the boundary, if exists, or optionally the next end of line - * also returns how many bytes to skip - */ -static int index_of(ceph::bufferlist& bl, - uint64_t max_len, - const std::string& str, - const bool check_crlf, - bool& reached_boundary, - int& skip) -{ - reached_boundary = false; - skip = 0; - - if (str.size() < 2) // we assume boundary is at least 2 chars (makes it easier with crlf checks) - return -EINVAL; - - if (bl.length() < str.size()) - return -1; - - const char *buf = bl.c_str(); - const char *s = str.c_str(); - - if (max_len > bl.length()) - max_len = bl.length(); - - for (uint64_t i = 0; i < max_len; i++, buf++) { - if (check_crlf && - i >= 1 && - is_crlf(buf - 1)) { - return i + 1; // skip the crlf - } - if ((i < max_len - str.size() + 1) && - (buf[0] == s[0] && buf[1] == s[1]) && - (strncmp(buf, s, str.size()) == 0)) { - reached_boundary = true; - skip = str.size(); - - /* oh, great, now we need to swallow the preceding crlf - * if exists - */ - if ((i >= 2) && - is_crlf(buf - 2)) { - i -= 2; - skip += 2; - } - return i; - } - } - - return -1; -} - -int RGWPostObj_ObjStore::read_with_boundary(ceph::bufferlist& bl, - uint64_t max, - const bool check_crlf, - bool& reached_boundary, - bool& done) -{ - uint64_t cl = max + 2 + boundary.size(); - - if (max > in_data.length()) { - uint64_t need_to_read = cl - in_data.length(); - - bufferptr bp(need_to_read); - - const auto read_len = recv_body(s, bp.c_str(), need_to_read); - if (read_len < 0) { - return read_len; - } - in_data.append(bp, 0, read_len); - } - - done = false; - int skip; - const int index = index_of(in_data, cl, boundary, check_crlf, - reached_boundary, skip); - if (index >= 0) { - max = index; - } - - if (max > in_data.length()) { - max = in_data.length(); - } - - bl.substr_of(in_data, 0, max); - - ceph::bufferlist new_read_data; - - /* - * now we need to skip boundary for next time, also skip any crlf, or - * check to see if it's the last final boundary (marked with "--" at the end - */ - if (reached_boundary) { - int left = in_data.length() - max; - if (left < skip + 2) { - int need = skip + 2 - left; - bufferptr boundary_bp(need); - const int r = recv_body(s, boundary_bp.c_str(), need); - if (r < 0) { - return r; - } - in_data.append(boundary_bp); - } - max += skip; // skip boundary for next time - if (in_data.length() >= max + 2) { - const char *data = in_data.c_str(); - if (is_crlf(data + max)) { - max += 2; - } else { - if (*(data + max) == '-' && - *(data + max + 1) == '-') { - done = true; - max += 2; - } - } - } - } - - new_read_data.substr_of(in_data, max, in_data.length() - max); - in_data = new_read_data; - - return 0; -} - -int RGWPostObj_ObjStore::read_line(ceph::bufferlist& bl, - const uint64_t max, - bool& reached_boundary, - bool& done) -{ - return read_with_boundary(bl, max, true, reached_boundary, done); -} - -int RGWPostObj_ObjStore::read_data(ceph::bufferlist& bl, - const uint64_t max, - bool& reached_boundary, - bool& done) -{ - return read_with_boundary(bl, max, false, reached_boundary, done); -} - - -int RGWPostObj_ObjStore::read_form_part_header(struct post_form_part* const part, - bool& done) -{ - bufferlist bl; - bool reached_boundary; - uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; - int r = read_line(bl, chunk_size, reached_boundary, done); - if (r < 0) { - return r; - } - - if (done) { - return 0; - } - - if (reached_boundary) { // skip the first boundary - r = read_line(bl, chunk_size, reached_boundary, done); - if (r < 0) { - return r; - } else if (done) { - return 0; - } - } - - while (true) { - /* - * iterate through fields - */ - std::string line = rgw_trim_whitespace(string(bl.c_str(), bl.length())); - - if (line.empty()) { - break; - } - - struct post_part_field field; - - string field_name; - r = parse_part_field(line, field_name, field); - if (r < 0) { - return r; - } - - part->fields[field_name] = field; - - if (stringcasecmp(field_name, "Content-Disposition") == 0) { - part->name = field.params["name"]; - } - - if (reached_boundary) { - break; - } - - r = read_line(bl, chunk_size, reached_boundary, done); - } - - return 0; -} - -bool RGWPostObj_ObjStore::part_str(parts_collection_t& parts, - const std::string& name, - std::string* val) -{ - const auto iter = parts.find(name); - if (std::end(parts) == iter) { - return false; - } - - ceph::bufferlist& data = iter->second.data; - std::string str = string(data.c_str(), data.length()); - *val = rgw_trim_whitespace(str); - return true; -} - -std::string RGWPostObj_ObjStore::get_part_str(parts_collection_t& parts, - const std::string& name, - const std::string& def_val) -{ - std::string val; - - if (part_str(parts, name, &val)) { - return val; - } else { - return rgw_trim_whitespace(def_val); - } -} - -bool RGWPostObj_ObjStore::part_bl(parts_collection_t& parts, - const std::string& name, - ceph::bufferlist* pbl) -{ - const auto iter = parts.find(name); - if (std::end(parts) == iter) { - return false; - } - - *pbl = iter->second.data; - return true; -} - -int RGWPostObj_ObjStore::verify_params() -{ - /* check that we have enough memory to store the object - note that this test isn't exact and may fail unintentionally - for large requests is */ - if (!s->length) { - return -ERR_LENGTH_REQUIRED; - } - off_t len = atoll(s->length); - if (len > (off_t)(s->cct->_conf->rgw_max_put_size)) { - return -ERR_TOO_LARGE; - } - - supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5"); - - return 0; -} - -int RGWPostObj_ObjStore::get_params() -{ - if (s->expect_cont) { - /* OK, here it really gets ugly. With POST, the params are embedded in the - * request body, so we need to continue before being able to actually look - * at them. This diverts from the usual request flow. */ - dump_continue(s); - s->expect_cont = false; - } - - std::string req_content_type_str = s->info.env->get("CONTENT_TYPE", ""); - std::string req_content_type; - std::map params; - parse_boundary_params(req_content_type_str, req_content_type, params); - - if (req_content_type.compare("multipart/form-data") != 0) { - err_msg = "Request Content-Type is not multipart/form-data"; - return -EINVAL; - } - - if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 20)) { - ldout(s->cct, 20) << "request content_type_str=" - << req_content_type_str << dendl; - ldout(s->cct, 20) << "request content_type params:" << dendl; - - for (const auto& pair : params) { - ldout(s->cct, 20) << " " << pair.first << " -> " << pair.second - << dendl; - } - } - - const auto iter = params.find("boundary"); - if (std::end(params) == iter) { - err_msg = "Missing multipart boundary specification"; - return -EINVAL; - } - - /* Create the boundary. */ - boundary = "--"; - boundary.append(iter->second); - - return 0; -} - - -int RGWPutACLs_ObjStore::get_params() -{ - const auto max_size = s->cct->_conf->rgw_max_put_param_size; - op_ret = rgw_rest_read_all_input(s, &data, &len, max_size, false); - return op_ret; -} - -int RGWPutLC_ObjStore::get_params() -{ - const auto max_size = s->cct->_conf->rgw_max_put_param_size; - op_ret = rgw_rest_read_all_input(s, &data, &len, max_size, false); - return op_ret; -} - -static int read_all_chunked_input(req_state *s, char **pdata, int *plen, const uint64_t max_read) -{ -#define READ_CHUNK 4096 -#define MAX_READ_CHUNK (128 * 1024) - int need_to_read = READ_CHUNK; - int total = need_to_read; - char *data = (char *)malloc(total + 1); - if (!data) - return -ENOMEM; - - int read_len = 0, len = 0; - do { - read_len = recv_body(s, data + len, need_to_read); - if (read_len < 0) { - free(data); - return read_len; - } - - len += read_len; - - if (read_len == need_to_read) { - if (need_to_read < MAX_READ_CHUNK) - need_to_read *= 2; - - if ((unsigned)total > max_read) { - free(data); - return -ERANGE; - } - total += need_to_read; - - void *p = realloc(data, total + 1); - if (!p) { - free(data); - return -ENOMEM; - } - data = (char *)p; - } else { - break; - } - - } while (true); - data[len] = '\0'; - - *pdata = data; - *plen = len; - - return 0; -} - -int rgw_rest_read_all_input(struct req_state *s, char **pdata, int *plen, - const uint64_t max_len, const bool allow_chunked) -{ - size_t cl = 0; - int len = 0; - char *data = NULL; - - if (s->length) - cl = atoll(s->length); - else if (!allow_chunked) - return -ERR_LENGTH_REQUIRED; - - if (cl) { - if (cl > (size_t)max_len) { - return -ERANGE; - } - data = (char *)malloc(cl + 1); - if (!data) { - return -ENOMEM; - } - len = recv_body(s, data, cl); - if (len < 0) { - free(data); - return len; - } - data[len] = '\0'; - } else if (allow_chunked && !s->length) { - const char *encoding = s->info.env->get("HTTP_TRANSFER_ENCODING"); - if (!encoding || strcmp(encoding, "chunked") != 0) - return -ERR_LENGTH_REQUIRED; - - int ret = read_all_chunked_input(s, &data, &len, max_len); - if (ret < 0) - return ret; - } - - *plen = len; - *pdata = data; - - return 0; -} - -int RGWCompleteMultipart_ObjStore::get_params() -{ - upload_id = s->info.args.get("uploadId"); - - if (upload_id.empty()) { - op_ret = -ENOTSUP; - return op_ret; - } - -#define COMPLETE_MULTIPART_MAX_LEN (1024 * 1024) /* api defines max 10,000 parts, this should be enough */ - op_ret = rgw_rest_read_all_input(s, &data, &len, COMPLETE_MULTIPART_MAX_LEN); - if (op_ret < 0) - return op_ret; - - return 0; -} - -int RGWListMultipart_ObjStore::get_params() -{ - upload_id = s->info.args.get("uploadId"); - - if (upload_id.empty()) { - op_ret = -ENOTSUP; - } - string marker_str = s->info.args.get("part-number-marker"); - - if (!marker_str.empty()) { - string err; - marker = strict_strtol(marker_str.c_str(), 10, &err); - if (!err.empty()) { - ldout(s->cct, 20) << "bad marker: " << marker << dendl; - op_ret = -EINVAL; - return op_ret; - } - } - - string str = s->info.args.get("max-parts"); - if (!str.empty()) - max_parts = atoi(str.c_str()); - - return op_ret; -} - -int RGWListBucketMultiparts_ObjStore::get_params() -{ - delimiter = s->info.args.get("delimiter"); - prefix = s->info.args.get("prefix"); - string str = s->info.args.get("max-parts"); - if (!str.empty()) - max_uploads = atoi(str.c_str()); - else - max_uploads = default_max; - - string key_marker = s->info.args.get("key-marker"); - string upload_id_marker = s->info.args.get("upload-id-marker"); - if (!key_marker.empty()) - marker.init(key_marker, upload_id_marker); - - return 0; -} - -int RGWDeleteMultiObj_ObjStore::get_params() -{ - - if (s->bucket_name.empty()) { - op_ret = -EINVAL; - return op_ret; - } - - // everything is probably fine, set the bucket - bucket = s->bucket; - - const auto max_size = s->cct->_conf->rgw_max_put_param_size; - op_ret = rgw_rest_read_all_input(s, &data, &len, max_size, false); - return op_ret; -} - - -void RGWRESTOp::send_response() -{ - if (!flusher.did_start()) { - set_req_state_err(s, http_ret); - dump_errno(s); - end_header(s, this); - } - flusher.flush(); -} - -int RGWRESTOp::verify_permission() -{ - return check_caps(s->user->caps); -} - -RGWOp* RGWHandler_REST::get_op(RGWRados* store) -{ - RGWOp *op; - switch (s->op) { - case OP_GET: - op = op_get(); - break; - case OP_PUT: - op = op_put(); - break; - case OP_DELETE: - op = op_delete(); - break; - case OP_HEAD: - op = op_head(); - break; - case OP_POST: - op = op_post(); - break; - case OP_COPY: - op = op_copy(); - break; - case OP_OPTIONS: - op = op_options(); - break; - default: - return NULL; - } - - if (op) { - op->init(store, s, this); - } - return op; -} /* get_op */ - -void RGWHandler_REST::put_op(RGWOp* op) -{ - delete op; -} /* put_op */ - -int RGWHandler_REST::allocate_formatter(struct req_state *s, - int default_type, - bool configurable) -{ - s->format = default_type; - if (configurable) { - string format_str = s->info.args.get("format"); - if (format_str.compare("xml") == 0) { - s->format = RGW_FORMAT_XML; - } else if (format_str.compare("json") == 0) { - s->format = RGW_FORMAT_JSON; - } else if (format_str.compare("html") == 0) { - s->format = RGW_FORMAT_HTML; - } else { - const char *accept = s->info.env->get("HTTP_ACCEPT"); - if (accept) { - char format_buf[64]; - unsigned int i = 0; - for (; i < sizeof(format_buf) - 1 && accept[i] && accept[i] != ';'; ++i) { - format_buf[i] = accept[i]; - } - format_buf[i] = 0; - if ((strcmp(format_buf, "text/xml") == 0) || (strcmp(format_buf, "application/xml") == 0)) { - s->format = RGW_FORMAT_XML; - } else if (strcmp(format_buf, "application/json") == 0) { - s->format = RGW_FORMAT_JSON; - } else if (strcmp(format_buf, "text/html") == 0) { - s->format = RGW_FORMAT_HTML; - } - } - } - } - - const string& mm = s->info.args.get("multipart-manifest"); - const bool multipart_delete = (mm.compare("delete") == 0); - const bool swift_bulkupload = s->prot_flags & RGW_REST_SWIFT && - s->info.args.exists("extract-archive"); - switch (s->format) { - case RGW_FORMAT_PLAIN: - { - const bool use_kv_syntax = s->info.args.exists("bulk-delete") || - multipart_delete || swift_bulkupload; - s->formatter = new RGWFormatter_Plain(use_kv_syntax); - break; - } - case RGW_FORMAT_XML: - { - const bool lowercase_underscore = s->info.args.exists("bulk-delete") || - multipart_delete || swift_bulkupload; - - s->formatter = new XMLFormatter(false, lowercase_underscore); - break; - } - case RGW_FORMAT_JSON: - s->formatter = new JSONFormatter(false); - break; - case RGW_FORMAT_HTML: - s->formatter = new HTMLFormatter(s->prot_flags & RGW_REST_WEBSITE); - break; - default: - return -EINVAL; - - }; - //s->formatter->reset(); // All formatters should reset on create already - - return 0; -} - -// This function enforces Amazon's spec for bucket names. -// (The requirements, not the recommendations.) -int RGWHandler_REST::validate_bucket_name(const string& bucket) -{ - int len = bucket.size(); - if (len < 3) { - if (len == 0) { - // This request doesn't specify a bucket at all - return 0; - } - // Name too short - return -ERR_INVALID_BUCKET_NAME; - } - else if (len > MAX_BUCKET_NAME_LEN) { - // Name too long - return -ERR_INVALID_BUCKET_NAME; - } - - return 0; -} - -// "The name for a key is a sequence of Unicode characters whose UTF-8 encoding -// is at most 1024 bytes long." -// However, we can still have control characters and other nasties in there. -// Just as long as they're utf-8 nasties. -int RGWHandler_REST::validate_object_name(const string& object) -{ - int len = object.size(); - if (len > MAX_OBJ_NAME_LEN) { - // Name too long - return -ERR_INVALID_OBJECT_NAME; - } - - if (check_utf8(object.c_str(), len)) { - // Object names must be valid UTF-8. - return -ERR_INVALID_OBJECT_NAME; - } - return 0; -} - -static http_op op_from_method(const char *method) -{ - if (!method) - return OP_UNKNOWN; - if (strcmp(method, "GET") == 0) - return OP_GET; - if (strcmp(method, "PUT") == 0) - return OP_PUT; - if (strcmp(method, "DELETE") == 0) - return OP_DELETE; - if (strcmp(method, "HEAD") == 0) - return OP_HEAD; - if (strcmp(method, "POST") == 0) - return OP_POST; - if (strcmp(method, "COPY") == 0) - return OP_COPY; - if (strcmp(method, "OPTIONS") == 0) - return OP_OPTIONS; - - return OP_UNKNOWN; -} - -int RGWHandler_REST::init_permissions(RGWOp* op) -{ - if (op->get_type() == RGW_OP_CREATE_BUCKET) - return 0; - - return do_init_permissions(); -} - -int RGWHandler_REST::read_permissions(RGWOp* op_obj) -{ - bool only_bucket = false; - - switch (s->op) { - case OP_HEAD: - case OP_GET: - only_bucket = false; - break; - case OP_PUT: - case OP_POST: - case OP_COPY: - /* is it a 'multi-object delete' request? */ - if (s->info.args.exists("delete")) { - only_bucket = true; - break; - } - if (is_obj_update_op()) { - only_bucket = false; - break; - } - /* is it a 'create bucket' request? */ - if (op_obj->get_type() == RGW_OP_CREATE_BUCKET) - return 0; - only_bucket = true; - break; - case OP_DELETE: - if (!s->info.args.exists("tagging")){ - only_bucket = true; - } - break; - case OP_OPTIONS: - only_bucket = true; - break; - default: - return -EINVAL; - } - - return do_read_permissions(op_obj, only_bucket); -} - -void RGWRESTMgr::register_resource(string resource, RGWRESTMgr *mgr) -{ - string r = "/"; - r.append(resource); - - /* do we have a resource manager registered for this entry point? */ - map::iterator iter = resource_mgrs.find(r); - if (iter != resource_mgrs.end()) { - delete iter->second; - } - resource_mgrs[r] = mgr; - resources_by_size.insert(pair(r.size(), r)); - - /* now build default resource managers for the path (instead of nested entry points) - * e.g., if the entry point is /auth/v1.0/ then we'd want to create a default - * manager for /auth/ - */ - - size_t pos = r.find('/', 1); - - while (pos != r.size() - 1 && pos != string::npos) { - string s = r.substr(0, pos); - - iter = resource_mgrs.find(s); - if (iter == resource_mgrs.end()) { /* only register it if one does not exist */ - resource_mgrs[s] = new RGWRESTMgr; /* a default do-nothing manager */ - resources_by_size.insert(pair(s.size(), s)); - } - - pos = r.find('/', pos + 1); - } -} - -void RGWRESTMgr::register_default_mgr(RGWRESTMgr *mgr) -{ - delete default_mgr; - default_mgr = mgr; -} - -RGWRESTMgr* RGWRESTMgr::get_resource_mgr(struct req_state* const s, - const std::string& uri, - std::string* const out_uri) -{ - *out_uri = uri; - - multimap::reverse_iterator iter; - - for (iter = resources_by_size.rbegin(); iter != resources_by_size.rend(); ++iter) { - string& resource = iter->second; - if (uri.compare(0, iter->first, resource) == 0 && - (uri.size() == iter->first || - uri[iter->first] == '/')) { - std::string suffix = uri.substr(iter->first); - return resource_mgrs[resource]->get_resource_mgr(s, suffix, out_uri); - } - } - - if (default_mgr) { - return default_mgr->get_resource_mgr_as_default(s, uri, out_uri); - } - - return this; -} - -void RGWREST::register_x_headers(const string& s_headers) -{ - std::vector hdrs = get_str_vec(s_headers); - for (auto& hdr : hdrs) { - boost::algorithm::to_upper(hdr); // XXX - (void) x_headers.insert(hdr); - } -} - -RGWRESTMgr::~RGWRESTMgr() -{ - map::iterator iter; - for (iter = resource_mgrs.begin(); iter != resource_mgrs.end(); ++iter) { - delete iter->second; - } - delete default_mgr; -} - -int64_t parse_content_length(const char *content_length) -{ - int64_t len = -1; - - if (*content_length == '\0') { - len = 0; - } else { - string err; - len = strict_strtoll(content_length, 10, &err); - if (!err.empty()) { - len = -1; - } - } - - return len; -} - -int RGWREST::preprocess(struct req_state *s, rgw::io::BasicClient* cio) -{ - req_info& info = s->info; - - /* save the request uri used to hash on the client side. request_uri may suffer - modifications as part of the bucket encoding in the subdomain calling format. - request_uri_aws4 will be used under aws4 auth */ - s->info.request_uri_aws4 = s->info.request_uri; - - s->cio = cio; - - // We need to know if this RGW instance is running the s3website API with a - // higher priority than regular S3 API, or possibly in place of the regular - // S3 API. - // Map the listing of rgw_enable_apis in REVERSE order, so that items near - // the front of the list have a higher number assigned (and -1 for items not in the list). - list apis; - get_str_list(g_conf->rgw_enable_apis, apis); - int api_priority_s3 = -1; - int api_priority_s3website = -1; - auto api_s3website_priority_rawpos = std::find(apis.begin(), apis.end(), "s3website"); - auto api_s3_priority_rawpos = std::find(apis.begin(), apis.end(), "s3"); - if (api_s3_priority_rawpos != apis.end()) { - api_priority_s3 = apis.size() - std::distance(apis.begin(), api_s3_priority_rawpos); - } - if (api_s3website_priority_rawpos != apis.end()) { - api_priority_s3website = apis.size() - std::distance(apis.begin(), api_s3website_priority_rawpos); - } - ldout(s->cct, 10) << "rgw api priority: s3=" << api_priority_s3 << " s3website=" << api_priority_s3website << dendl; - bool s3website_enabled = api_priority_s3website >= 0; - - if (info.host.size()) { - ssize_t pos = info.host.find(':'); - if (pos >= 0) { - info.host = info.host.substr(0, pos); - } - ldout(s->cct, 10) << "host=" << info.host << dendl; - string domain; - string subdomain; - bool in_hosted_domain_s3website = false; - bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, &subdomain, hostnames_set); - - string s3website_domain; - string s3website_subdomain; - - if (s3website_enabled) { - in_hosted_domain_s3website = rgw_find_host_in_domains(info.host, &s3website_domain, &s3website_subdomain, hostnames_s3website_set); - if (in_hosted_domain_s3website) { - in_hosted_domain = true; // TODO: should hostnames be a strict superset of hostnames_s3website? - domain = s3website_domain; - subdomain = s3website_subdomain; - } - } - - ldout(s->cct, 20) - << "subdomain=" << subdomain - << " domain=" << domain - << " in_hosted_domain=" << in_hosted_domain - << " in_hosted_domain_s3website=" << in_hosted_domain_s3website - << dendl; - - if (g_conf->rgw_resolve_cname - && !in_hosted_domain - && !in_hosted_domain_s3website) { - string cname; - bool found; - int r = rgw_resolver->resolve_cname(info.host, cname, &found); - if (r < 0) { - ldout(s->cct, 0) - << "WARNING: rgw_resolver->resolve_cname() returned r=" << r - << dendl; - } - - if (found) { - ldout(s->cct, 5) << "resolved host cname " << info.host << " -> " - << cname << dendl; - in_hosted_domain = - rgw_find_host_in_domains(cname, &domain, &subdomain, hostnames_set); - - if (s3website_enabled - && !in_hosted_domain_s3website) { - in_hosted_domain_s3website = - rgw_find_host_in_domains(cname, &s3website_domain, - &s3website_subdomain, - hostnames_s3website_set); - if (in_hosted_domain_s3website) { - in_hosted_domain = true; // TODO: should hostnames be a - // strict superset of hostnames_s3website? - domain = s3website_domain; - subdomain = s3website_subdomain; - } - } - - ldout(s->cct, 20) - << "subdomain=" << subdomain - << " domain=" << domain - << " in_hosted_domain=" << in_hosted_domain - << " in_hosted_domain_s3website=" << in_hosted_domain_s3website - << dendl; - } - } - - // Handle A/CNAME records that point to the RGW storage, but do match the - // CNAME test above, per issue http://tracker.ceph.com/issues/15975 - // If BOTH domain & subdomain variables are empty, then none of the above - // cases matched anything, and we should fall back to using the Host header - // directly as the bucket name. - // As additional checks: - // - if the Host header is an IP, we're using path-style access without DNS - // - Also check that the Host header is a valid bucket name before using it. - // - Don't enable virtual hosting if no hostnames are configured - if (subdomain.empty() - && (domain.empty() || domain != info.host) - && !looks_like_ip_address(info.host.c_str()) - && RGWHandler_REST::validate_bucket_name(info.host) == 0 - && !(hostnames_set.empty() && hostnames_s3website_set.empty())) { - subdomain.append(info.host); - in_hosted_domain = 1; - } - - if (s3website_enabled && api_priority_s3website > api_priority_s3) { - in_hosted_domain_s3website = 1; - } - - if (in_hosted_domain_s3website) { - s->prot_flags |= RGW_REST_WEBSITE; - } - - - if (in_hosted_domain && !subdomain.empty()) { - string encoded_bucket = "/"; - encoded_bucket.append(subdomain); - if (s->info.request_uri[0] != '/') - encoded_bucket.append("/"); - encoded_bucket.append(s->info.request_uri); - s->info.request_uri = encoded_bucket; - } - - if (!domain.empty()) { - s->info.domain = domain; - } - - ldout(s->cct, 20) - << "final domain/bucket" - << " subdomain=" << subdomain - << " domain=" << domain - << " in_hosted_domain=" << in_hosted_domain - << " in_hosted_domain_s3website=" << in_hosted_domain_s3website - << " s->info.domain=" << s->info.domain - << " s->info.request_uri=" << s->info.request_uri - << dendl; - } - - if (s->info.domain.empty()) { - s->info.domain = s->cct->_conf->rgw_dns_name; - } - - s->decoded_uri = url_decode(s->info.request_uri); - /* Validate for being free of the '\0' buried in the middle of the string. */ - if (std::strlen(s->decoded_uri.c_str()) != s->decoded_uri.length()) { - return -ERR_ZERO_IN_URL; - } - - /* FastCGI specification, section 6.3 - * http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S6.3 - * === - * The Authorizer application receives HTTP request information from the Web - * server on the FCGI_PARAMS stream, in the same format as a Responder. The - * Web server does not send CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, and - * SCRIPT_NAME headers. - * === - * Ergo if we are in Authorizer role, we MUST look at HTTP_CONTENT_LENGTH - * instead of CONTENT_LENGTH for the Content-Length. - * - * There is one slight wrinkle in this, and that's older versions of - * nginx/lighttpd/apache setting BOTH headers. As a result, we have to check - * both headers and can't always simply pick A or B. - */ - const char* content_length = info.env->get("CONTENT_LENGTH"); - const char* http_content_length = info.env->get("HTTP_CONTENT_LENGTH"); - if (!http_content_length != !content_length) { - /* Easy case: one or the other is missing */ - s->length = (content_length ? content_length : http_content_length); - } else if (s->cct->_conf->rgw_content_length_compat && - content_length && http_content_length) { - /* Hard case: Both are set, we have to disambiguate */ - int64_t content_length_i, http_content_length_i; - - content_length_i = parse_content_length(content_length); - http_content_length_i = parse_content_length(http_content_length); - - // Now check them: - if (http_content_length_i < 0) { - // HTTP_CONTENT_LENGTH is invalid, ignore it - } else if (content_length_i < 0) { - // CONTENT_LENGTH is invalid, and HTTP_CONTENT_LENGTH is valid - // Swap entries - content_length = http_content_length; - } else { - // both CONTENT_LENGTH and HTTP_CONTENT_LENGTH are valid - // Let's pick the larger size - if (content_length_i < http_content_length_i) { - // prefer the larger value - content_length = http_content_length; - } - } - s->length = content_length; - // End of: else if (s->cct->_conf->rgw_content_length_compat && - // content_length && - // http_content_length) - } else { - /* no content length was defined */ - s->length = NULL; - } - - if (s->length) { - if (*s->length == '\0') { - s->content_length = 0; - } else { - string err; - s->content_length = strict_strtoll(s->length, 10, &err); - if (!err.empty()) { - ldout(s->cct, 10) << "bad content length, aborting" << dendl; - return -EINVAL; - } - } - } - - if (s->content_length < 0) { - ldout(s->cct, 10) << "negative content length, aborting" << dendl; - return -EINVAL; - } - - map::iterator giter; - for (giter = generic_attrs_map.begin(); giter != generic_attrs_map.end(); - ++giter) { - const char *env = info.env->get(giter->first.c_str()); - if (env) { - s->generic_attrs[giter->second] = env; - } - } - - if (g_conf->rgw_print_continue) { - const char *expect = info.env->get("HTTP_EXPECT"); - s->expect_cont = (expect && !strcasecmp(expect, "100-continue")); - } - s->op = op_from_method(info.method); - - info.init_meta_info(&s->has_bad_meta); - - return 0; -} - -RGWHandler_REST* RGWREST::get_handler( - RGWRados * const store, - struct req_state* const s, - const rgw::auth::StrategyRegistry& auth_registry, - const std::string& frontend_prefix, - RGWRestfulIO* const rio, - RGWRESTMgr** const pmgr, - int* const init_error -) { - *init_error = preprocess(s, rio); - if (*init_error < 0) { - return nullptr; - } - - RGWRESTMgr *m = mgr.get_manager(s, frontend_prefix, s->decoded_uri, - &s->relative_uri); - if (! m) { - *init_error = -ERR_METHOD_NOT_ALLOWED; - return nullptr; - } - - if (pmgr) { - *pmgr = m; - } - - RGWHandler_REST* handler = m->get_handler(s, auth_registry, frontend_prefix); - if (! handler) { - *init_error = -ERR_METHOD_NOT_ALLOWED; - return NULL; - } - *init_error = handler->init(store, s, rio); - if (*init_error < 0) { - m->put_handler(handler); - return nullptr; - } - - return handler; -} /* get stream handler */