]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: with CORS support
authorBabu Shanmugam <anbu@enovance.com>
Tue, 5 Mar 2013 03:52:55 +0000 (09:22 +0530)
committerSage Weil <sage@inktank.com>
Mon, 1 Apr 2013 04:51:48 +0000 (21:51 -0700)
With CORS test cases

1. Added license headers to the cors files
2. SIWFT POST metadata for cors will replace the old cors configuration
3. Fixed a buf in rgw_cors_swift.h

With Yehuda's review comments along with some fixes;
1. If the origin is allowed only for https, we should not approve the same host for http requests
2. Accounted for hostname situtation like www.www.org, or www.wowwww.com or www.*
3. Replaced atoi with strtol
4. Have a centralized place for parsing host names, hence avoiding duplicates

Checked certain senarios with amazon S3 and made changes accordingly

With some fixes in rgw_cors.cc and str_list.cc

Removing the whitespace auto-append to the delimiters in get_str_list(), added white spaces delimiters in is_string_in_set()

21 files changed:
src/Makefile.am
src/common/str_list.cc
src/rgw/rgw_common.cc
src/rgw/rgw_common.h
src/rgw/rgw_cors.cc [new file with mode: 0644]
src/rgw/rgw_cors.h [new file with mode: 0644]
src/rgw/rgw_cors_s3.cc [new file with mode: 0644]
src/rgw/rgw_cors_s3.h [new file with mode: 0644]
src/rgw/rgw_cors_swift.h [new file with mode: 0644]
src/rgw/rgw_html_errors.h
src/rgw/rgw_main.cc
src/rgw/rgw_op.cc
src/rgw/rgw_op.h
src/rgw/rgw_rest.cc
src/rgw/rgw_rest.h
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_rest_swift.cc
src/rgw/rgw_rest_swift.h
src/rgw/rgw_xml.cc
src/test/test_cors.cc [new file with mode: 0644]

index b27c8bb6b7029a4d6ea066d1886bf388470e5462..6d198a1b3a98b2307120eb65abc43bc08ce39447 100644 (file)
@@ -370,7 +370,9 @@ librgw_a_SOURCES =  \
        rgw/rgw_policy_s3.cc \
        rgw/rgw_gc.cc \
        rgw/rgw_multi_del.cc \
-       rgw/rgw_env.cc
+       rgw/rgw_env.cc \
+  rgw/rgw_cors.cc \
+  rgw/rgw_cors_s3.cc
 librgw_a_CFLAGS = ${CRYPTO_CFLAGS} ${AM_CFLAGS}
 librgw_a_CXXFLAGS = -Woverloaded-virtual ${AM_CXXFLAGS}
 noinst_LIBRARIES += librgw.a
@@ -874,6 +876,12 @@ unittest_texttable_LDADD = librados.la ${UNITTEST_LDADD}
 unittest_texttable_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
 check_PROGRAMS += unittest_texttable
 
+unittest_cors_SOURCES = test/test_cors.cc
+unittest_cors_LDFLAGS = libglobal.la 
+unittest_cors_LDADD = librados.la librgw.a ${UNITTEST_LDADD} ${UNITTEST_STATIC_LDADD} -lcryptopp -lcurl -luuid -lexpat 
+unittest_cors_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
+check_PROGRAMS += unittest_cors
+
 ceph_test_librbd_SOURCES = test/librbd/test_librbd.cc test/librados/test.cc
 ceph_test_librbd_LDADD =  librbd.la librados.la ${UNITTEST_STATIC_LDADD}
 ceph_test_librbd_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS}
