]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: Fix CORS allow-headers validation
authorRobin H. Johnson <robbat2@gentoo.org>
Sun, 15 Dec 2013 20:26:19 +0000 (12:26 -0800)
committerYehuda Sadeh <yehuda@inktank.com>
Tue, 28 Jan 2014 20:53:01 +0000 (12:53 -0800)
This fix is needed because Ceph presently validates CORS headers in a
case-sensitive manner. Keeps a local cache of lowercased allowed headers
to avoid converting the allowed headers to lowercase each time.

CORS 6.2.6: If any of the header field-names is not a ASCII
case-insensitive match for any of the values in list of headers do not
set any additional headers and terminate this set of steps.

Signed-off-by: Robin H. Johnson <robbat2@gentoo.org>
Reviewed-by: Yehuda Sadeh <yehuda@inktank.com>
(cherry picked from commit 31b60bfd9347a386ff12b4e4f1812d664bcfff01)

src/rgw/rgw_cors.cc
src/rgw/rgw_cors.h
src/rgw/rgw_op.cc

index 4be83605b502e6ae46b4b92e39c9a1aab8f40a00..a120a6866455e505dbe312406877d5840c18f2d8 100644 (file)
@@ -51,6 +51,33 @@ void RGWCORSRule::erase_origin_if_present(string& origin, bool *rule_empty) {
   }
 }
 
+/*
+ * make attrs look-like-this
+ * does not convert underscores or dashes
+ *
+ * Per CORS specification, section 3:
+ * ===
+ * "Converting a string to ASCII lowercase" means replacing all characters in the
+ * range U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z with
+ * the corresponding characters in the range U+0061 LATIN SMALL LETTER A to
+ * U+007A LATIN SMALL LETTER Z).
+ * ===
+ *
+ * @todo When UTF-8 is allowed in HTTP headers, this function will need to change
+ */
+string lowercase_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) {
+       buf[i] = tolower(*s);
+  }
+  return string(buf);
+}
+
+
 static bool is_string_in_set(set<string>& s, string h) {
   if ((s.find("*") != s.end()) || 
           (s.find(h) != s.end())) {
@@ -96,7 +123,13 @@ bool RGWCORSRule::is_origin_present(const char *o) {
 
 bool RGWCORSRule::is_header_allowed(const char *h, size_t len) {
   string hdr(h, len);
-  return is_string_in_set(allowed_hdrs, hdr);
+  if(lowercase_allowed_hdrs.empty()) {
+    set<string>::iterator iter;
+    for (iter = allowed_hdrs.begin(); iter != allowed_hdrs.end(); ++iter) {
+      lowercase_allowed_hdrs.insert(lowercase_http_attr(*iter));
+    }
+  }
+  return is_string_in_set(lowercase_allowed_hdrs, lowercase_http_attr(hdr));
 }
 
 void RGWCORSRule::format_exp_headers(string& s) {
index 1e0ec3bc7ece9d3fe95307ade37ae1d98041e7d6..124ebf92a7f23dfafcfda4fa076784068dbdda43 100644 (file)
@@ -41,7 +41,8 @@ protected:
   uint32_t       max_age;
   uint8_t        allowed_methods;
   std::string         id;
-  std::set<string> allowed_hdrs;
+  std::set<string> allowed_hdrs; /* If you change this, you need to discard lowercase_allowed_hdrs */
+  std::set<string> lowercase_allowed_hdrs; /* Not built until needed in RGWCORSRule::is_header_allowed */
   std::set<string> allowed_origins;
   std::list<string> exposable_hdrs;
 
index 72ff5214f435ca0772a8eba6f9aa8e12cd19eee3..25af50fe2386db62e8b7868e4b0ccb8fff73a700 100644 (file)
@@ -470,6 +470,11 @@ int RGWOp::read_bucket_cors()
   return 0;
 }
 
+/** CORS 6.2.6.
+ * If any of the header field-names is not a ASCII case-insensitive match for
+ * any of the values in list of headers do not set any additional headers and
+ * terminate this set of steps.
+ * */
 static void get_cors_response_headers(RGWCORSRule *rule, const char *req_hdrs, string& hdrs, string& exp_hdrs, unsigned *max_age) {
   if (req_hdrs) {
     list<string> hl;
@@ -487,12 +492,20 @@ static void get_cors_response_headers(RGWCORSRule *rule, const char *req_hdrs, s
   *max_age = rule->get_max_age();
 }
 
+/**
+ * Generate the CORS header response
+ *
+ * This is described in the CORS standard, section 6.2.
+ */
 bool RGWOp::generate_cors_headers(string& origin, string& method, string& headers, string& exp_headers, unsigned *max_age)
 {
+  /* CORS 6.2.1. */
   const char *orig = s->info.env->get("HTTP_ORIGIN");
   if (!orig) {
     return false;
   }
+
+  /* Custom: */
   origin = orig;
   int ret = read_bucket_cors();
   if (ret < 0) {
@@ -504,10 +517,12 @@ bool RGWOp::generate_cors_headers(string& origin, string& method, string& header
     return false;
   }
 
+  /* CORS 6.2.2. */
   RGWCORSRule *rule = bucket_cors.host_name_rule(orig);
   if (!rule)
     return false;
 
+  /* CORS 6.2.3. */
   const char *req_meth = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_METHOD");
   if (!req_meth) {
     req_meth = s->info.method;
@@ -515,13 +530,15 @@ bool RGWOp::generate_cors_headers(string& origin, string& method, string& header
 
   if (req_meth)
     method = req_meth;
-
+  /* CORS 6.2.5. */
   if (!validate_cors_rule_method(rule, req_meth)) {
     return false;
   }
 
+  /* CORS 6.2.4. */
   const char *req_hdrs = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_HEADERS");
 
+  /* CORS 6.2.6. */
   get_cors_response_headers(rule, req_hdrs, headers, exp_headers, max_age);
 
   return true;