From f165049cbafd54c70d17777527e85b6fe26b1df2 Mon Sep 17 00:00:00 2001 From: Babu Shanmugam Date: Tue, 5 Mar 2013 09:22:55 +0530 Subject: [PATCH] rgw: with CORS support 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() --- src/Makefile.am | 10 +- src/common/str_list.cc | 23 +- src/rgw/rgw_common.cc | 7 +- src/rgw/rgw_common.h | 5 + src/rgw/rgw_cors.cc | 144 ++++++ src/rgw/rgw_cors.h | 132 ++++++ src/rgw/rgw_cors_s3.cc | 231 ++++++++++ src/rgw/rgw_cors_s3.h | 58 +++ src/rgw/rgw_cors_swift.h | 77 ++++ src/rgw/rgw_html_errors.h | 1 + src/rgw/rgw_main.cc | 8 +- src/rgw/rgw_op.cc | 234 +++++++++- src/rgw/rgw_op.h | 79 +++- src/rgw/rgw_rest.cc | 47 ++ src/rgw/rgw_rest.h | 30 +- src/rgw/rgw_rest_s3.cc | 79 +++- src/rgw/rgw_rest_s3.h | 41 +- src/rgw/rgw_rest_swift.cc | 53 +++ src/rgw/rgw_rest_swift.h | 10 + src/rgw/rgw_xml.cc | 5 +- src/test/test_cors.cc | 906 ++++++++++++++++++++++++++++++++++++++ 21 files changed, 2155 insertions(+), 25 deletions(-) create mode 100644 src/rgw/rgw_cors.cc create mode 100644 src/rgw/rgw_cors.h create mode 100644 src/rgw/rgw_cors_s3.cc create mode 100644 src/rgw/rgw_cors_s3.h create mode 100644 src/rgw/rgw_cors_swift.h create mode 100644 src/test/test_cors.cc diff --git a/src/Makefile.am b/src/Makefile.am index b27c8bb6b7029..6d198a1b3a98b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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} diff --git a/src/common/str_list.cc b/src/common/str_list.cc index cae2b22015d4d..a9867a6efdd5d 100644 --- a/src/common/str_list.cc +++ b/src/common/str_list.cc @@ -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& { size_t pos = 0; string token; - + str_list.clear(); while (pos < str.size()) { diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 65ff2fe99edb5..ad9cb1ad91702 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -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) || diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index bb203ef72bb23..2abed2f9f9166 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -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 index 0000000000000..85aa48f998a32 --- /dev/null +++ b/src/rgw/rgw_cors.cc @@ -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 + * + * 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 + +#include +#include + +#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::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::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& 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::iterator it = s.begin(); + it != s.end(); it++){ + unsigned off; + if((off = (*it).find("*"))!=string::npos){ + list 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::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::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::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::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 index 0000000000000..372229a79da7a --- /dev/null +++ b/src/rgw/rgw_cors.h @@ -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 + * + * 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 +#include +#include +#include + +#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 allowed_hdrs; + std::set allowed_origins; + std::list exposable_hdrs; + +public: + RGWCORSRule() : max_age(CORS_MAX_AGE_INVALID),allowed_methods(0) {} + RGWCORSRule(std::set& o, std::set& h, + std::list& 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 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& get_rules(){ + return rules; + } + bool is_empty(){ + return rules.empty(); + } + void get_origins_list(const char *origin, std::list& 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 index 0000000000000..d3d6134bb30fa --- /dev/null +++ b/src/rgw/rgw_cors_s3.cc @@ -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 + * + * 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 + +#include +#include + +#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::iterator it = allowed_origins.begin(); + it != allowed_origins.end(); + it++){ + string host = *it; + f.dump_string("AllowedOrigin", host); + } + /*AllowedHeader*/ + for(set::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::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::iterator it = rules.begin(); + it != rules.end(); it++){ + (static_cast(*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 index 0000000000000..32e5dcdd8d04e --- /dev/null +++ b/src/rgw/rgw_cors_s3.h @@ -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 + * + * 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 +#include +#include +#include + +#include +#include +#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 index 0000000000000..5d1a203a9ce61 --- /dev/null +++ b/src/rgw/rgw_cors_swift.h @@ -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 + * + * 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 +#include +#include +#include +#include +#include + +#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 o, h, oc; + list 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::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::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*/ diff --git a/src/rgw/rgw_html_errors.h b/src/rgw/rgw_html_errors.h index 254af46e66343..cc586c1406b4f 100644 --- a/src/rgw/rgw_html_errors.h +++ b/src/rgw/rgw_html_errors.h @@ -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" }, diff --git a/src/rgw/rgw_main.cc b/src/rgw/rgw_main.cc index 3d4459cb576be..cadc51e7b3f1c 100644 --- a/src/rgw/rgw_main.cc +++ b/src/rgw/rgw_main.cc @@ -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) { diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index ecceb06c5fb44..bff0e3573cbb5 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -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 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 attrs, orig_attrs, rmattrs; map::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(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 orig_attrs, attrs, rmattrs; + map::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 hl; + get_str_list(req_hdrs, hl); + for(list::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(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; } diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 8f1252cf2dc76..7272090a96d37 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -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 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); diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index 8b1c2c5e32572..a02ab83c1c92d 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -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; } diff --git a/src/rgw/rgw_rest.h b/src/rgw/rgw_rest.h index 7dd61bdb1e5d1..6c24f8b2fb0b9 100644 --- a/src/rgw/rgw_rest.h +++ b/src/rgw/rgw_rest.h @@ -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 diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 649f0b46a8407..0a3198829457b 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -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? */ diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index cc900507c1238..277ebf1ffb856 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -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() {} diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc index 31f92af1de277..4ce95125dd798 100644 --- a/src/rgw/rgw_rest_swift.cc +++ b/src/rgw/rgw_rest_swift.cc @@ -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) { diff --git a/src/rgw/rgw_rest_swift.h b/src/rgw/rgw_rest_swift.h index 1704823581b3d..420d1d8687345 100644 --- a/src/rgw/rgw_rest_swift.h +++ b/src/rgw/rgw_rest_swift.h @@ -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() {} diff --git a/src/rgw/rgw_xml.cc b/src/rgw/rgw_xml.cc index adf3ccb4ab07d..41e1f8ca63e1c 100644 --- a/src/rgw/rgw_xml.cc +++ b/src/rgw/rgw_xml.cc @@ -108,7 +108,10 @@ find(string name) map::iterator first; map::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 index 0000000000000..719dcb81656e0 --- /dev/null +++ b/src/test/test_cors.cc @@ -0,0 +1,906 @@ +#include +#include +#include +#include +extern "C"{ +#include +} +#include "common/ceph_crypto.h" +#include +#include +#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 +#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 << " \n"; + cout << "Options:\n" + "-g - The ip address of the gateway\n" + "-p - The port number of the gateway\n" + "-k - The key type, either SWIFT or S3\n" + "-s3 - Only, if the key type is S3, gives S3 credentials\n" + "-swift - 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 response; + list 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::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 o, set h, + list 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(&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::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::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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 origins, h; + list 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 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; +} + -- 2.39.5