// -*- 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 */