index cae2b22015d4d27ed824c99e5088e3d088d1f5cb..a9867a6efdd5d2d2aa614bceacc43789f9523b67 100644 (file)
@@ -20,24 +20,19 @@ using std::string;
 
 static bool get_next_token(const std::string &s, size_t& pos, const char *delims, string& token)
 {
-  int start = s.find_first_not_of(" \t", pos);
+  int start = s.find_first_not_of(delims, pos);
   int end;
 
-  if (start < 0)
+  if (start < 0){
+    pos = s.size();
     return false;
-
-  if (s[start] == ',') {
-    end = start + 1;
-    pos = end;
-  } else {
-    end = s.find_first_of(delims, start+1);
-    if (end >= 0)
-      pos = end + 1;
   }
 
-  if (end < 0) {
-    end = s.size();
-    pos = end;
+  end = s.find_first_of(delims, start);
+  if (end >= 0)
+    pos = end + 1;
+  else {
+    pos = end = s.size();
   }
 
   token = s.substr(start, end - start);
@@ -54,7 +49,7 @@ void get_str_list(const std::string& str, const char *delims, std::list<string>&
 {
   size_t pos = 0;
   string token;
-
+  
   str_list.clear();
 
   while (pos < str.size()) {
index 65ff2fe99edb54cb0a24d6a000681b111858fe42..ad9cb1ad91702aa4d212e19a8a84a3f7f1f16bf6 100644 (file)
@@ -93,7 +93,8 @@ is_err() const
 
 
 req_state::req_state(CephContext *_cct, struct RGWEnv *e) : cct(_cct), cio(NULL), op(OP_UNKNOWN), 
-                                                           has_acl_header(false), os_auth_token(NULL),
+                                                            has_acl_header(false), os_auth_token(NULL),
+                                                            bucket_cors(NULL),
                                                             env(e)
 {
   enable_ops_log = env->conf->enable_ops_log;
@@ -131,6 +132,7 @@ req_state::req_state(CephContext *_cct, struct RGWEnv *e) : cct(_cct), cio(NULL)
 req_state::~req_state() {
   delete formatter;
   delete bucket_acl;
+  delete bucket_cors;
   delete object_acl;
   free((void *)object);
   free((void *)bucket_name);
@@ -456,7 +458,8 @@ int XMLArgs::parse()
           (name.compare("partNumber") == 0) ||
           (name.compare("uploadId") == 0) ||
           (name.compare("versionId") == 0) ||
-          (name.compare("torrent") == 0)) {
+          (name.compare("torrent") == 0) ||
+          (name.compare("cors") == 0)) {
         sub_resources[name] = val;
       } else if (name[0] == 'r') { // root of all evil
         if ((name.compare("response-content-type") == 0) ||
index bb203ef72bb233dd999811a55715d72cc85243be..2abed2f9f9166a8f9ef70a442565d30203dc6858 100644 (file)
@@ -28,6 +28,7 @@
 #include "include/types.h"
 #include "include/utime.h"
 #include "rgw_acl.h"
+#include "rgw_cors.h"
 
 using namespace std;
 
@@ -43,6 +44,7 @@ using ceph::crypto::MD5;
 #define RGW_AMZ_META_PREFIX "x-amz-meta-"
 
 #define RGW_ATTR_ACL           RGW_ATTR_PREFIX "acl"
+#define RGW_ATTR_CORS          RGW_ATTR_PREFIX "cors"
 #define RGW_ATTR_ETAG          RGW_ATTR_PREFIX "etag"
 #define RGW_ATTR_BUCKETS       RGW_ATTR_PREFIX "buckets"
 #define RGW_ATTR_META_PREFIX   RGW_ATTR_PREFIX RGW_AMZ_META_PREFIX
@@ -108,6 +110,7 @@ using ceph::crypto::MD5;
 #define ERR_TOO_MANY_BUCKETS     2020
 #define ERR_INVALID_REQUEST      2021
 #define ERR_TOO_SMALL            2022
+#define ERR_NOT_FOUND            2023
 #define ERR_USER_SUSPENDED       2100
 #define ERR_INTERNAL_ERROR       2200
 
@@ -269,6 +272,7 @@ enum http_op {
   OP_HEAD,
   OP_POST,
   OP_COPY,
+  OP_OPTIONS,
   OP_UNKNOWN,
 };
 
@@ -623,6 +627,7 @@ struct req_state {
    RGWUserInfo user; 
    RGWAccessControlPolicy *bucket_acl;
    RGWAccessControlPolicy *object_acl;
+   RGWCORSConfiguration   *bucket_cors;
 
    string canned_acl;
    bool has_acl_header;
diff --git a/src/rgw/rgw_cors.cc b/src/rgw/rgw_cors.cc
new file mode 100644 (file)
index 0000000..85aa48f
--- /dev/null
@@ -0,0 +1,144 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+#include <string.h>
+
+#include <iostream>
+#include <map>
+
+#include "include/types.h"
+#include "common/debug.h"
+#include "include/str_list.h"
+#include "common/Formatter.h"
+
+#include "rgw_cors.h"
+
+#define dout_subsys ceph_subsys_rgw
+using namespace std;
+
+void RGWCORSRule::dump_origins(){
+  unsigned num_origins = allowed_origins.size();
+  dout(10) << "Allowed origins : " << num_origins << dendl;
+  for(set<string>::iterator it = allowed_origins.begin();
+      it != allowed_origins.end(); 
+      it++){
+    dout(10) << *it << "," << dendl;
+  }
+}
+
+void RGWCORSRule::erase_origin_if_present(string& origin, bool *rule_empty){
+  set<string>::iterator it = allowed_origins.find(origin);
+  if(!rule_empty)
+    return;
+  *rule_empty = false;
+  if(it != allowed_origins.end()){
+    dout(10) << "Found origin " << origin << ", set size:" << 
+        allowed_origins.size() << dendl;
+    allowed_origins.erase(it);
+    *rule_empty = (allowed_origins.size() == 0);
+  }
+}
+
+static bool is_string_in_set(set<string>& s, string h){
+  if((s.find("*") != s.end()) || 
+          (s.find(h) != s.end())){
+    return true;
+  }
+  /* The header can be Content-*-type, or Content-* */
+  for(set<string>::iterator it = s.begin();
+      it != s.end(); it++){
+    unsigned off;
+    if((off = (*it).find("*"))!=string::npos){
+      list<string> ssplit;
+      unsigned flen = 0;
+      
+      get_str_list((*it), "* \t", ssplit);
+      if(off != 0){
+        string sl = ssplit.front();
+        flen = sl.length();
+        dout(10) << "Finding " << sl << ", in " << h << ", at offset 0" << dendl;
+        if(h.find(sl) != 0)
+          continue;
+        ssplit.pop_front();
+      }
+      if(off != ((*it).length() - 1)){
+        string sl = ssplit.front();
+        dout(10) << "Finding " << sl << ", in " << h 
+          << ", at offset not less than " << flen << dendl;
+        if(h.compare((h.size() - sl.size()), sl.size(), sl) != 0)
+          continue;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+bool RGWCORSRule::is_origin_present(const char *o){
+  string origin = o;
+  return is_string_in_set(allowed_origins, origin);
+}
+
+bool RGWCORSRule::is_header_allowed(const char *h, size_t len){
+  string hdr(h, len);
+  return is_string_in_set(allowed_hdrs, hdr);
+}
+
+void RGWCORSRule::format_exp_headers(string& s){
+  s = "";
+  for(list<string>::iterator it = exposable_hdrs.begin();
+      it != exposable_hdrs.end(); it++){
+      if(s.length() > 0)
+        s.append(",");
+      s.append((*it));
+  }
+}
+
+RGWCORSRule * RGWCORSConfiguration::host_name_rule(const char *origin){
+  for(list<RGWCORSRule>::iterator it_r = rules.begin(); 
+      it_r != rules.end(); it_r++){
+    RGWCORSRule& r = (*it_r);
+    if(r.is_origin_present(origin))
+      return &r;
+  }
+  return NULL;
+}
+
+void RGWCORSConfiguration::erase_host_name_rule(string& origin){
+  bool rule_empty;
+  unsigned loop = 0;
+  /*Erase the host name from that rule*/
+  dout(10) << "Num of rules : " << rules.size() << dendl;
+  for(list<RGWCORSRule>::iterator it_r = rules.begin(); 
+      it_r != rules.end(); it_r++, loop++){
+    RGWCORSRule& r = (*it_r);
+    r.erase_origin_if_present(origin, &rule_empty);
+    dout(10) << "Origin:" << origin << ", rule num:" 
+      << loop << ", emptying now:" << rule_empty << dendl;
+    if(rule_empty){
+      rules.erase(it_r);
+      break;
+    }
+  }
+}
+
+void RGWCORSConfiguration::dump(){
+  unsigned loop = 1;
+  unsigned num_rules = rules.size();
+  dout(10) << "Number of rules: " << num_rules << dendl;
+  for(list<RGWCORSRule>::iterator it = rules.begin();
+      it!= rules.end(); it++, loop++){
+    dout(10) << " <<<<<<< Rule " << loop << " >>>>>>> " << dendl;
+    (*it).dump_origins();
+  }
+}
diff --git a/src/rgw/rgw_cors.h b/src/rgw/rgw_cors.h
new file mode 100644 (file)
index 0000000..372229a
--- /dev/null
@@ -0,0 +1,132 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_RGW_CORS_H
+#define CEPH_RGW_CORS_H
+
+#include <map>
+#include <string>
+#include <iostream>
+#include <include/types.h>
+
+#define RGW_CORS_GET    0x1
+#define RGW_CORS_PUT    0x2
+#define RGW_CORS_HEAD   0x4
+#define RGW_CORS_POST   0x8
+#define RGW_CORS_DELETE 0x10
+#define RGW_CORS_ALL    (RGW_CORS_GET   |  \
+                         RGW_CORS_PUT   |  \
+                         RGW_CORS_HEAD  |  \
+                         RGW_CORS_POST  |  \
+                         RGW_CORS_DELETE)
+
+#define CORS_MAX_AGE_INVALID ((uint32_t)-1)
+
+class RGWCORSRule
+{
+protected:
+  uint32_t       max_age;
+  uint8_t        allowed_methods;
+  std::string         id;
+  std::set<string> allowed_hdrs;
+  std::set<string> allowed_origins;
+  std::list<string> exposable_hdrs;
+
+public:
+  RGWCORSRule() : max_age(CORS_MAX_AGE_INVALID),allowed_methods(0) {}
+  RGWCORSRule(std::set<string>& o, std::set<string>& h, 
+              std::list<string>& e, uint8_t f, unsigned a)
+      :max_age(a),
+       allowed_methods(f),
+       allowed_hdrs(h),
+       allowed_origins(o),
+       exposable_hdrs(e){}
+  virtual ~RGWCORSRule() {}
+
+  std::string& get_id() { return id; }
+  uint32_t get_max_age() { return max_age; }
+  uint8_t get_allowed_methods() { return allowed_methods; }
+
+  void encode(bufferlist& bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(max_age, bl);
+    ::encode(allowed_methods, bl);
+    ::encode(id, bl);
+    ::encode(allowed_hdrs, bl);
+    ::encode(allowed_origins, bl);
+    ::encode(exposable_hdrs, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::iterator& bl) {
+    DECODE_START(1, bl);
+    ::decode(max_age, bl);
+    ::decode(allowed_methods, bl);
+    ::decode(id, bl);
+    ::decode(allowed_hdrs, bl);
+    ::decode(allowed_origins, bl);
+    ::decode(exposable_hdrs, bl);
+    DECODE_FINISH(bl);
+  }
+  bool is_origin_present(const char *o);
+  void format_exp_headers(std::string& s);
+  void erase_origin_if_present(std::string& origin, bool *rule_empty);
+  void dump_origins(); 
+  void dump(Formatter *f) const;
+  bool is_header_allowed(const char *hdr, size_t len);
+};
+WRITE_CLASS_ENCODER(RGWCORSRule)
+
+class RGWCORSConfiguration
+{
+  protected:
+    std::list<RGWCORSRule> rules;
+  public:
+    RGWCORSConfiguration(){}
+    ~RGWCORSConfiguration(){}
+
+  void encode(bufferlist& bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(rules, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::iterator& bl) {
+    DECODE_START(1, bl);
+    ::decode(rules, bl);
+    DECODE_FINISH(bl);
+  }
+  void dump(Formatter *f) const;
+  std::list<RGWCORSRule>& get_rules(){
+    return rules;
+  }
+  bool is_empty(){
+    return rules.empty();
+  }
+  void get_origins_list(const char *origin, std::list<string>& origins);
+  RGWCORSRule * host_name_rule(const char *origin);
+  void erase_host_name_rule(std::string& origin);
+  void dump();
+  void stack_rule(RGWCORSRule& r){
+    rules.push_front(r);    
+  }
+};
+WRITE_CLASS_ENCODER(RGWCORSConfiguration)
+
+static inline int validate_name_string(string& o){
+  if(o.length() == 0)
+    return -1;
+  if(o.find_first_of("*") != o.find_last_of("*"))
+    return -1;
+  return 0;
+}
+#endif /*CEPH_RGW_CORS_H*/
diff --git a/src/rgw/rgw_cors_s3.cc b/src/rgw/rgw_cors_s3.cc
new file mode 100644 (file)
index 0000000..d3d6134
--- /dev/null
@@ -0,0 +1,231 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+#include <string.h>
+
+#include <iostream>
+#include <map>
+
+#include "include/types.h"
+
+#include "rgw_cors_s3.h"
+#include "rgw_user.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+using namespace std;
+
+void RGWCORSRule_S3::to_xml(XMLFormatter& f){
+
+  f.open_object_section("CORSRule");
+  /*ID if present*/
+  if(id.length() > 0){
+    f.dump_string("ID", id);;
+  }
+  /*AllowedMethods*/
+  string m;
+  if(allowed_methods & RGW_CORS_GET)
+    f.dump_string("AllowedMethod", "GET");
+  if(allowed_methods & RGW_CORS_PUT)
+    f.dump_string("AllowedMethod", "PUT");
+  if(allowed_methods & RGW_CORS_DELETE)
+    f.dump_string("AllowedMethod", "DELETE");
+  if(allowed_methods & RGW_CORS_HEAD)
+    f.dump_string("AllowedMethod", "HEAD");
+  if(allowed_methods & RGW_CORS_POST)
+    f.dump_string("AllowedMethod", "POST");
+  /*AllowedOrigins*/
+  for(set<string>::iterator it = allowed_origins.begin(); 
+      it != allowed_origins.end(); 
+      it++){
+    string host = *it;
+    f.dump_string("AllowedOrigin", host);
+  }
+  /*AllowedHeader*/
+  for(set<string>::iterator it = allowed_hdrs.begin(); 
+      it != allowed_hdrs.end(); it++){
+    f.dump_string("AllowedHeader", *it);
+  }
+  /*MaxAgeSeconds*/
+  if(max_age != CORS_MAX_AGE_INVALID){
+    f.dump_unsigned("MaxAgeSeconds", max_age);
+  }
+  /*ExposeHeader*/
+  for(list<string>::iterator it = exposable_hdrs.begin(); 
+      it != exposable_hdrs.end(); it++){
+    f.dump_string("ExposeHeader", *it);
+  }
+  f.close_section();
+}
+
+bool RGWCORSRule_S3::xml_end(const char *el){
+  XMLObjIter iter = find("AllowedMethod");
+  XMLObj *obj;
+  /*Check all the allowedmethods*/
+  obj = iter.get_next();
+  if(obj){
+    for( ; obj; obj = iter.get_next()) {
+      const char *s = obj->get_data().c_str();
+      dout(10) << "RGWCORSRule::xml_end, el : " << el << ", data : " << s << dendl;
+      if(strcasecmp(s, "GET") == 0){
+        allowed_methods |= RGW_CORS_GET;
+      }else if(strcasecmp(s, "POST") == 0){
+        allowed_methods |= RGW_CORS_POST;
+      }else if(strcasecmp(s, "DELETE") == 0){
+        allowed_methods |= RGW_CORS_DELETE;
+      }else if(strcasecmp(s, "HEAD") == 0){
+        allowed_methods |= RGW_CORS_HEAD;
+      }else if(strcasecmp(s, "PUT") == 0){
+        allowed_methods |= RGW_CORS_PUT;
+      }else{
+        return false;
+      }
+    }
+  } 
+  /*Check the id's len, it should be less than 255*/
+  XMLObj *xml_id = find_first("ID");
+  if(xml_id != NULL){
+    string data = xml_id->get_data();
+    if(data.length() > 255){
+      dout(0) << "RGWCORSRule has id of length greater than 255" << dendl;
+      return false;
+    }
+    dout(10) << "RGWCORRule id : " << data << dendl;  
+    id = data;
+  }
+  /*Check if there is atleast one AllowedOrigin*/
+  iter = find("AllowedOrigin");
+  if(!(obj = iter.get_next())){
+    dout(0) << "RGWCORSRule does not have even one AllowedOrigin" << dendl;
+    return false;
+  }
+  for( ; obj; obj = iter.get_next()){
+    dout(10) << "RGWCORSRule - origin : " << obj->get_data() << dendl;
+    /*Just take the hostname*/
+    string host = obj->get_data();
+    if(validate_name_string(host) != 0)
+      return false;
+    allowed_origins.insert(allowed_origins.end(), host);
+  }
+  /*Check of max_age*/
+  iter = find("MaxAgeSeconds");
+  if((obj = iter.get_next())){
+    char *end = NULL;
+    max_age = strtol(obj->get_data().c_str(), &end, 10);
+    if (max_age == LONG_MAX)
+      max_age = CORS_MAX_AGE_INVALID;
+    dout(10) << "RGWCORSRule : max_age : " << max_age << dendl;
+  }
+  /*Check and update ExposeHeader*/
+  iter = find("ExposeHeader");
+  if((obj = iter.get_next())){
+    for(; obj; obj = iter.get_next()){
+      dout(10) << "RGWCORSRule - exp_hdr : " << obj->get_data() << dendl;
+      exposable_hdrs.push_back(obj->get_data());
+    }
+  }
+  /*Check and update AllowedHeader*/
+  iter = find("AllowedHeader");
+  if((obj = iter.get_next())){
+    for(; obj; obj = iter.get_next()){
+      dout(10) << "RGWCORSRule - allowed_hdr : " << obj->get_data() << dendl;
+      string s = obj->get_data();
+      if(validate_name_string(s) != 0)
+         return false;
+      allowed_hdrs.insert(allowed_hdrs.end(), s);
+    }
+  }
+  return true;
+}
+
+void RGWCORSConfiguration_S3::to_xml(ostream& out){
+  XMLFormatter f;
+  f.open_object_section("CORSConfiguration");
+  for(list<RGWCORSRule>::iterator it = rules.begin();
+      it != rules.end(); it++){
+    (static_cast<RGWCORSRule_S3 &>(*it)).to_xml(f);
+  }
+  f.close_section();
+  f.flush(out);
+}
+
+bool RGWCORSConfiguration_S3::xml_end(const char *el){
+  XMLObjIter iter = find("CORSRule");
+  RGWCORSRule_S3 *obj;
+  if(!(obj = (RGWCORSRule_S3 *)iter.get_next())){
+    dout(0) << "CORSConfiguration should have atleast one CORSRule" << dendl;
+    return false;
+  }
+  for(; obj; obj = (RGWCORSRule_S3 *)iter.get_next()){
+    rules.push_back(*obj);
+  }
+  return true;
+}
+
+class CORSRuleID_S3 : public XMLObj {
+  public:
+    CORSRuleID_S3(){}
+    ~CORSRuleID_S3(){}
+};
+
+class CORSRuleAllowedOrigin_S3 : public XMLObj {
+  public:
+    CORSRuleAllowedOrigin_S3(){}
+    ~CORSRuleAllowedOrigin_S3(){}
+};
+
+class CORSRuleAllowedMethod_S3 : public XMLObj {
+  public:
+    CORSRuleAllowedMethod_S3(){}
+    ~CORSRuleAllowedMethod_S3(){}
+};
+
+class CORSRuleAllowedHeader_S3 : public XMLObj {
+  public:
+    CORSRuleAllowedHeader_S3(){}
+    ~CORSRuleAllowedHeader_S3(){}
+};
+
+class CORSRuleMaxAgeSeconds_S3 : public XMLObj {
+  public:
+    CORSRuleMaxAgeSeconds_S3(){}
+    ~CORSRuleMaxAgeSeconds_S3(){}
+};
+
+class CORSRuleExposeHeader_S3 : public XMLObj {
+  public:
+    CORSRuleExposeHeader_S3(){}
+    ~CORSRuleExposeHeader_S3(){}
+};
+
+XMLObj *RGWCORSXMLParser_S3::alloc_obj(const char *el){
+  if(strcmp(el, "CORSConfiguration") == 0){
+    return new RGWCORSConfiguration_S3;
+  } else if(strcmp(el, "CORSRule") == 0){
+    return new RGWCORSRule_S3;
+  } else if(strcmp(el, "ID") == 0){
+    return new CORSRuleID_S3;
+  } else if(strcmp(el, "AllowedOrigin") == 0){
+    return new CORSRuleAllowedOrigin_S3;
+  } else if(strcmp(el, "AllowedMethod") == 0){
+    return new CORSRuleAllowedMethod_S3;
+  } else if(strcmp(el, "AllowedHeader") == 0){
+    return new CORSRuleAllowedHeader_S3;
+  } else if(strcmp(el, "MaxAgeSeconds") == 0){
+    return new CORSRuleMaxAgeSeconds_S3;
+  } else if(strcmp(el, "ExposeHeader")  == 0){
+    return new CORSRuleExposeHeader_S3;
+  }
+  return NULL;
+}
+
diff --git a/src/rgw/rgw_cors_s3.h b/src/rgw/rgw_cors_s3.h
new file mode 100644 (file)
index 0000000..32e5dcd
--- /dev/null
@@ -0,0 +1,58 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_RGW_CORS_S3_H
+#define CEPH_RGW_CORS_S3_H
+
+#include <map>
+#include <string>
+#include <iostream>
+#include <expat.h>
+
+#include <include/types.h>
+#include <common/Formatter.h>
+#include "rgw_xml.h"
+#include "rgw_cors.h"
+
+using namespace std;
+
+class RGWCORSRule_S3 : public RGWCORSRule, public XMLObj
+{
+  public:
+    RGWCORSRule_S3(){}
+    ~RGWCORSRule_S3(){}
+    
+    bool xml_end(const char *el);
+    void to_xml(XMLFormatter& f);
+};
+
+class RGWCORSConfiguration_S3 : public RGWCORSConfiguration, public XMLObj
+{
+  public:
+    RGWCORSConfiguration_S3(){}
+    ~RGWCORSConfiguration_S3(){}
+
+    bool xml_end(const char *el);
+    void to_xml(ostream& out);
+};
+
+class RGWCORSXMLParser_S3 : public RGWXMLParser
+{
+  CephContext *cct;
+
+  XMLObj *alloc_obj(const char *el);
+public:
+  RGWCORSXMLParser_S3(CephContext *_cct) : cct(_cct) {}
+};
+#endif /*CEPH_RGW_CORS_S3_H*/
diff --git a/src/rgw/rgw_cors_swift.h b/src/rgw/rgw_cors_swift.h
new file mode 100644 (file)
index 0000000..5d1a203
--- /dev/null
@@ -0,0 +1,77 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_RGW_CORS_SWIFT3_H
+#define CEPH_RGW_CORS_SWIFT3_H
+
+#include <map>
+#include <string>
+#include <iostream>
+#include <vector>
+#include <include/types.h>
+#include <include/str_list.h>
+
+#include "rgw_cors.h"
+
+using namespace std;
+
+class RGWCORSConfiguration_SWIFT : public RGWCORSConfiguration
+{
+  public:
+    RGWCORSConfiguration_SWIFT(){}
+    ~RGWCORSConfiguration_SWIFT(){}
+    int create_update(const char *allow_origins, const char *allow_headers, 
+                  const char *expose_headers, const char *max_age){
+      set<string> o, h, oc;
+      list<string> e;
+      unsigned a = CORS_MAX_AGE_INVALID;
+      uint8_t flags = RGW_CORS_ALL;
+
+      string ao = allow_origins;
+      get_str_set(ao, oc);
+      if(oc.empty())
+        return -EINVAL;
+      for(set<string>::iterator it = oc.begin(); it != oc.end(); it++){
+        string host = *it;
+        if(validate_name_string(host) != 0)
+          return -EINVAL;
+        o.insert(o.end(), host);
+      }
+      if(allow_headers){
+        string ah = allow_headers;
+        get_str_set(ah, h);
+        for(set<string>::iterator it = h.begin();
+            it != h.end(); it++){
+          string s = (*it);
+          if(validate_name_string(s) != 0)
+            return -EINVAL;
+        }
+      }
+      if(expose_headers){
+        string eh = expose_headers;
+        get_str_list(eh, e);
+      }
+      if(max_age){
+        char *end = NULL;
+        a = strtol(max_age, &end, 10);
+        if (a == LONG_MAX)
+          a = CORS_MAX_AGE_INVALID;
+      }
+
+      RGWCORSRule rule(o, h, e, flags, a);
+      stack_rule(rule);
+      return 0;
+    }
+};
+#endif /*CEPH_RGW_CORS_SWIFT3_H*/
index 254af46e6634375ed806993c20e8ab3f562c075a..cc586c1406b4fc1ac793e76051741c0842b9cbde 100644 (file)
@@ -38,6 +38,7 @@ const static struct rgw_html_errors RGW_HTML_ERRORS[] = {
     { ENOENT, 404, "NoSuchKey" },
     { ERR_NO_SUCH_BUCKET, 404, "NoSuchBucket" },
     { ERR_NO_SUCH_UPLOAD, 404, "NoSuchUpload" },
+    { ERR_NOT_FOUND, 404, "Not Found"},
     { ERR_METHOD_NOT_ALLOWED, 405, "MethodNotAllowed" },
     { ETIMEDOUT, 408, "RequestTimeout" },
     { EEXIST, 409, "BucketAlreadyExists" },
index 3d4459cb576be783ad0c20446852cd2403423df2..cadc51e7b3f1c2c659a14b76e0d47c3ea7de1e13 100644 (file)
@@ -330,7 +330,13 @@ void RGWProcess::handle_request(RGWRequest *req)
     abort_early(s, ret);
     goto done;
   }
-
+  req->log(s, "reading the cors attr");
+  ret = handler->read_cors_config();
+  if (ret < 0) {
+    abort_early(s, ret);
+    goto done;
+  }
+  
   req->log(s, "verifying op permissions");
   ret = op->verify_permission();
   if (ret < 0) {
index ecceb06c5fb4420f9b1e31cb03573e9234c89dba..bff0e3573cbb57913456e5133aefef2841ba737d 100644 (file)
@@ -18,6 +18,8 @@
 #include "rgw_log.h"
 #include "rgw_multi.h"
 #include "rgw_multi_del.h"
+#include "rgw_cors.h"
+#include "rgw_cors_s3.h"
 
 #include "rgw_client_io.h"
 
@@ -801,7 +803,7 @@ void RGWCreateBucket::execute()
 {
   RGWAccessControlPolicy old_policy(s->cct);
   map<string, bufferlist> attrs;
-  bufferlist aclbl;
+  bufferlist aclbl, corsbl;
   bool existed;
   int r;
   rgw_obj obj(store->zone.domain_root, s->bucket_name_str);
@@ -1526,7 +1528,7 @@ void RGWPutMetadata::execute()
   int meta_prefix_len = sizeof(RGW_ATTR_META_PREFIX) - 1;
   map<string, bufferlist> attrs, orig_attrs, rmattrs;
   map<string, bufferlist>::iterator iter;
-  bufferlist bl;
+  bufferlist bl, cors_bl;
 
   rgw_obj obj(s->bucket, s->object_str);
 
@@ -1557,6 +1559,10 @@ void RGWPutMetadata::execute()
     policy.encode(bl);
     attrs[RGW_ATTR_ACL] = bl;
   }
+  if(has_cors) {
+    cors_config.encode(cors_bl);
+    attrs[RGW_ATTR_CORS] = cors_bl;
+  }
   ret = store->set_attrs(s->obj_ctx, obj, attrs, &rmattrs);
 }
 
@@ -1838,6 +1844,192 @@ void RGWPutACLs::execute()
   ret = store->set_attr(s->obj_ctx, obj, RGW_ATTR_ACL, bl);
 }
 
+int RGWGetCORS::verify_permission()
+{
+  if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0)
+    return -EACCES;
+
+  return 0;
+}
+
+void RGWGetCORS::execute()
+{
+  stringstream ss;
+  if(!s->bucket_cors){
+    dout(2) << "No CORS configuration set yet for this bucket" << dendl;
+    ret = -ENOENT;
+    return;
+  }
+  RGWCORSConfiguration_S3 *s3cors = static_cast<RGWCORSConfiguration_S3 *>(s->bucket_cors);
+  s3cors->to_xml(ss);
+  cors = ss.str(); 
+}
+
+int RGWPutCORS::verify_permission()
+{
+  if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0)
+    return -EACCES;
+
+  return 0;
+}
+
+void RGWPutCORS::execute()
+{
+  bufferlist bl;
+
+  RGWCORSConfiguration_S3 *cors_config;
+  RGWCORSXMLParser_S3 parser(s->cct);
+  stringstream ss;
+  rgw_obj obj;
+
+  ret = 0;
+
+  if (!parser.init()) {
+    ret = -EINVAL;
+    return;
+  }
+  ret = get_params();
+  if (ret < 0)
+    return;
+
+  ldout(s->cct, 15) << "read len=" << len << " data=" << (data ? data : "") << dendl;
+  if (!parser.parse(data, len, 1)) {
+    ret = -EINVAL;
+    return;
+  }
+  cors_config = (RGWCORSConfiguration_S3 *)parser.find_first("CORSConfiguration");
+  if (!cors_config) {
+    ret = -EINVAL;
+    return;
+  }
+
+  if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 15)) {
+    ldout(s->cct, 15) << "CORSConfiguration";
+    cors_config->to_xml(*_dout);
+    *_dout << dendl;
+  }
+
+  string no_obj;
+  cors_config->encode(bl);
+  obj.init(s->bucket, no_obj);
+  store->set_atomic(s->obj_ctx, obj);
+  ret = store->set_attr(s->obj_ctx, obj, RGW_ATTR_CORS, bl);
+}
+
+int RGWDeleteCORS::verify_permission()
+{
+  if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0)
+    return -EACCES;
+
+  return 0;
+}
+
+void RGWDeleteCORS::execute()
+{
+  bufferlist bl;
+  rgw_obj obj;
+  string no_obj;
+  if(!s->bucket_cors){
+    dout(2) << "No CORS configuration set yet for this bucket" << dendl;
+    ret = -ENOENT;
+    return;
+  }
+  obj.init(s->bucket, no_obj);
+  store->set_atomic(s->obj_ctx, obj);
+  map<string, bufferlist> orig_attrs, attrs, rmattrs;
+  map<string, bufferlist>::iterator iter;
+  /* check if obj exists, read orig attrs */
+  ret = get_obj_attrs(store, s, obj, orig_attrs, NULL);
+  if (ret < 0)
+    return;
+
+  /* only remove meta attrs */
+  for (iter = orig_attrs.begin(); iter != orig_attrs.end(); ++iter) {
+    const string& name = iter->first;
+    dout(10) << "DeleteCORS : attr: " << name << dendl;
+    if (name.compare(0, (sizeof(RGW_ATTR_CORS) - 1), RGW_ATTR_CORS) == 0) {
+      rmattrs[name] = iter->second;
+    } else if (attrs.find(name) == attrs.end()) {
+      attrs[name] = iter->second;
+    }
+  }
+  ret = store->set_attrs(s->obj_ctx, obj, attrs, &rmattrs);
+}
+
+void RGWOptionsCORS::get_response_params(string& hdrs, string& exp_hdrs, unsigned *max_age){
+  if(req_hdrs){
+    list<string> hl;
+    get_str_list(req_hdrs, hl);
+    for(list<string>::iterator it = hl.begin(); it != hl.end(); it++){
+      if(!rule->is_header_allowed((*it).c_str(), (*it).length())){
+        dout(5) << "Header " << (*it) << " is not registered in this rule" << dendl;
+      }else {
+        if(hdrs.length() > 0)hdrs.append(",");
+        hdrs.append((*it));
+      }
+    }
+  }
+  rule->format_exp_headers(exp_hdrs);
+  *max_age = rule->get_max_age();
+}
+
+int RGWOptionsCORS::validate_cors_request(){
+  RGWCORSConfiguration *cc = s->bucket_cors;
+  rule = cc->host_name_rule(origin);
+  if(!rule){
+    dout(10) << "There is no corsrule present for " << origin << dendl;
+    return -ENOENT;
+  }
+
+  uint8_t flags = 0;
+  if(strcmp(req_meth, "GET") == 0) flags = RGW_CORS_GET;
+  else if (strcmp(req_meth, "POST") == 0) flags = RGW_CORS_POST;
+  else if (strcmp(req_meth, "PUT") == 0) flags = RGW_CORS_PUT;
+  else if (strcmp(req_meth, "DELETE") == 0) flags = RGW_CORS_DELETE;
+  else if (strcmp(req_meth, "HEAD") == 0) flags = RGW_CORS_HEAD;
+
+  if ((rule->get_allowed_methods() & flags) == flags){
+    dout(10) << "Method " << req_meth << " is supported" << dendl;
+  }else {
+    dout(5) << "Method " << req_meth << " is not supported" << dendl;
+    req_meth = NULL;
+    return -ENOTSUP;
+  }
+  return 0;
+}
+
+void RGWOptionsCORS::execute()
+{
+  if(!s->bucket_cors){
+    dout(2) << "No CORS configuration set yet for this bucket" << dendl;
+    ret = -EACCES;
+    return;
+  }
+  req_meth = s->env->get("HTTP_ACCESS_CONTROL_REQUEST_METHOD");
+  if(!req_meth){
+    dout(0) << 
+    "Preflight request without mandatory Access-control-request-method header"
+    << dendl;
+    ret = -EACCES;
+    return;
+  }
+  origin = s->env->get("HTTP_ORIGIN");
+  if(!origin){
+    dout(0) << 
+    "Preflight request without mandatory Origin header"
+    << dendl;
+    ret = -EACCES;
+    return;
+  }
+  req_hdrs = s->env->get("HTTP_ACCESS_CONTROL_ALLOW_HEADERS");
+  ret = validate_cors_request();
+  if(!rule){
+    origin = req_meth = NULL;
+    return;
+  }
+  return;
+}
+
 int RGWInitMultipart::verify_permission()
 {
   if (!verify_bucket_permission(s, RGW_PERM_WRITE))
@@ -2318,6 +2510,41 @@ int RGWHandler::do_read_permissions(RGWOp *op, bool only_bucket)
   return ret;
 }
 
+int RGWHandler::read_cors_config(void)
+{
+  int ret;
+  bufferlist bl;
+  dout(10) << "Going to read cors from attrs" << dendl;
+  string no_object;
+  rgw_obj no_obj(s->bucket, no_object);
+  if (no_obj.bucket.name.size()) {
+    ret = store->get_attr(s->obj_ctx, no_obj, RGW_ATTR_CORS, bl);
+    if (ret >= 0) {
+      bufferlist::iterator iter = bl.begin();
+      s->bucket_cors = new RGWCORSConfiguration();
+      try {
+        s->bucket_cors->decode(iter);
+      } catch (buffer::error& err) {
+        ldout(s->cct, 0) << "ERROR: could not decode policy, caught buffer::error" << dendl;
+        return -EIO;
+      }
+      if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 15)) {
+        RGWCORSConfiguration_S3 *s3cors = static_cast<RGWCORSConfiguration_S3 *>(s->bucket_cors);
+        ldout(s->cct, 15) << "Read RGWCORSConfiguration";
+        s3cors->to_xml(*_dout);
+        *_dout << dendl;
+      }
+    }else{
+      /*Not a serious error*/
+      dout(2) << "Warning: There is no content for CORS xattr,"
+            " cors may not be set yet" << dendl;
+      ret = 0;
+    }
+  }
+  return ret;
+}
+
 
 RGWOp *RGWHandler::get_op(RGWRados *store)
 {
@@ -2341,6 +2568,9 @@ RGWOp *RGWHandler::get_op(RGWRados *store)
    case OP_COPY:
      op = op_copy();
      break;
+   case OP_OPTIONS:
+     op = op_options();
+     break;
    default:
      return NULL;
   }
index 8f1252cf2dc7659c607ec9708bf248fef60b9df7..7272090a96d37b2e8fa4e83484f350ad0be27561 100644 (file)
@@ -18,6 +18,7 @@
 #include "rgw_rados.h"
 #include "rgw_user.h"
 #include "rgw_acl.h"
+#include "rgw_cors.h"
 
 using namespace std;
 
@@ -363,11 +364,13 @@ class RGWPutMetadata : public RGWOp {
 protected:
   int ret;
   map<string, bufferlist> attrs;
-  bool has_policy;
+  bool has_policy, has_cors;
   RGWAccessControlPolicy policy;
+  RGWCORSConfiguration cors_config;
 
 public:
   RGWPutMetadata() {
+    has_cors = false;
     has_policy = false;
     ret = 0;
   }
@@ -499,6 +502,78 @@ public:
   virtual const char *name() { return "put_acls"; }
 };
 
+class RGWGetCORS : public RGWOp {
+protected:
+  int ret;
+  string cors;
+
+public:
+  RGWGetCORS() : ret(0) {}
+
+  int verify_permission();
+  void execute();
+
+  virtual void send_response() = 0;
+  virtual const char *name() { return "get_cors"; }
+};
+
+class RGWPutCORS : public RGWOp {
+protected:
+  int ret;
+  size_t len;
+  char *data;
+
+public:
+  RGWPutCORS() {
+    ret = 0;
+    len = 0;
+    data = NULL;
+  }
+  virtual ~RGWPutCORS() {
+    free(data);
+  }
+
+  int verify_permission();
+  void execute();
+
+  virtual int get_params() = 0;
+  virtual void send_response() = 0;
+  virtual const char *name() { return "put_cors"; }
+};
+
+class RGWDeleteCORS : public RGWOp {
+protected:
+  int ret;
+
+public:
+  RGWDeleteCORS() : ret(0) {}
+
+  int verify_permission();
+  void execute();
+
+  virtual void send_response() = 0;
+  virtual const char *name() { return "delete_cors"; }
+};
+
+class RGWOptionsCORS : public RGWOp {
+protected:
+  int ret;
+  RGWCORSRule *rule;
+  const char *origin, *req_hdrs, *req_meth;
+
+public:
+  RGWOptionsCORS() : ret(0), rule(NULL), origin(NULL),
+                     req_hdrs(NULL), req_meth(NULL){
+  }
+
+  int verify_permission(){return 0;}
+  int validate_cors_request();
+  void execute();
+  void get_response_params(string& allowed_hdrs, string& exp_hdrs, unsigned *max_age);
+  virtual void send_response() = 0;
+  virtual const char *name() { return "options_cors"; }
+};
+
 class RGWInitMultipart : public RGWOp {
 protected:
   int ret;
@@ -746,8 +821,10 @@ protected:
   virtual RGWOp *op_head() { return NULL; }
   virtual RGWOp *op_post() { return NULL; }
   virtual RGWOp *op_copy() { return NULL; }
+  virtual RGWOp *op_options() { return NULL; }
 public:
   RGWHandler() : store(NULL), s(NULL) {}
+  int read_cors_config();
   virtual ~RGWHandler();
   virtual int init(RGWRados *store, struct req_state *_s, RGWClientIO *cio);
 
index 8b1c2c5e32572e0a016a3ff81b8b868ce283eaa6..a02ab83c1c92dc03f02981b726efeaee2e6b4264 100644 (file)
@@ -12,6 +12,7 @@
 #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"
@@ -343,6 +344,23 @@ void dump_owner(struct req_state *s, string& id, string& name, const char *secti
   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')){
+    s->cio->print("Access-Control-Allow-Origin: %s\n", origin?origin:"");
+    if(meth && (meth[0] != '\0'))
+      s->cio->print("Access-Control-Allow-Methods: %s\n", meth?meth:"");
+    if(hdr && (hdr[0] != '\0'))
+      s->cio->print("Access-Control-Allow-Headers: %s\n", hdr);
+    if(exp_hdr && (exp_hdr[0] != '\0')){
+      s->cio->print("Access-Control-Expose-Headers: %s\n", exp_hdr);
+    }
+    if(max_age != CORS_MAX_AGE_INVALID){
+      s->cio->print("Access-Control-Max-Age: %d\n", max_age);
+    }
+  }
+}
+
 void dump_start(struct req_state *s)
 {
   if (!s->content_started) {
@@ -671,6 +689,30 @@ int RGWPutACLs_ObjStore::get_params()
   return ret;
 }
 
+int RGWPutCORS_ObjStore::get_params()
+{
+  size_t cl = 0;
+  if (s->length)
+    cl = atoll(s->length);
+  if (cl) {
+    data = (char *)malloc(cl + 1);
+    if (!data) {
+       ret = -ENOMEM;
+       return ret;
+    }
+    int read_len;
+    int r = s->cio->read(data, cl, &read_len);
+    len = read_len;
+    if (r < 0)
+      return r;
+    data[len] = '\0';
+  } else {
+    len = 0;
+  }
+
+  return ret;
+}
+
 static int read_all_chunked_input(req_state *s, char **pdata, int *plen)
 {
 #define READ_CHUNK 4096
@@ -1031,6 +1073,8 @@ static http_op op_from_method(const char *method)
     return OP_POST;
   if (strcmp(method, "COPY") == 0)
     return OP_COPY;
+  if (strcmp(method, "OPTIONS") == 0)
+    return OP_OPTIONS;
 
   return OP_UNKNOWN;
 }
@@ -1065,6 +1109,9 @@ int RGWHandler_ObjStore::read_permissions(RGWOp *op_obj)
     break;
   case OP_COPY: // op itself will read and verify the permissions
     return 0;
+  case OP_OPTIONS:
+    only_bucket = false;
+    break;
   default:
     return -EINVAL;
   }
index 7dd61bdb1e5d13a49bdcae315e7395fe63a2d2f5..6c24f8b2fb0b9a6be7187c81d226c64ccd455560 100644 (file)
@@ -148,6 +148,32 @@ public:
   int get_params();
 };
 
+class RGWGetCORS_ObjStore : public RGWGetCORS {
+public:
+  RGWGetCORS_ObjStore() {}
+  ~RGWGetCORS_ObjStore() {}
+};
+
+class RGWPutCORS_ObjStore : public RGWPutCORS {
+public:
+  RGWPutCORS_ObjStore() {}
+  ~RGWPutCORS_ObjStore() {}
+
+  int get_params();
+};
+
+class RGWDeleteCORS_ObjStore : public RGWDeleteCORS {
+public:
+  RGWDeleteCORS_ObjStore() {}
+  ~RGWDeleteCORS_ObjStore() {}
+};
+
+class RGWOptionsCORS_ObjStore : public RGWOptionsCORS {
+public:
+  RGWOptionsCORS_ObjStore() {}
+  ~RGWOptionsCORS_ObjStore() {}
+};
+
 class RGWInitMultipart_ObjStore : public RGWInitMultipart {
 public:
   RGWInitMultipart_ObjStore() {}
@@ -216,6 +242,7 @@ protected:
   virtual RGWOp *op_head() { return NULL; }
   virtual RGWOp *op_post() { return NULL; }
   virtual RGWOp *op_copy() { return NULL; }
+  virtual RGWOp *op_options() { return NULL; }
 
   virtual int validate_bucket_name(const string& bucket);
   virtual int validate_object_name(const string& object);
@@ -295,7 +322,8 @@ extern void dump_uri_from_state(struct req_state *s);
 extern void dump_redirect(struct req_state *s, const string& redirect);
 extern void dump_pair(struct req_state *s, const char *key, const char *value);
 extern bool is_valid_url(const char *url);
-
+extern 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);
 
 
 #endif
index 649f0b46a84070e20b1bbf9fa03a872aab1ce1d0..0a3198829457bc8d5c09d33668410b174c33542b 100644 (file)
@@ -10,6 +10,7 @@
 #include "rgw_acl.h"
 #include "rgw_policy_s3.h"
 #include "rgw_user.h"
+#include "rgw_cors.h"
 
 #include "common/armor.h"
 
@@ -1227,6 +1228,64 @@ void RGWPutACLs_ObjStore_S3::send_response()
   dump_start(s);
 }
 
+void RGWGetCORS_ObjStore_S3::send_response()
+{
+  if(ret){
+    if(ret == -ENOENT) 
+      set_req_state_err(s, ERR_NOT_FOUND);
+    else 
+      set_req_state_err(s, ret);
+  }
+  dump_errno(s);
+  end_header(s, "application/xml");
+  dump_start(s);
+  if(!ret)s->cio->write(cors.c_str(), cors.size());
+}
+
+void RGWPutCORS_ObjStore_S3::send_response()
+{
+  if (ret)
+    set_req_state_err(s, ret);
+  dump_errno(s);
+  end_header(s, "application/xml");
+  dump_start(s);
+}
+
+void RGWDeleteCORS_ObjStore_S3::send_response()
+{
+  int r = ret;
+  if (!r || r == -ENOENT)
+    r = STATUS_NO_CONTENT;
+
+  set_req_state_err(s, r);
+  dump_errno(s);
+  end_header(s);
+}
+
+void RGWOptionsCORS_ObjStore_S3::send_response()
+{
+  string hdrs, exp_hdrs;
+  uint32_t max_age = CORS_MAX_AGE_INVALID;
+  /*EACCES means, there is no CORS registered yet for the bucket
+   *ENOENT means, there is no match of the Origin in the list of CORSRule
+   *ENOTSUPP means, the HTTP_METHOD is not supported
+   */
+  if(ret == -ENOENT)
+    ret = -EACCES;
+  if(ret != -EACCES){
+    get_response_params(hdrs, exp_hdrs, &max_age);
+  }else{
+    set_req_state_err(s, ret);
+    dump_errno(s);
+    end_header(s);
+    return;
+  }
+
+  dump_errno(s);
+  dump_access_control(s, origin, req_meth, hdrs.c_str(), exp_hdrs.c_str(), max_age); 
+  end_header(s);
+}
+
 int RGWInitMultipart_ObjStore_S3::get_params()
 {
   RGWAccessControlPolicy_S3 s3policy(s->cct);
@@ -1482,6 +1541,8 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_get()
     return new RGWGetBucketLogging_ObjStore_S3;
   if (is_acl_op()) {
     return new RGWGetACLs_ObjStore_S3;
+  } else if (is_cors_op()){
+    return new RGWGetCORS_ObjStore_S3;
   } else if (s->args.exists("uploads")) {
     return new RGWListBucketMultiparts_ObjStore_S3;
   }
@@ -1504,12 +1565,17 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_put()
     return NULL;
   if (is_acl_op()) {
     return new RGWPutACLs_ObjStore_S3;
-  }
+  } else if (is_cors_op()){
+    return new RGWPutCORS_ObjStore_S3;
+  } 
   return new RGWCreateBucket_ObjStore_S3;
 }
 
 RGWOp *RGWHandler_ObjStore_Bucket_S3::op_delete()
 {
+  if(is_cors_op()) {
+    return new RGWDeleteCORS_ObjStore_S3;
+  }
   return new RGWDeleteBucket_ObjStore_S3;
 }
 
@@ -1522,6 +1588,11 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_post()
   return new RGWPostObj_ObjStore_S3;
 }
 
+RGWOp *RGWHandler_ObjStore_Bucket_S3::op_options()
+{
+  return new RGWOptionsCORS_ObjStore_S3;
+}
+
 RGWOp *RGWHandler_ObjStore_Obj_S3::get_obj_op(bool get_data)
 {
   if (is_acl_op()) {
@@ -1584,6 +1655,11 @@ RGWOp *RGWHandler_ObjStore_Obj_S3::op_post()
   return NULL;
 }
 
+RGWOp *RGWHandler_ObjStore_Obj_S3::op_options()
+{
+  return new RGWOptionsCORS_ObjStore_S3;
+}
+
 int RGWHandler_ObjStore_S3::init_from_header(struct req_state *s, int default_formatter, bool configurable_format)
 {
   string req;
@@ -1720,6 +1796,7 @@ int RGWHandler_ObjStore_S3::init(RGWRados *store, struct req_state *s, RGWClient
   return RGWHandler_ObjStore::init(store, s, cio);
 }
 
+
 /*
  * ?get the canonical amazon-style header for something?
  */
index cc900507c1238234f4076f3d1a9d7e993b818b86..277ebf1ffb8569ae63dd4f5ece271bedc906097f 100644 (file)
@@ -164,6 +164,37 @@ public:
   void send_response();
 };
 
+class RGWGetCORS_ObjStore_S3 : public RGWGetCORS_ObjStore {
+public:
+  RGWGetCORS_ObjStore_S3() {}
+  ~RGWGetCORS_ObjStore_S3() {}
+
+  void send_response();
+};
+
+class RGWPutCORS_ObjStore_S3 : public RGWPutCORS_ObjStore {
+public:
+  RGWPutCORS_ObjStore_S3() {}
+  ~RGWPutCORS_ObjStore_S3() {}
+
+  void send_response();
+};
+
+class RGWDeleteCORS_ObjStore_S3 : public RGWDeleteCORS_ObjStore {
+public:
+  RGWDeleteCORS_ObjStore_S3() {}
+  ~RGWDeleteCORS_ObjStore_S3() {}
+
+  void send_response();
+};
+
+class RGWOptionsCORS_ObjStore_S3 : public RGWOptionsCORS_ObjStore {
+public:
+  RGWOptionsCORS_ObjStore_S3() {}
+  ~RGWOptionsCORS_ObjStore_S3() {}
+
+  void send_response();
+};
 
 class RGWInitMultipart_ObjStore_S3 : public RGWInitMultipart_ObjStore {
 public:
@@ -269,8 +300,11 @@ protected:
   bool is_acl_op() {
     return s->args.exists("acl");
   }
+  bool is_cors_op() {
+      return s->args.exists("cors");
+  }
   bool is_obj_update_op() {
-    return is_acl_op();
+    return is_acl_op() || is_cors_op();
   }
   RGWOp *get_obj_op(bool get_data);
 
@@ -279,6 +313,7 @@ protected:
   RGWOp *op_put();
   RGWOp *op_delete();
   RGWOp *op_post();
+  RGWOp *op_options();
 public:
   RGWHandler_ObjStore_Bucket_S3() {}
   virtual ~RGWHandler_ObjStore_Bucket_S3() {}
@@ -289,6 +324,9 @@ protected:
   bool is_acl_op() {
     return s->args.exists("acl");
   }
+  bool is_cors_op() {
+      return s->args.exists("cors");
+  }
   bool is_obj_update_op() {
     return is_acl_op();
   }
@@ -299,6 +337,7 @@ protected:
   RGWOp *op_put();
   RGWOp *op_delete();
   RGWOp *op_post();
+  RGWOp *op_options();
 public:
   RGWHandler_ObjStore_Obj_S3() {}
   virtual ~RGWHandler_ObjStore_Obj_S3() {}
index 31f92af1de277e2acd2cc4ea7f7f9050fe16a752..4ce95125dd79803ad1fce21dd5f4d52917eedc8c 100644 (file)
@@ -4,6 +4,7 @@
 #include "rgw_swift.h"
 #include "rgw_rest_swift.h"
 #include "rgw_acl_swift.h"
+#include "rgw_cors_swift.h"
 #include "rgw_formats.h"
 #include "rgw_client_io.h"
 
@@ -364,6 +365,25 @@ int RGWPutMetadata_ObjStore_SWIFT::get_params()
       policy = swift_policy;
       has_policy = true;
     }
+
+    /*Check and update CORS configuration*/
+    const char *allow_origins = s->env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_ALLOW_ORIGIN");
+    const char *allow_headers = s->env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_ALLOW_HEADERS");
+    const char *expose_headers = s->env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_EXPOSE_HEADERS");
+    const char *max_age = s->env->get("HTTP_X_CONTAINER_META_ACCESS_CONTROL_MAX_AGE");
+    if(allow_origins){
+      RGWCORSConfiguration_SWIFT *swift_cors = new RGWCORSConfiguration_SWIFT;
+      int r = swift_cors->create_update(allow_origins, allow_headers, expose_headers, max_age);
+      if (r < 0){
+        dout(0) << "Error creating/updating the cors configuration" << dendl;
+        delete swift_cors;
+        return r;
+      }
+      has_cors = true;
+      cors_config = *swift_cors;
+      cors_config.dump();
+      delete swift_cors;
+    }
   }
 
   return 0;
@@ -512,6 +532,29 @@ send_data:
   return 0;
 }
 
+void RGWOptionsCORS_ObjStore_SWIFT::send_response()
+{
+  string hdrs, exp_hdrs;
+  uint32_t max_age = CORS_MAX_AGE_INVALID;
+  /*EACCES means, there is no CORS registered yet for the bucket
+   *ENOENT means, there is no match of the Origin in the list of CORSRule
+   *ENOTSUPP means, the HTTP_METHOD is not supported
+   */
+  if(ret == -ENOENT)
+    ret = -EACCES;
+  if(ret != -EACCES){
+    get_response_params(hdrs, exp_hdrs, &max_age);
+  }else{
+    set_req_state_err(s, ret);
+    dump_errno(s);
+    end_header(s);
+    return;
+  }
+  dump_errno(s);
+  dump_access_control(s, origin, req_meth, hdrs.c_str(), exp_hdrs.c_str(), max_age); 
+  end_header(s);
+}
+
 RGWOp *RGWHandler_ObjStore_Service_SWIFT::op_get()
 {
   return new RGWListBuckets_ObjStore_SWIFT;
@@ -568,6 +611,11 @@ RGWOp *RGWHandler_ObjStore_Bucket_SWIFT::op_post()
   return new RGWPutMetadata_ObjStore_SWIFT;
 }
 
+RGWOp *RGWHandler_ObjStore_Bucket_SWIFT::op_options()
+{
+  return new RGWOptionsCORS_ObjStore_SWIFT;
+}
+
 RGWOp *RGWHandler_ObjStore_Obj_SWIFT::get_obj_op(bool get_data)
 {
   if (is_acl_op()) {
@@ -621,6 +669,11 @@ RGWOp *RGWHandler_ObjStore_Obj_SWIFT::op_copy()
   return new RGWCopyObj_ObjStore_SWIFT;
 }
 
+RGWOp *RGWHandler_ObjStore_Obj_SWIFT::op_options()
+{
+  return new RGWOptionsCORS_ObjStore_SWIFT;
+}
+
 int RGWHandler_ObjStore_SWIFT::authorize()
 {
   if (!s->os_auth_token) {
index 1704823581b3da2d8410b506d13193027ca60fe0..420d1d86873453fa2de71f9a4294bd2dd88cf26a 100644 (file)
@@ -126,6 +126,14 @@ public:
   void send_response() {}
 };
 
+class RGWOptionsCORS_ObjStore_SWIFT : public RGWOptionsCORS_ObjStore {
+public:
+  RGWOptionsCORS_ObjStore_SWIFT() {}
+  ~RGWOptionsCORS_ObjStore_SWIFT() {}
+
+  void send_response();
+};
+
 class RGWHandler_ObjStore_SWIFT : public RGWHandler_ObjStore {
   friend class RGWRESTMgr_SWIFT;
 protected:
@@ -168,6 +176,7 @@ protected:
   RGWOp *op_put();
   RGWOp *op_delete();
   RGWOp *op_post();
+  RGWOp *op_options();
 public:
   RGWHandler_ObjStore_Bucket_SWIFT() {}
   virtual ~RGWHandler_ObjStore_Bucket_SWIFT() {}
@@ -186,6 +195,7 @@ protected:
   RGWOp *op_delete();
   RGWOp *op_post();
   RGWOp *op_copy();
+  RGWOp *op_options();
 public:
   RGWHandler_ObjStore_Obj_SWIFT() {}
   virtual ~RGWHandler_ObjStore_Obj_SWIFT() {}
index adf3ccb4ab07d1390da3aa1b2260ebd0031acafc..41e1f8ca63e1ccd604bbe770a908b272c0f5baa7 100644 (file)
@@ -108,7 +108,10 @@ find(string name)
   map<string, XMLObj *>::iterator first;
   map<string, XMLObj *>::iterator last;
   first = children.find(name);
-  last = children.upper_bound(name);
+  if(first != children.end()){
+    last = children.upper_bound(name);
+  }else
+    last = children.end();
   iter.set(first, last);
   return iter;
 }
diff --git a/src/test/test_cors.cc b/src/test/test_cors.cc
new file mode 100644 (file)
index 0000000..719dcb8
--- /dev/null
@@ -0,0 +1,906 @@
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+extern "C"{
+#include <curl/curl.h>
+}
+#include "common/ceph_crypto.h"
+#include <map>
+#include <list>
+#define S3_BUCKET_NAME "s3testgw.fcgi"
+#define SWIFT_BUCKET_NAME "swift3testgw.fcgi"
+#define BUCKET_URL \
+  ((g_test->get_key_type() == KEY_TYPE_S3)?(string("/"S3_BUCKET_NAME)):(string("/swift/v1/"SWIFT_BUCKET_NAME)))
+#define GTEST
+#ifdef GTEST
+#include <gtest/gtest.h>
+#else
+#define TEST(x, y) void y()
+#define ASSERT_EQ(v, s) if(v != s)cout << "Error at " << __LINE__ << "(" << #v << "!= " << #s << "\n"; \
+                                else cout << "(" << #v << "==" << #s << ") PASSED\n";
+#define EXPECT_EQ(v, s) ASSERT_EQ(v, s)
+#define ASSERT_TRUE(c) if(c)cout << "Error at " << __LINE__ << "(" << #c << ")" << "\n"; \
+                          else cout << "(" << #c << ") PASSED\n";
+#define EXPECT_TRUE(c) ASSERT_TRUE(c) 
+#endif
+#include "common/code_environment.h"
+#include "common/ceph_argparse.h"
+#include "common/Finisher.h"
+#include "global/global_init.h"
+#include "rgw/rgw_cors.h"
+#include "rgw/rgw_cors_s3.h"
+
+using namespace std;
+
+#define CURL_VERBOSE 0
+#define HTTP_RESPONSE_STR "RespCode"
+#define CEPH_CRYPTO_HMACSHA1_DIGESTSIZE 20
+
+extern "C" int ceph_armor(char *dst, const char *dst_end, 
+                          const char *src, const char *end);
+enum key_type {
+  KEY_TYPE_SWIFT = 1,
+  KEY_TYPE_S3
+};
+
+static void print_usage(char *exec){
+  cout << "Usage: " << exec << " <Options>\n";
+  cout << "Options:\n"
+          "-g <gw-ip> - The ip address of the gateway\n"
+          "-p <gw-port> - The port number of the gateway\n"
+          "-k <SWIFT|S3> - The key type, either SWIFT or S3\n"
+          "-s3 <AWSAccessKeyId:SecretAccessKeyID> - Only, if the key type is S3, gives S3 credentials\n"
+          "-swift <Auth-Token> - Only if the key type is SWIFT, and gives the SWIFT credentials\n";
+}
+class test_cors_helper {
+  private:
+    string host;
+    string port;
+    string creds;
+    CURL *curl_inst;
+    map<string, string> response;
+    list<string> extra_hdrs;
+    string *resp_data;
+    unsigned resp_code;
+    key_type kt;
+  public:
+    test_cors_helper() : resp_data(NULL){
+      curl_global_init(CURL_GLOBAL_ALL);
+    }
+    ~test_cors_helper(){
+      curl_global_cleanup();
+    }
+    int send_request(string method, string uri, 
+                     size_t (*function)(void *,size_t,size_t,void *) = 0,
+                     void *ud = 0, size_t length = 0);
+    int extract_input(int argc, char *argv[]);
+    string& get_response(string hdr){
+      return response[hdr];
+    }
+    void set_extra_header(string hdr){
+      extra_hdrs.push_back(hdr);
+    }
+    void set_response(char *val);
+    void set_response_data(char *data, size_t len){
+      if(resp_data) delete resp_data;
+      resp_data = new string(data, len);
+      /*cout << resp_data->c_str() << "\n";*/
+    }
+    const string *get_response_data(){return resp_data;}
+    unsigned get_resp_code(){return resp_code;}
+    key_type get_key_type(){return kt;}
+};
+
+int test_cors_helper::extract_input(int argc, char *argv[]){
+#define ERR_CHECK_NEXT_PARAM(o) \
+  if((loop + 1) >= argc)return -1; \
+  else o = argv[loop+1];
+
+  for(unsigned loop = 1;loop < (unsigned)argc; loop += 2){
+    if(strcmp(argv[loop], "-g") == 0){
+      ERR_CHECK_NEXT_PARAM(host);
+    }else if(strcmp(argv[loop], "-k") == 0){
+      string type;
+      ERR_CHECK_NEXT_PARAM(type);
+      if(type.compare("S3") == 0)kt = KEY_TYPE_S3;
+      else if(type.compare("SWIFT") == 0)kt = KEY_TYPE_SWIFT;
+    }else if(strcmp(argv[loop],"-s3") == 0){
+      ERR_CHECK_NEXT_PARAM(creds);
+    }else if(strcmp(argv[loop],"-swift") == 0){
+      ERR_CHECK_NEXT_PARAM(creds);
+    }else if(strcmp(argv[loop],"-p") == 0){
+      ERR_CHECK_NEXT_PARAM(port);
+    }else return -1;
+  }
+  if(host.length() <= 0 ||
+     creds.length() <= 0)
+    return -1;
+  return 0;
+}
+
+void test_cors_helper::set_response(char *r){
+  string sr(r), h, v;
+  unsigned off = sr.find(": ");
+  if(off != string::npos){
+    h.assign(sr, 0, off);
+    v.assign(sr, off + 2, sr.find("\r\n") - (off+2));
+  }else{
+    /*Could be the status code*/
+    if(sr.find("HTTP/") != string::npos){
+      h.assign(HTTP_RESPONSE_STR);
+      off = sr.find(" ");
+      v.assign(sr, off + 1, sr.find("\r\n") - (off + 1));
+      resp_code = atoi((v.substr(0, 3)).c_str());
+    }
+  }
+  response[h] = v;
+}
+
+size_t write_header(void *ptr, size_t size, size_t nmemb, void *ud){
+  test_cors_helper *h = (test_cors_helper *)ud;
+  h->set_response((char *)ptr);
+  return size*nmemb;
+}
+
+size_t write_data(void *ptr, size_t size, size_t nmemb, void *ud){
+  test_cors_helper *h = (test_cors_helper *)ud;
+  h->set_response_data((char *)ptr, size*nmemb);
+  return size*nmemb;
+}
+static inline void buf_to_hex(const unsigned char *buf, int len, char *str)
+{
+  int i;
+  str[0] = '\0';
+  for (i = 0; i < len; i++) {
+    sprintf(&str[i*2], "%02x", (int)buf[i]);
+  }
+}
+
+static void calc_hmac_sha1(const char *key, int key_len,
+                    const char *msg, int msg_len, char *dest)
+/* destination should be CEPH_CRYPTO_HMACSHA1_DIGESTSIZE bytes long */
+{
+  ceph::crypto::HMACSHA1 hmac((const unsigned char *)key, key_len);
+  hmac.Update((const unsigned char *)msg, msg_len);
+  hmac.Final((unsigned char *)dest);
+  
+  char hex_str[(CEPH_CRYPTO_HMACSHA1_DIGESTSIZE * 2) + 1];
+  buf_to_hex((unsigned char *)dest, CEPH_CRYPTO_HMACSHA1_DIGESTSIZE, hex_str);
+}
+
+static int get_s3_auth(string method, string creds, string date, string res, string& out){
+  string aid, secret, auth_hdr;
+  unsigned off = creds.find(":");
+  out = "";
+  if(off != string::npos){
+    aid.assign(creds, 0, off);
+    secret.assign(creds, off + 1, string::npos);
+
+    /*sprintf(auth_hdr, "%s\n\n\n%s\n%s", req_type, date, res);*/
+    char hmac_sha1[CEPH_CRYPTO_HMACSHA1_DIGESTSIZE];
+    char b64[65]; /* 64 is really enough */
+    auth_hdr.append(method + string("\n\n\n") + date + string("\n") + res);
+    calc_hmac_sha1(secret.c_str(), secret.length(), auth_hdr.c_str(), auth_hdr.length(), hmac_sha1);
+    int ret = ceph_armor(b64, b64 + 64, hmac_sha1,
+                         hmac_sha1 + CEPH_CRYPTO_HMACSHA1_DIGESTSIZE);
+    if (ret < 0) {
+      cout << "ceph_armor failed\n";
+      return -1;
+    }
+    b64[ret] = 0;
+    out.append(aid + string(":") + b64);
+  }else return -1;
+  return 0;
+}
+
+void get_date(string& d){
+  struct timeval tv;
+  char date[64];
+  struct tm tm;
+  char *days[] = {(char *)"Sun", (char *)"Mon", (char *)"Tue",
+                  (char *)"Wed", (char *)"Thu", (char *)"Fri", 
+                  (char *)"Sat"};
+  char *months[] = {(char *)"Jan", (char *)"Feb", (char *)"Mar", 
+                    (char *)"Apr", (char *)"May", (char *)"Jun",
+                    (char *)"Jul",(char *) "Aug", (char *)"Sep", 
+                    (char *)"Oct", (char *)"Nov", (char *)"Dec"};
+  gettimeofday(&tv, NULL);
+  gmtime_r(&tv.tv_sec, &tm);
+  sprintf(date, "%s, %d %s %d %d:%d:%d GMT", 
+          days[tm.tm_wday], 
+          tm.tm_mday, months[tm.tm_mon], 
+          tm.tm_year + 1900,
+          tm.tm_hour, tm.tm_min, 0 /*tm.tm_sec*/);
+  d = date;
+}
+
+int test_cors_helper::send_request(string method, string res, 
+                                   size_t (*read_function)( void *,size_t,size_t,void *),
+                                   void *ud,
+                                   size_t length){
+  string url;
+  string auth, date;
+  url.append(string("http://") + host);
+  if(port.length() > 0)url.append(string(":") + port);
+  url.append(res);
+  curl_inst = curl_easy_init();
+  if(curl_inst){
+    curl_easy_setopt(curl_inst, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl_inst, CURLOPT_CUSTOMREQUEST, method.c_str());
+    curl_easy_setopt(curl_inst, CURLOPT_VERBOSE, CURL_VERBOSE);
+    curl_easy_setopt(curl_inst, CURLOPT_HEADERFUNCTION, write_header);
+    curl_easy_setopt(curl_inst, CURLOPT_WRITEHEADER, (void *)this);
+    curl_easy_setopt(curl_inst, CURLOPT_WRITEFUNCTION, write_data);
+    curl_easy_setopt(curl_inst, CURLOPT_WRITEDATA, (void *)this);
+    if(read_function){
+      curl_easy_setopt(curl_inst, CURLOPT_READFUNCTION, read_function);
+      curl_easy_setopt(curl_inst, CURLOPT_READDATA, (void *)ud);
+      curl_easy_setopt(curl_inst, CURLOPT_UPLOAD, 1L);
+      curl_easy_setopt(curl_inst, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
+    }
+
+    get_date(date);
+    string http_date;
+    http_date.append(string("Date: ") + date);
+    if(kt == KEY_TYPE_S3){
+      string s3auth;
+      if(get_s3_auth(method, creds, date, res, s3auth) < 0)return -1;
+      auth.append(string("Authorization: AWS ") + s3auth);
+    }else if(kt == KEY_TYPE_SWIFT){
+      auth.append(string("X-Auth-Token: ") + creds);
+    }
+
+    struct curl_slist *slist = NULL;
+    slist = curl_slist_append(slist, auth.c_str());
+    slist = curl_slist_append(slist, http_date.c_str());
+    for(list<string>::iterator it = extra_hdrs.begin();
+        it != extra_hdrs.end(); it++){
+      slist = curl_slist_append(slist, (*it).c_str());
+    }
+    if(read_function)
+      curl_slist_append(slist, "Expect:");
+    curl_easy_setopt(curl_inst, CURLOPT_HTTPHEADER, slist); 
+
+    response.erase(response.begin(), response.end());
+    extra_hdrs.erase(extra_hdrs.begin(), extra_hdrs.end());
+    CURLcode res = curl_easy_perform(curl_inst);
+    if(res != CURLE_OK){
+      cout << "Curl perform failed for " << url << ", res: " << 
+        curl_easy_strerror(res) << "\n";
+      return -1;
+    }
+    curl_slist_free_all(slist);
+  }
+  curl_easy_cleanup(curl_inst);
+  return 0;
+}
+
+test_cors_helper *g_test;
+Finisher *finisher;
+
+static int create_bucket(void){
+  if(g_test->get_key_type() == KEY_TYPE_S3){
+    g_test->send_request(string("PUT"), string("/"S3_BUCKET_NAME));
+    if(g_test->get_resp_code() != 200U){
+      cout << "Error creating bucket, http code " << g_test->get_resp_code();
+      return -1;
+    }
+  }else if(g_test->get_key_type() == KEY_TYPE_SWIFT){
+    g_test->send_request(string("PUT"), string("/swift/v1/"SWIFT_BUCKET_NAME));
+    if(g_test->get_resp_code() != 201U){
+      cout << "Error creating bucket, http code " << g_test->get_resp_code();
+      return -1;
+    }
+  }else return -1;
+  return 0;
+}
+
+static int delete_bucket(void){
+  if(g_test->get_key_type() == KEY_TYPE_S3){
+    g_test->send_request(string("DELETE"), string("/"S3_BUCKET_NAME));
+    if(g_test->get_resp_code() != 204U){
+      cout << "Error deleting bucket, http code " << g_test->get_resp_code();
+      return -1;
+    }
+  }else if(g_test->get_key_type() == KEY_TYPE_SWIFT){
+    g_test->send_request(string("DELETE"), string("/swift/v1/"SWIFT_BUCKET_NAME));
+    if(g_test->get_resp_code() != 204U){
+      cout << "Error deleting bucket, http code " << g_test->get_resp_code();
+      return -1;
+    }
+  }else return -1;
+  return 0;
+}
+
+RGWCORSRule *xml_to_cors_rule(string s){
+  RGWCORSConfiguration_S3 *cors_config;
+  RGWCORSXMLParser_S3 parser(g_ceph_context);
+  const string *data = g_test->get_response_data();
+  if (!parser.init()) {
+    return NULL;
+  }
+  if (!parser.parse(data->c_str(), data->length(), 1)) {
+    return NULL;
+  }
+  cors_config = (RGWCORSConfiguration_S3 *)parser.find_first("CORSConfiguration");
+  if (!cors_config) {
+    return NULL;
+  }
+  return cors_config->host_name_rule(s.c_str());
+}
+
+size_t cors_read_xml(void *ptr, size_t s, size_t n, void *ud){
+  stringstream *ss = (stringstream *)ud;
+  size_t len = ss->str().length();
+  if(s*n < len){
+    cout << "Cannot copy xml data, as len is not enough\n";
+    return 0;
+  }
+  memcpy(ptr, (void *)ss->str().c_str(), len);
+  return len;
+}
+
+void send_cors(set<string> o, set<string> h,
+               list<string> e, uint8_t flags, 
+               unsigned max_age){
+  if(g_test->get_key_type() == KEY_TYPE_S3){
+    RGWCORSRule rule(o, h, e, flags, max_age);
+    RGWCORSConfiguration config;
+    config.stack_rule(rule);
+    stringstream ss;
+    RGWCORSConfiguration_S3 *s3;
+    s3 = static_cast<RGWCORSConfiguration_S3 *>(&config);
+    s3->to_xml(ss);
+
+    g_test->send_request(string("PUT"), string("/"S3_BUCKET_NAME"?cors"), cors_read_xml, 
+                         (void *)&ss, ss.str().length());
+  }else if(g_test->get_key_type() == KEY_TYPE_SWIFT){
+    set<string>::iterator it;
+    string a_o;
+    for(it = o.begin(); it != o.end(); it++){
+      if(a_o.length() > 0)a_o.append(" ");
+      a_o.append(*it);
+    }
+    g_test->set_extra_header(string("X-Container-Meta-Access-Control-Allow-Origin: ") + a_o);
+
+    if(!h.empty()){
+      string a_h;
+      for(it = h.begin(); it != h.end(); it++){
+        if(a_h.length() > 0)a_h.append(" ");
+        a_h.append(*it);
+      }
+      g_test->set_extra_header(string("X-Container-Meta-Access-Control-Allow-Headers: ") + a_h);
+    }
+    if(!e.empty()){
+      list<string>::iterator lit;
+      string e_h;
+      for(lit = e.begin(); lit != e.end(); lit++){
+        if(e_h.length() > 0)e_h.append(" ");
+        e_h.append(*lit);
+      }
+      g_test->set_extra_header(string("X-Container-Meta-Access-Control-Expose-Headers: ") + e_h);
+    }
+    if(max_age != CORS_MAX_AGE_INVALID){
+      char age[32];
+      sprintf(age, "%d", max_age);
+      g_test->set_extra_header(string("X-Container-Meta-Access-Control-Max-Age: ") + string(age));
+    }
+    //const char *data = "1";
+    stringstream ss;
+    ss << "1";
+    g_test->send_request(string("POST"), string("/swift/v1/"SWIFT_BUCKET_NAME), cors_read_xml, 
+                         (void *)&ss, 1);
+  }
+}
+
+TEST(TestCORS, getcors_firsttime){
+  if(g_test->get_key_type() == KEY_TYPE_SWIFT)return;
+  ASSERT_EQ(0, create_bucket());
+  g_test->send_request(string("GET"), string("/"S3_BUCKET_NAME"?cors"));
+  EXPECT_EQ(404U, g_test->get_resp_code());
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, putcors_firsttime){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "example.com");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  /*Now get the CORS and check if its fine*/
+  if(g_test->get_key_type() == KEY_TYPE_S3){
+    g_test->send_request(string("GET"), string("/"S3_BUCKET_NAME"?cors"));
+    EXPECT_EQ(200U, g_test->get_resp_code());
+
+    RGWCORSRule *r = xml_to_cors_rule(string("example.com"));
+    EXPECT_TRUE(r != NULL);
+    if(!r)return;
+
+    EXPECT_TRUE((r->get_allowed_methods() & (RGW_CORS_GET | RGW_CORS_PUT))
+                == (RGW_CORS_GET | RGW_CORS_PUT));
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, putcors_invalid_hostname){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "*.example.*");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ((400U), g_test->get_resp_code());
+  origins.erase(origins.begin(), origins.end());
+  if((g_test->get_key_type() != KEY_TYPE_SWIFT)){
+    origins.insert(origins.end(), "");
+    send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+    EXPECT_EQ((400U), g_test->get_resp_code());
+    origins.erase(origins.begin(), origins.end());
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, putcors_invalid_headers){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "www.example.com");
+  h.insert(h.end(), "*-Header-*");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ((400U), g_test->get_resp_code());
+  h.erase(h.begin(), h.end());
+  if((g_test->get_key_type() != KEY_TYPE_SWIFT)){
+    h.insert(h.end(), "");
+    flags = RGW_CORS_GET | RGW_CORS_PUT;
+    send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+    EXPECT_EQ((400U), g_test->get_resp_code());
+    h.erase(h.begin(), h.end());
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_1){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "*.example.com");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: a.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: SomeHeader"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("a.example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0U, s.length());
+    s = g_test->get_response(string("Access-Control-Max-Age"));
+    EXPECT_EQ(0U, s.length());
+    s = g_test->get_response(string("Access-Control-Expose-Headers"));
+    EXPECT_EQ(0U, s.length());
+  }
+
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_2){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "*.example.com");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT | RGW_CORS_DELETE | RGW_CORS_HEAD;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: a.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: HEAD"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("a.example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("HEAD"));
+  }
+
+  g_test->set_extra_header(string("Origin: foo.bar.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: HEAD"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("foo.bar.example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("HEAD"));
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_3){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "*");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT | RGW_CORS_DELETE | RGW_CORS_HEAD;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  /*Check for HEAD in Access-Control-Allow-Methods*/
+  g_test->set_extra_header(string("Origin: a.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: HEAD"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("a.example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("HEAD"));
+  }
+
+  /*Check for DELETE in Access-Control-Allow-Methods*/
+  g_test->set_extra_header(string("Origin: foo.bar"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: DELETE"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("foo.bar"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("DELETE"));
+  }
+
+  /*Check for PUT in Access-Control-Allow-Methods*/
+  g_test->set_extra_header(string("Origin: foo.bar"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: PUT"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("foo.bar"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("PUT"));
+  }
+
+  /*Check for POST in Access-Control-Allow-Methods*/
+  g_test->set_extra_header(string("Origin: foo.bar"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: POST"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("foo.bar"));
+    if(g_test->get_key_type() == KEY_TYPE_S3){
+      s = g_test->get_response(string("Access-Control-Allow-Methods"));
+      EXPECT_EQ(0U, s.length());
+    }else{
+      s = g_test->get_response(string("Access-Control-Allow-Methods"));
+      EXPECT_EQ(0, s.compare("POST"));
+    }
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_4){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "example.com");
+  h.insert(h.end(), "Header1");
+  h.insert(h.end(), "Header2");
+  h.insert(h.end(), "*");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0U, s.length());
+  }
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header1"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header1"));
+  }
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header2"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header2"));
+  }
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header2, Header1"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header2,Header1"));
+  }
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header1, Header2"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header1,Header2"));
+  }
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header1, Header2, Header3"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header1,Header2,Header3"));
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_5){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "example.com");
+  e.insert(e.end(), "Expose1");
+  e.insert(e.end(), "Expose2");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Expose-Headers"));
+    EXPECT_EQ(0, s.compare("Expose1,Expose2"));
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_6){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+  unsigned err = (g_test->get_key_type() == KEY_TYPE_SWIFT)?401U:403U;
+
+  origins.insert(origins.end(), "http://www.example.com");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: http://example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: http://www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+
+  origins.erase(origins.begin(), origins.end());
+  origins.insert(origins.end(), "*.example.com");
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: .example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: http://example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: http://www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: https://www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+
+  origins.erase(origins.begin(), origins.end());
+  origins.insert(origins.end(), "https://example*.com");
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: https://example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: http://example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: www.example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(err, g_test->get_resp_code());
+
+  g_test->set_extra_header(string("Origin: https://example.a.b.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, optionscors_test_options_7){
+  ASSERT_EQ(0, create_bucket());
+  set<string> origins, h;
+  list<string> e;
+
+  origins.insert(origins.end(), "example.com");
+  h.insert(h.end(), "Header*");
+  h.insert(h.end(), "Hdr-*-Length");
+  h.insert(h.end(), "*-Length");
+  h.insert(h.end(), "foo*foo");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+  
+  g_test->set_extra_header(string("Origin: example.com"));
+  g_test->set_extra_header(string("Access-Control-Request-Method: GET"));
+  g_test->set_extra_header(string("Access-Control-Allow-Headers: Header1, Header2, Header3, "
+                                  "Hdr--Length, Hdr-1-Length, Header-Length, Content-Length, foofoofoo"));
+  g_test->send_request(string("OPTIONS"), BUCKET_URL);
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  if(g_test->get_resp_code() == 200){
+    string s = g_test->get_response(string("Access-Control-Allow-Origin"));
+    EXPECT_EQ(0, s.compare("example.com"));
+    s = g_test->get_response(string("Access-Control-Allow-Methods"));
+    EXPECT_EQ(0, s.compare("GET"));
+    s = g_test->get_response(string("Access-Control-Allow-Headers"));
+    EXPECT_EQ(0, s.compare("Header1,Header2,Header3,"
+                           "Hdr--Length,Hdr-1-Length,Header-Length,Content-Length,foofoofoo"));
+  }
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, deletecors_firsttime){
+  if(g_test->get_key_type() == KEY_TYPE_SWIFT)return;
+  ASSERT_EQ(0, create_bucket());
+  g_test->send_request("DELETE", "/"S3_BUCKET_NAME"?cors");
+  EXPECT_EQ(204U, g_test->get_resp_code());
+  ASSERT_EQ(0, delete_bucket());
+}
+
+TEST(TestCORS, deletecors_test){
+  set<string> origins, h;
+  list<string> e;
+  if(g_test->get_key_type() == KEY_TYPE_SWIFT)return;
+  ASSERT_EQ(0, create_bucket());
+  origins.insert(origins.end(), "example.com");
+  uint8_t flags = RGW_CORS_GET | RGW_CORS_PUT;
+  
+  send_cors(origins, h, e, flags, CORS_MAX_AGE_INVALID);
+  EXPECT_EQ(((g_test->get_key_type() == KEY_TYPE_SWIFT)?202U:200U), g_test->get_resp_code());
+
+  g_test->send_request("GET", "/"S3_BUCKET_NAME"?cors");
+  EXPECT_EQ(200U, g_test->get_resp_code());
+  g_test->send_request("DELETE", "/"S3_BUCKET_NAME"?cors");
+  EXPECT_EQ(204U, g_test->get_resp_code());
+  g_test->send_request("GET", "/"S3_BUCKET_NAME"?cors");
+  EXPECT_EQ(404U, g_test->get_resp_code());
+  ASSERT_EQ(0, delete_bucket());
+}
+
+int main(int argc, char *argv[]){
+  vector<const char*> args;
+  argv_to_vec(argc, (const char **)argv, args);
+
+  global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0);
+  common_init_finish(g_ceph_context);
+  g_test = new test_cors_helper();
+  finisher = new Finisher(g_ceph_context);
+#ifdef GTEST
+  ::testing::InitGoogleTest(&argc, argv);
+#endif
+  finisher->start();
+
+  if(g_test->extract_input(argc, argv) < 0){
+    print_usage(argv[0]);
+    return -1;
+  }
+#ifdef GTEST
+  int r = RUN_ALL_TESTS();
+  if (r >= 0) {
+    cout << "There are failures in the test case\n";
+    return -1;
+  }
+#endif
+  finisher->stop();
+  return 0;
+}
+