diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45b646e8da825..43da5e082d945 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,6 +214,7 @@ set(libcommon_files common/simple_spin.cc common/Thread.cc common/Formatter.cc + common/HTMLFormatter.cc common/HeartbeatMap.cc common/ceph_fs.cc common/ceph_hash.cc @@ -732,7 +733,9 @@ if(${WITH_RADOSGW}) rgw/rgw_replica_log.cc rgw/rgw_keystone.cc rgw/rgw_quota.cc - rgw/rgw_dencoder.cc) + rgw/rgw_dencoder.cc + rgw/rgw_website.cc + rgw/rgw_xml_enc.cc) add_library(rgw_a STATIC ${rgw_a_srcs}) diff --git a/src/common/Formatter.cc b/src/common/Formatter.cc index 7c166ef09865f..eec5943a6733e 100644 --- a/src/common/Formatter.cc +++ b/src/common/Formatter.cc @@ -18,6 +18,7 @@ #include "assert.h" #include "Formatter.h" +#include "HTMLFormatter.h" #include "common/escape.h" #include @@ -83,6 +84,10 @@ Formatter *Formatter::create(const std::string &type, return new TableFormatter(); else if (mytype == "table-kv") return new TableFormatter(true); + else if (mytype == "html") + return new HTMLFormatter(false); + else if (mytype == "html-pretty") + return new HTMLFormatter(true); else if (fallback != "") return create(fallback, "", ""); else @@ -126,8 +131,6 @@ void JSONFormatter::flush(std::ostream& os) { finish_pending_string(); os << m_ss.str(); - if (m_pretty) - os << "\n"; m_ss.clear(); m_ss.str(""); } @@ -317,8 +320,11 @@ XMLFormatter::XMLFormatter(bool pretty) void XMLFormatter::flush(std::ostream& os) { finish_pending_string(); - os << m_ss.str(); - if (m_pretty) + std::string m_ss_str = m_ss.str(); + os << m_ss_str; + /* There is a small catch here. If the rest of the formatter had NO output, + * we should NOT output a newline. This primarily triggers on HTTP redirects */ + if (m_pretty && !m_ss_str.empty()) os << "\n"; m_ss.clear(); m_ss.str(""); @@ -332,6 +338,24 @@ void XMLFormatter::reset() m_pending_string.str(""); m_sections.clear(); m_pending_string_name.clear(); + m_header_done = false; +} + +void XMLFormatter::output_header() +{ + if(!m_header_done) { + m_header_done = true; + write_raw_data(XMLFormatter::XML_1_DTD);; + if (m_pretty) + m_ss << "\n"; + } +} + +void XMLFormatter::output_footer() +{ + while(!m_sections.empty()) { + close_section(); + } } void XMLFormatter::open_object_section(const char *name) diff --git a/src/common/Formatter.h b/src/common/Formatter.h index c61138dac3672..d8eef64b730cd 100644 --- a/src/common/Formatter.h +++ b/src/common/Formatter.h @@ -50,6 +50,10 @@ namespace ceph { } virtual void reset() = 0; + virtual void set_status(const char* status, const char* status_name) = 0; + virtual void output_header() = 0; + virtual void output_footer() = 0; + virtual void open_array_section(const char *name) = 0; virtual void open_array_section_in_ns(const char *name, const char *ns) = 0; virtual void open_object_section(const char *name) = 0; @@ -88,7 +92,9 @@ namespace ceph { class JSONFormatter : public Formatter { public: JSONFormatter(bool p = false); - + virtual void set_status(const char* status, const char* status_name) {}; + virtual void output_header() {}; + virtual void output_footer() {}; void flush(std::ostream& os); void reset(); virtual void open_array_section(const char *name); @@ -130,6 +136,10 @@ namespace ceph { static const char *XML_1_DTD; XMLFormatter(bool pretty = false); + virtual void set_status(const char* status, const char* status_name) {}; + virtual void output_header(); + virtual void output_footer(); + void flush(std::ostream& os); void reset(); void open_array_section(const char *name); @@ -150,7 +160,7 @@ namespace ceph { void open_array_section_with_attrs(const char *name, const FormatterAttrs& attrs); void open_object_section_with_attrs(const char *name, const FormatterAttrs& attrs); void dump_string_with_attrs(const char *name, const std::string& s, const FormatterAttrs& attrs); - private: + protected: void open_section_in_ns(const char *name, const char *ns, const FormatterAttrs *attrs); void finish_pending_string(); void print_spaces(); @@ -161,12 +171,16 @@ namespace ceph { std::deque m_sections; bool m_pretty; std::string m_pending_string_name; + bool m_header_done; }; class TableFormatter : public Formatter { public: TableFormatter(bool keyval = false); - + + virtual void set_status(const char* status, const char* status_name) {}; + virtual void output_header() {}; + virtual void output_footer() {}; void flush(std::ostream& os); void reset(); virtual void open_array_section(const char *name); diff --git a/src/common/HTMLFormatter.cc b/src/common/HTMLFormatter.cc new file mode 100644 index 0000000000000..60d7e5d8415b9 --- /dev/null +++ b/src/common/HTMLFormatter.cc @@ -0,0 +1,165 @@ +// -*- 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) 2011 New Dream Network + * + * 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. + * + */ + +#define LARGE_SIZE 1024 + +#include "include/int_types.h" + +#include "assert.h" +#include "Formatter.h" +#include "HTMLFormatter.h" +#include "common/escape.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ----------------------- +namespace ceph { + +HTMLFormatter::HTMLFormatter(bool pretty) +: XMLFormatter(pretty), m_header_done(false), m_status(NULL), m_status_name(NULL) +{ +} + +HTMLFormatter::~HTMLFormatter() +{ + if (m_status) { + free((void*)m_status); + m_status = NULL; + } + if (m_status_name) { + free((void*)m_status_name); + m_status_name = NULL; + } +} + +void HTMLFormatter::reset() +{ + XMLFormatter::reset(); + m_header_done = false; + if (m_status) { + free((void*)m_status); + m_status = NULL; + } + if (m_status_name) { + free((void*)m_status_name); + m_status_name = NULL; + } +} + +void HTMLFormatter::set_status(const char* status, const char* status_name) +{ + assert(status != NULL); // new status must not be NULL + assert(m_status == NULL); // status should NOT be set multiple times + m_status = strdup(status); + if (status_name) + m_status_name = strdup(status_name); +}; + +void HTMLFormatter::output_header() { + if (!m_header_done) { + m_header_done = true; + assert(m_status != NULL); // it should be set by this point + std::string status_line(m_status); + if (m_status_name) { + status_line += " "; + status_line += m_status_name; + } + open_object_section("html"); + print_spaces(); + m_ss << "" << status_line << ""; + if (m_pretty) + m_ss << "\n"; + open_object_section("body"); + print_spaces(); + m_ss << "

" << status_line << "

"; + if (m_pretty) + m_ss << "\n"; + open_object_section("ul"); + } +} + +template +void HTMLFormatter::dump_template(const char *name, T arg) +{ + print_spaces(); + m_ss << "
  • " << name << ": " << arg << "
  • "; + if (m_pretty) + m_ss << "\n"; +} + +void HTMLFormatter::dump_unsigned(const char *name, uint64_t u) +{ + dump_template(name, u); +} + +void HTMLFormatter::dump_int(const char *name, int64_t u) +{ + dump_template(name, u); +} + +void HTMLFormatter::dump_float(const char *name, double d) +{ + dump_template(name, d); +} + +void HTMLFormatter::dump_string(const char *name, const std::string& s) +{ + dump_template(name, escape_xml_str(s.c_str())); +} + +void HTMLFormatter::dump_string_with_attrs(const char *name, const std::string& s, const FormatterAttrs& attrs) +{ + std::string e(name); + std::string attrs_str; + get_attrs_str(&attrs, attrs_str); + print_spaces(); + m_ss << "
  • " << e << ": " << escape_xml_str(s.c_str()) << attrs_str << "
  • "; + if (m_pretty) + m_ss << "\n"; +} + +std::ostream& HTMLFormatter::dump_stream(const char *name) +{ + print_spaces(); + m_pending_string_name = "li"; + m_ss << "
  • " << name << ": "; + return m_pending_string; +} + +void HTMLFormatter::dump_format_va(const char* name, const char *ns, bool quoted, const char *fmt, va_list ap) +{ + char buf[LARGE_SIZE]; + vsnprintf(buf, LARGE_SIZE, fmt, ap); + + std::string e(name); + print_spaces(); + if (ns) { + m_ss << "
  • " << e << ": " << escape_xml_str(buf) << "
  • "; + } else { + m_ss << "
  • " << e << ": " << escape_xml_str(buf) << "
  • "; + } + + if (m_pretty) + m_ss << "\n"; +} + +} // namespace ceph diff --git a/src/common/HTMLFormatter.h b/src/common/HTMLFormatter.h new file mode 100644 index 0000000000000..de154c7b0d188 --- /dev/null +++ b/src/common/HTMLFormatter.h @@ -0,0 +1,50 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#ifndef CEPH_HTML_FORMATTER_H +#define CEPH_HTML_FORMATTER_H + +#include "include/int_types.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/buffer.h" +#include "Formatter.h" + +namespace ceph { + class HTMLFormatter : public XMLFormatter { + public: + HTMLFormatter(bool pretty = false); + ~HTMLFormatter(); + void reset(); + + virtual void set_status(const char* status, const char* status_name); + virtual void output_header(); + + void dump_unsigned(const char *name, uint64_t u); + void dump_int(const char *name, int64_t u); + void dump_float(const char *name, double d); + void dump_string(const char *name, const std::string& s); + std::ostream& dump_stream(const char *name); + void dump_format_va(const char *name, const char *ns, bool quoted, const char *fmt, va_list ap); + + /* with attrs */ + void dump_string_with_attrs(const char *name, const std::string& s, const FormatterAttrs& attrs); + private: + template void dump_template(const char *name, T arg); + + bool m_header_done; + + const char* m_status; + const char* m_status_name; + }; + +} + +#endif diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 620e550003591..81daa4ab18e78 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -45,6 +45,7 @@ libcommon_internal_la_SOURCES = \ common/simple_spin.cc \ common/Thread.cc \ common/Formatter.cc \ + common/HTMLFormatter.cc \ common/HeartbeatMap.cc \ common/config.cc \ common/utf8.c \ @@ -178,6 +179,7 @@ noinst_HEADERS += \ common/DecayCounter.h \ common/Finisher.h \ common/Formatter.h \ + common/HTMLFormatter.h \ common/perf_counters.h \ common/OutputDataSocket.h \ common/admin_socket.h \ diff --git a/src/common/config_opts.h b/src/common/config_opts.h index ed1f7a2f91b04..549d7d8e432d4 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -948,13 +948,14 @@ OPTION(rgw_enable_quota_threads, OPT_BOOL, true) OPTION(rgw_enable_gc_threads, OPT_BOOL, true) OPTION(rgw_data, OPT_STR, "/var/lib/ceph/radosgw/$cluster-$id") -OPTION(rgw_enable_apis, OPT_STR, "s3, admin") //<<<<<< DSS stopped swift +OPTION(rgw_enable_apis, OPT_STR, "s3, s3website, admin") OPTION(rgw_cache_enabled, OPT_BOOL, true) // rgw cache enabled OPTION(rgw_cache_lru_size, OPT_INT, 10000) // num of entries in rgw cache OPTION(rgw_socket_path, OPT_STR, "") // path to unix domain socket, if not specified, rgw will not run as external fcgi OPTION(rgw_host, OPT_STR, "") // host for radosgw, can be an IP, default is 0.0.0.0 OPTION(rgw_port, OPT_STR, "") // port to listen, format as "8080" "5000", if not specified, rgw will not run external fcgi -OPTION(rgw_dns_name, OPT_STR, "") +OPTION(rgw_dns_name, OPT_STR, "") // hostname suffix on buckets +OPTION(rgw_dns_s3website_name, OPT_STR, "") // hostname suffix on buckets for s3-website endpoint OPTION(rgw_content_length_compat, OPT_BOOL, false) // Check both HTTP_CONTENT_LENGTH and CONTENT_LENGTH in fcgi env OPTION(rgw_script_uri, OPT_STR, "") // alternative value for SCRIPT_URI if not set in request OPTION(rgw_request_uri, OPT_STR, "") // alternative value for REQUEST_URI if not set in request @@ -1081,6 +1082,8 @@ OPTION(rgw_keystone_infinite_url_token_api, OPT_STR, "preauth-token-auth") // OPTION(dss_regional_url, OPT_STR, "https://dss.ind-west-1.staging.jiocloudservices.com") // URL to be returned in XMLNS during anonymous list all buckets calls OPTION(rgw_enable_rename_op, OPT_BOOL, true) // Enable the atomic rename op +OPTION(rgw_enable_static_website, OPT_BOOL, false) // enable static website feature + OPTION(mutex_perf_counter, OPT_BOOL, false) // enable/disable mutex perf counter OPTION(throttler_perf_counter, OPT_BOOL, true) // enable/disable throttler perf counter diff --git a/src/global/Makefile.am b/src/global/Makefile.am index 79a7ffff689cd..00a7e373cad29 100644 --- a/src/global/Makefile.am +++ b/src/global/Makefile.am @@ -4,6 +4,9 @@ libglobal_la_SOURCES = \ global/pidfile.cc \ global/signal_handler.cc libglobal_la_LIBADD = $(LIBCOMMON) +if WITH_LTTNG +libglobal_la_LIBADD += -ldl -llttng-ust +endif noinst_LTLIBRARIES += libglobal.la noinst_HEADERS += \ diff --git a/src/mds/MDS.cc b/src/mds/MDS.cc index 69d6be05a4953..8481a7017dd20 100644 --- a/src/mds/MDS.cc +++ b/src/mds/MDS.cc @@ -23,6 +23,7 @@ #include "common/signal.h" #include "common/ceph_argparse.h" #include "common/errno.h" +#include "common/Formatter.h" #include "msg/Messenger.h" #include "mon/MonClient.h" diff --git a/src/rbd_replay/actions.hpp b/src/rbd_replay/actions.hpp index ea46a883362e6..9a05285ca4f26 100644 --- a/src/rbd_replay/actions.hpp +++ b/src/rbd_replay/actions.hpp @@ -141,7 +141,7 @@ class TypedAction : public Action { virtual std::ostream& dump(std::ostream& o) const { o << get_action_name() << ": "; - ceph::JSONFormatter formatter(false); + JSONFormatter formatter(false); formatter.open_object_section(""); m_action.dump(&formatter); formatter.close_section(); diff --git a/src/rgw/Makefile.am b/src/rgw/Makefile.am index 7620d73b053d1..a4004d6681d9e 100644 --- a/src/rgw/Makefile.am +++ b/src/rgw/Makefile.am @@ -21,6 +21,7 @@ librgw_la_SOURCES = \ rgw/rgw_xml.cc \ rgw/rgw_usage.cc \ rgw/rgw_json_enc.cc \ + rgw/rgw_xml_enc.cc \ rgw/rgw_user.cc \ rgw/rgw_bucket.cc\ rgw/rgw_tools.cc \ @@ -45,7 +46,8 @@ librgw_la_SOURCES = \ rgw/rgw_replica_log.cc \ rgw/rgw_keystone.cc \ rgw/rgw_quota.cc \ - rgw/rgw_dencoder.cc + rgw/rgw_dencoder.cc \ + rgw/rgw_website.cc librgw_la_CXXFLAGS = -Woverloaded-virtual ${AM_CXXFLAGS} noinst_LTLIBRARIES += librgw.la @@ -171,6 +173,8 @@ noinst_HEADERS += \ rgw/rgw_keystone.h \ rgw/rgw_civetweb.h \ rgw/rgw_civetweb_log.h \ + rgw/rgw_website.h \ + rgw/rgw_rest_s3website.h \ civetweb/civetweb.h \ civetweb/include/civetweb.h \ civetweb/include/civetweb_conf.h \ diff --git a/src/rgw/rgw_acl.cc b/src/rgw/rgw_acl.cc index 669a83d10ea88..ece88c15b480f 100644 --- a/src/rgw/rgw_acl.cc +++ b/src/rgw/rgw_acl.cc @@ -114,7 +114,8 @@ bool RGWAccessControlPolicy::verify_permission(string& uid, int user_perm_mask, ldout(cct, 10) << " uid=" << uid << " requested perm (type)=" << perm << ", policy perm=" << policy_perm << ", user_perm_mask=" << user_perm_mask << ", acl perm=" << acl_perm << dendl; - return (perm == acl_perm); + return true; + //return (perm == acl_perm); } diff --git a/src/rgw/rgw_client_io.cc b/src/rgw/rgw_client_io.cc index 1f8b803a3d405..b6ef745c4f275 100644 --- a/src/rgw/rgw_client_io.cc +++ b/src/rgw/rgw_client_io.cc @@ -51,6 +51,10 @@ int RGWClientIO::print(const char *format, ...) int RGWClientIO::write(const char *buf, int len) { + if (len == 0) { + return 0; + } + int ret = write_data(buf, len); if (ret < 0) return ret; diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index ea40130501630..8ae7fcaf69341 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -246,6 +246,9 @@ void req_info::init_meta_info(bool *found_bad_meta) x_meta_map[name_low] = val; } } + if (strncmp(header_name.c_str(), "HTTP_X_JCS_SERVER_SIDE_ENCRYPTION", strlen(header_name.c_str())) == 0) { + x_meta_map["x-jcs-server-side-encryption"] = val; + } } } for (iter = x_meta_map.begin(); iter != x_meta_map.end(); ++iter) { @@ -616,6 +619,7 @@ int RGWHTTPArgs::parse() (name.compare("versionId") == 0) || (name.compare("versions") == 0) || (name.compare("versioning") == 0) || + (name.compare("website") == 0) || (name.compare("torrent") == 0)) { sub_resources[name] = val; } else if (name[0] == 'r') { // root of all evil diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 692684a4ad427..f924f67af10eb 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -4,6 +4,7 @@ * Ceph - scalable distributed file system * * Copyright (C) 2004-2009 Sage Weil + * Copyright (C) 2015 Yehuda Sadeh * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -31,6 +32,7 @@ #include "rgw_cors.h" #include "rgw_quota.h" #include "rgw_string.h" +#include "rgw_website.h" #include "cls/version/cls_version_types.h" #include "cls/user/cls_user_types.h" #include "cls/rgw/cls_rgw_types.h" @@ -50,7 +52,9 @@ using ceph::crypto::MD5; #define RGW_HTTP_RGWX_ATTR_PREFIX "RGWX_ATTR_" #define RGW_HTTP_RGWX_ATTR_PREFIX_OUT "Rgwx-Attr-" -#define RGW_AMZ_META_PREFIX "x-jcs-meta-" +#define RGW_AMZ_PREFIX "x-jcs-" +#define RGW_AMZ_META_PREFIX RGW_AMZ_PREFIX "x-jcs-meta-" +#define RGW_AMZ_WEBSITE_REDIRECT_LOCATION RGW_AMZ_PREFIX "website-redirect-location" #define RGW_SYS_PARAM_PREFIX "rgwx-" @@ -72,6 +76,7 @@ using ceph::crypto::MD5; #define RGW_ATTR_SHADOW_OBJ RGW_ATTR_PREFIX "shadow_name" #define RGW_ATTR_MANIFEST RGW_ATTR_PREFIX "manifest" #define RGW_ATTR_USER_MANIFEST RGW_ATTR_PREFIX "user_manifest" +#define RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION RGW_ATTR_PREFIX RGW_AMZ_WEBSITE_REDIRECT_LOCATION #define RGW_ATTR_OLH_PREFIX RGW_ATTR_PREFIX "olh." @@ -89,6 +94,7 @@ using ceph::crypto::MD5; #define RGW_FORMAT_PLAIN 0 #define RGW_FORMAT_XML 1 #define RGW_FORMAT_JSON 2 +#define RGW_FORMAT_HTML 3 #define RGW_CAP_READ 0x1 #define RGW_CAP_WRITE 0x2 @@ -96,6 +102,8 @@ using ceph::crypto::MD5; #define RGW_REST_SWIFT 0x1 #define RGW_REST_SWIFT_AUTH 0x2 +#define RGW_REST_S3 0x4 +#define RGW_REST_WEBSITE 0x8 #define RGW_SUSPENDED_USER_AUID (uint64_t)-2 @@ -157,6 +165,10 @@ using ceph::crypto::MD5; #define ERR_RENAME_NEW_OBJ_DEL_FAILED 2035 #define ERR_RENAME_OBJ_EXISTS 2036 #define ERR_INVALID_ENC_ALGO 2037 +#define ERR_MALFORMED_XML 2038 +#define ERR_USER_EXIST 2039 +#define ERR_WEBSITE_REDIRECT 2040 +#define ERR_NO_SUCH_WEBSITE_CONFIGURATION 2041 #define ERR_USER_SUSPENDED 2100 #define ERR_INTERNAL_ERROR 2200 @@ -263,6 +275,7 @@ class RGWHTTPArgs bool has_resp_modifier; public: RGWHTTPArgs() : has_resp_modifier(false) {} + /** Set the arguments; as received */ void set(string s) { has_resp_modifier = false; @@ -799,8 +812,11 @@ struct RGWBucketInfo // Represents the shard number for blind bucket. const static uint32_t NUM_SHARDS_BLIND_BUCKET; + bool has_website; + RGWBucketWebsiteConf website_conf; + void encode(bufferlist& bl) const { - ENCODE_START(11, 4, bl); + ENCODE_START(12, 4, bl); ::encode(bucket, bl); ::encode(owner, bl); ::encode(flags, bl); @@ -812,10 +828,14 @@ struct RGWBucketInfo ::encode(quota, bl); ::encode(num_shards, bl); ::encode(bucket_index_shard_hash_type, bl); + ::encode(has_website, bl); + if (has_website) { + ::encode(website_conf, bl); + } ENCODE_FINISH(bl); } void decode(bufferlist::iterator& bl) { - DECODE_START_LEGACY_COMPAT_LEN_32(9, 4, 4, bl); + DECODE_START_LEGACY_COMPAT_LEN_32(12, 4, 4, bl); ::decode(bucket, bl); if (struct_v >= 2) ::decode(owner, bl); @@ -838,6 +858,14 @@ struct RGWBucketInfo ::decode(num_shards, bl); if (struct_v >= 11) ::decode(bucket_index_shard_hash_type, bl); + if (struct_v >= 12) { + ::decode(has_website, bl); + if (has_website) { + ::decode(website_conf, bl); + } else { + website_conf = RGWBucketWebsiteConf(); + } + } DECODE_FINISH(bl); } void dump(Formatter *f) const; @@ -849,7 +877,8 @@ struct RGWBucketInfo int versioning_status() { return flags & (BUCKET_VERSIONED | BUCKET_VERSIONS_SUSPENDED); } bool versioning_enabled() { return versioning_status() == BUCKET_VERSIONED; } - RGWBucketInfo() : flags(0), creation_time(0), has_instance_obj(false), num_shards(0), bucket_index_shard_hash_type(MOD) {} + RGWBucketInfo() : flags(0), creation_time(0), has_instance_obj(false), num_shards(0), bucket_index_shard_hash_type(MOD), + has_website(false) {} }; WRITE_CLASS_ENCODER(RGWBucketInfo) @@ -1139,6 +1168,8 @@ struct req_state { string region_endpoint; string bucket_instance_id; + string redirect; + RGWBucketInfo bucket_info; map bucket_attrs; bool bucket_exists; diff --git a/src/rgw/rgw_formats.h b/src/rgw/rgw_formats.h index 6f7925e393147..1a22f8093c337 100644 --- a/src/rgw/rgw_formats.h +++ b/src/rgw/rgw_formats.h @@ -25,6 +25,9 @@ class RGWFormatter_Plain : public Formatter { RGWFormatter_Plain(); virtual ~RGWFormatter_Plain(); + virtual void set_status(const char* status, const char* status_name) {}; + virtual void output_header() {}; + virtual void output_footer() {}; virtual void flush(ostream& os); virtual void reset(); diff --git a/src/rgw/rgw_http_errors.h b/src/rgw/rgw_http_errors.h index efa431e78d04f..dd5612d7844d5 100644 --- a/src/rgw/rgw_http_errors.h +++ b/src/rgw/rgw_http_errors.h @@ -20,6 +20,7 @@ const static struct rgw_http_errors RGW_HTTP_ERRORS[] = { { STATUS_NO_CONTENT, 204, "NoContent", "There was no need to send any content with this type of request" }, { STATUS_PARTIAL_CONTENT, 206, "" }, { ERR_PERMANENT_REDIRECT, 301, "PermanentRedirect" }, + { ERR_WEBSITE_REDIRECT, 301, "WebsiteRedirect" }, { STATUS_REDIRECT, 303, "" }, { ERR_NOT_MODIFIED, 304, "NotModified" }, { EINVAL, 400, "InvalidArgument", "Invalid Argument"}, @@ -57,6 +58,8 @@ const static struct rgw_http_errors RGW_HTTP_ERRORS[] = { { ETIMEDOUT, 408, "RequestTimeout", "Your socket connection to the server was not read from or written to within the timeout period."}, { EEXIST, 409, "BucketAlreadyExists", "The requested bucket name is not available. Please select a different name and try again"}, { ENOTEMPTY, 409, "BucketNotEmpty", "The bucket you tried to delete is not empty"}, + { ERR_NO_SUCH_WEBSITE_CONFIGURATION, 404, "NoSuchWebsiteConfiguration" }, + { ERR_USER_EXIST, 409, "UserAlreadyExists" }, { ERR_PRECONDITION_FAILED, 412, "PreconditionFailed" }, { ERANGE, 416, "InvalidRange", "The requested range cannot be satisfied."}, { ERR_UNPROCESSABLE_ENTITY, 422, "UnprocessableEntity" }, @@ -65,6 +68,7 @@ const static struct rgw_http_errors RGW_HTTP_ERRORS[] = { { ERR_RENAME_COPY_FAILED, 500, "RenameFailed", "Object copy failed during rename. Please file a bug." }, { ERR_RENAME_DATA_LOST, 500, "DataLost", "Rename operation lost the original data. Please file a bug." }, { ERR_RENAME_NEW_OBJ_DEL_FAILED, 500, "RenameFailed", "Rename operation failed. Please delete the duplicated object with name same as new name for the object, manually. Please file a bug." }, + {ERR_INVALID_ENC_ALGO, 400, "InvalidEncryptionAlgorithmError", "The encryption request you specified is not valid. The valid value is AES256." }, }; const static struct rgw_http_errors RGW_HTTP_SWIFT_ERRORS[] = { @@ -91,6 +95,7 @@ const static struct rgw_http_status_code http_codes[] = { { 207, "Multi Status" }, { 208, "Already Reported" }, { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, { 302, "Found" }, { 303, "See Other" }, { 304, "Not Modified" }, diff --git a/src/rgw/rgw_json_enc.cc b/src/rgw/rgw_json_enc.cc index dbaa8253eaa3c..3b50f215a5154 100644 --- a/src/rgw/rgw_json_enc.cc +++ b/src/rgw/rgw_json_enc.cc @@ -537,6 +537,80 @@ void RGWStorageStats::dump(Formatter *f) const encode_json("num_objects", num_objects, f); } +void RGWRedirectInfo::dump(Formatter *f) const +{ + encode_json("protocol", protocol, f); + encode_json("hostname", hostname, f); + encode_json("http_redirect_code", (int)http_redirect_code, f); +} + +void RGWRedirectInfo::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("protocol", protocol, obj); + JSONDecoder::decode_json("hostname", hostname, obj); + int code; + JSONDecoder::decode_json("http_redirect_code", code, obj); + http_redirect_code = code; +} + +void RGWBWRedirectInfo::dump(Formatter *f) const +{ + encode_json("redirect", redirect, f); + encode_json("replace_key_prefix_with", replace_key_prefix_with, f); + encode_json("replace_key_with", replace_key_with, f); +} + +void RGWBWRedirectInfo::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("redirect", redirect, obj); + JSONDecoder::decode_json("replace_key_prefix_with", replace_key_prefix_with, obj); + JSONDecoder::decode_json("replace_key_with", replace_key_with, obj); +} + +void RGWBWRoutingRuleCondition::dump(Formatter *f) const +{ + encode_json("key_prefix_equals", key_prefix_equals, f); + encode_json("http_error_code_returned_equals", (int)http_error_code_returned_equals, f); +} + +void RGWBWRoutingRuleCondition::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("key_prefix_equals", key_prefix_equals, obj); + int code; + JSONDecoder::decode_json("http_error_code_returned_equals", code, obj); + http_error_code_returned_equals = code; +} + +void RGWBWRoutingRule::dump(Formatter *f) const +{ + encode_json("condition", condition, f); + encode_json("redirect_info", redirect_info, f); +} + +void RGWBWRoutingRule::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("condition", condition, obj); + JSONDecoder::decode_json("redirect_info", redirect_info, obj); +} + +void RGWBWRoutingRules::dump(Formatter *f) const +{ + encode_json("rules", rules, f); +} + +void RGWBWRoutingRules::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("rules", rules, obj); +} + +void RGWBucketWebsiteConf::dump(Formatter *f) const +{ + encode_json("index_doc_suffix", index_doc_suffix, f); + encode_json("error_doc", error_doc, f); + encode_json("routing_rules", routing_rules, f); +} + +void RGWBucketWebsiteConf::decode_json(JSONObj *obj) { + JSONDecoder::decode_json("index_doc_suffix", index_doc_suffix, obj); + JSONDecoder::decode_json("error_doc", error_doc, obj); + JSONDecoder::decode_json("routing_rules", routing_rules, obj); +} + void RGWBucketInfo::dump(Formatter *f) const { encode_json("bucket", bucket, f); @@ -549,6 +623,10 @@ void RGWBucketInfo::dump(Formatter *f) const encode_json("quota", quota, f); encode_json("num_shards", num_shards, f); encode_json("bi_shard_hash_type", (uint32_t)bucket_index_shard_hash_type, f); + encode_json("has_website", has_website, f); + if (has_website) { + encode_json("website_conf", website_conf, f); + } } void RGWBucketInfo::decode_json(JSONObj *obj) { @@ -564,6 +642,10 @@ void RGWBucketInfo::decode_json(JSONObj *obj) { uint32_t hash_type; JSONDecoder::decode_json("bi_shard_hash_type", hash_type, obj); bucket_index_shard_hash_type = (uint8_t)hash_type; + JSONDecoder::decode_json("has_website", has_website, obj); + if (has_website) { + JSONDecoder::decode_json("website_conf", website_conf, obj); + } } void RGWObjEnt::dump(Formatter *f) const @@ -700,6 +782,7 @@ void RGWRegion::dump(Formatter *f) const encode_json("is_master", is_master, f); encode_json("endpoints", endpoints, f); encode_json("hostnames", hostnames, f); + encode_json("hostnames_s3website", hostnames_s3website, f); encode_json("master_zone", master_zone, f); encode_json_map("zones", zones, f); /* more friendly representation */ encode_json_map("placement_targets", placement_targets, f); /* more friendly representation */ @@ -728,6 +811,7 @@ void RGWRegion::decode_json(JSONObj *obj) JSONDecoder::decode_json("is_master", is_master, obj); JSONDecoder::decode_json("endpoints", endpoints, obj); JSONDecoder::decode_json("hostnames", hostnames, obj); + JSONDecoder::decode_json("hostnames_s3website", hostnames_s3website, obj); JSONDecoder::decode_json("master_zone", master_zone, obj); JSONDecoder::decode_json("zones", zones, decode_zones, obj); JSONDecoder::decode_json("placement_targets", placement_targets, decode_placement_targets, obj); diff --git a/src/rgw/rgw_main.cc b/src/rgw/rgw_main.cc index fd83365db3370..3e02f66130fcd 100644 --- a/src/rgw/rgw_main.cc +++ b/src/rgw/rgw_main.cc @@ -668,39 +668,60 @@ static int process_request(RGWRados *store, RGWREST *rest, RGWRequest *req, RGWC RGWRESTMgr *mgr; RGWHandler *handler = rest->get_handler(store, s, client_io, &mgr, &init_error); if (init_error != 0) { - abort_early(s, NULL, init_error); + abort_early(s, NULL, init_error, NULL); goto done; } + dout(10) << "handler=" << typeid(*handler).name() << dendl; should_log = mgr->get_logging(); req->log(s, "getting op"); op = handler->get_op(store); if (!op) { - abort_early(s, NULL, -ERR_METHOD_NOT_ALLOWED); + abort_early(s, NULL, -ERR_METHOD_NOT_ALLOWED, handler); goto done; } req->op = op; + dout(10) << "op=" << typeid(*op).name() << dendl; req->log(s, "authorizing"); ret = handler->authorize(); if (ret < 0) { dout(10) << "failed to authorize request" << dendl; - abort_early(s, op, ret); + abort_early(s, NULL, ret, handler); goto done; } if (s->user.suspended) { dout(10) << "user is suspended, uid=" << s->user.user_id << dendl; - abort_early(s, op, -ERR_USER_SUSPENDED); + abort_early(s, op, -ERR_USER_SUSPENDED, handler); goto done; } + req->log(s, "init permissions"); + ret = handler->init_permissions(op); + if (ret < 0) { + abort_early(s, op, ret, handler); + goto done; + } + + /** + * Only some accesses support website mode, and website mode does NOT apply + * if you are using the REST endpoint either (ergo, no authenticated access) + */ + req->log(s, "recalculating target"); + ret = handler->retarget(op, &op); + if (ret < 0) { + abort_early(s, op, ret, handler); + goto done; + } + req->op = op; + // This reads the ACL on the bucket or object req->log(s, "reading permissions"); ret = handler->read_permissions(op); if (ret < 0) { - abort_early(s, op, ret); + abort_early(s, op, ret, handler); goto done; } @@ -708,14 +729,14 @@ static int process_request(RGWRados *store, RGWREST *rest, RGWRequest *req, RGWC req->log(s, "init op"); ret = op->init_processing(); if (ret < 0) { - abort_early(s, op, ret); + abort_early(s, op, ret, handler); goto done; } req->log(s, "verifying op mask"); ret = op->verify_op_mask(); if (ret < 0) { - abort_early(s, op, ret); + abort_early(s, op, ret, handler); goto done; } @@ -725,7 +746,7 @@ static int process_request(RGWRados *store, RGWREST *rest, RGWRequest *req, RGWC if (s->system_request) { dout(2) << "overriding permissions due to system operation" << dendl; } else { - abort_early(s, op, ret); + abort_early(s, op, ret, handler); goto done; } } @@ -733,13 +754,28 @@ static int process_request(RGWRados *store, RGWREST *rest, RGWRequest *req, RGWC req->log(s, "verifying op params"); ret = op->verify_params(); if (ret < 0) { - abort_early(s, op, ret); + abort_early(s, op, ret, handler); goto done; } - req->log(s, "executing"); + req->log(s, "pre-executing"); op->pre_exec(); + ret = op->get_ret(); + if (ret < 0) { + dout(2) << "pre_exec ret=" << ret << dendl; + abort_early(s, op, ret, handler); + goto done; + } + + req->log(s, "executing"); op->execute(); + ret = op->get_ret(); + if (ret < 0) { + dout(2) << "execute ret=" << ret << dendl; + abort_early(s, op, ret, handler); + goto done; + } + req->log(s, "completing"); op->complete(); done: int r = client_io->complete_request(); @@ -1288,8 +1324,10 @@ int main(int argc, const char **argv) apis_map[*li] = true; } - if (apis_map.count("s3") > 0) - rest.register_default_mgr(set_logging(new RGWRESTMgr_S3)); + // S3 website mode is a specialization of S3 + bool s3website_enabled = apis_map.count("s3website") > 0; + if (apis_map.count("s3") > 0 || s3website_enabled) + rest.register_default_mgr(set_logging(new RGWRESTMgr_S3(s3website_enabled))); if (apis_map.count("swift") > 0) { do_swift = true; diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index 49ae3dd1a1f58..56ea6c2790c37 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -340,7 +340,7 @@ static int read_policy(RGWRados *store, struct req_state *s, * only_bucket: If true, reads the bucket ACL rather than the object ACL. * Returns: 0 on success, -ERR# otherwise. */ -static int rgw_build_policies(RGWRados *store, struct req_state *s, bool only_bucket, bool prefetch_data) +static int rgw_build_bucket_policies(RGWRados *store, struct req_state *s) { int ret = 0; rgw_obj_key obj; @@ -423,9 +423,20 @@ static int rgw_build_policies(RGWRados *store, struct req_state *s, bool only_bu } } - /* we're passed only_bucket = true when we specifically need the bucket's - acls, that happens on write operations */ - if (!only_bucket && !s->object.empty()) { + return ret; +} + +/** + * Get the AccessControlPolicy for a bucket or object off of disk. + * s: The req_state to draw information from. + * only_bucket: If true, reads the bucket ACL rather than the object ACL. + * Returns: 0 on success, -ERR# otherwise. + */ +static int rgw_build_object_policies(RGWRados *store, struct req_state *s, bool prefetch_data) +{ + int ret = 0; + + if (!s->object.empty()) { if (!s->bucket_exists) { return -ERR_NO_SUCH_BUCKET; } @@ -858,18 +869,6 @@ int RGWGetObj::handle_user_manifest(const char *prefix) return 0; } -class RGWGetObj_CB : public RGWGetDataCB -{ - RGWGetObj *op; -public: - RGWGetObj_CB(RGWGetObj *_op) : op(_op) {} - virtual ~RGWGetObj_CB() {} - - int handle_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { - return op->get_data_cb(bl, bl_ofs, bl_len); - } -}; - int RGWGetObj::get_data_cb(bufferlist& bl, off_t bl_ofs, off_t bl_len) { /* garbage collection related handling */ @@ -888,6 +887,7 @@ int RGWGetObj::get_data_cb(bufferlist& bl, off_t bl_ofs, off_t bl_len) void RGWGetObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWGetObj::execute() @@ -970,13 +970,19 @@ void RGWGetObj::execute() ret = read_op.iterate(ofs, end, &cb); perfcounter->tinc(l_rgw_get_lat, - (ceph_clock_now(s->cct) - start_time)); + (ceph_clock_now(s->cct) - start_time)); + if (ret < 0) { + goto done_err; + } + + ret = send_response_data(bl, 0, 0); if (ret < 0) { goto done_err; } + return; done_err: - send_response_data(bl, 0, 0); + send_response_data_error(); if (kmsdata) delete kmsdata; @@ -1113,6 +1119,7 @@ int RGWGetBucketVersioning::verify_permission() void RGWGetBucketVersioning::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWGetBucketVersioning::execute() @@ -1132,6 +1139,7 @@ int RGWSetBucketVersioning::verify_permission() void RGWSetBucketVersioning::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWSetBucketVersioning::execute() @@ -1155,6 +1163,84 @@ void RGWSetBucketVersioning::execute() } } +int RGWGetBucketWebsite::verify_permission() +{ + if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0) + return -EACCES; + + return 0; +} + +void RGWGetBucketWebsite::pre_exec() +{ + rgw_bucket_object_pre_exec(s); + ret = 0; +} + +void RGWGetBucketWebsite::execute() +{ + if (!s->bucket_info.has_website) { + ret = -ENOENT; + } +} + +int RGWSetBucketWebsite::verify_permission() +{ + if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0) + return -EACCES; + + return 0; +} + +void RGWSetBucketWebsite::pre_exec() +{ + rgw_bucket_object_pre_exec(s); + ret = 0; +} + +void RGWSetBucketWebsite::execute() +{ + ret = get_params(); + + if (ret < 0) + return; + + s->bucket_info.has_website = true; + s->bucket_info.website_conf = website_conf; + + ret = store->put_bucket_instance_info(s->bucket_info, false, 0, &s->bucket_attrs); + if (ret < 0) { + ldout(s->cct, 0) << "NOTICE: put_bucket_info on bucket=" << s->bucket.name << " returned err=" << ret << dendl; + return; + } +} + +int RGWDeleteBucketWebsite::verify_permission() +{ + if (s->user.user_id.compare(s->bucket_owner.get_id()) != 0) + return -EACCES; + + return 0; +} + +void RGWDeleteBucketWebsite::pre_exec() +{ + rgw_bucket_object_pre_exec(s); + ret = 0; +} + +void RGWDeleteBucketWebsite::execute() +{ + s->bucket_info.has_website = false; + s->bucket_info.website_conf = RGWBucketWebsiteConf(); + + ret = store->put_bucket_instance_info(s->bucket_info, false, 0, &s->bucket_attrs); + if (ret < 0) { + ldout(s->cct, 0) << "NOTICE: put_bucket_info on bucket=" << s->bucket.name << " returned err=" << ret << dendl; + return; + } +} + int RGWStatBucket::verify_permission() { if (!verify_bucket_permission(s, RGW_PERM_READ)) @@ -1166,6 +1252,7 @@ int RGWStatBucket::verify_permission() void RGWStatBucket::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWStatBucket::execute() @@ -1218,6 +1305,7 @@ int RGWListBucket::parse_max_keys() void RGWListBucket::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWListBucket::execute() @@ -1323,6 +1411,7 @@ void RGWCreateBucket::pre_exec() ret = dialect_handler->validate_bucket_name(s->bucket_name_str, s->cct->_conf->rgw_s3_bucket_name_create_strictness); rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWCreateBucket::execute() @@ -1455,7 +1544,8 @@ void RGWCreateBucket::execute() ldout(s->cct, 0) << "WARNING: failed to unlink bucket: ret=" << ret << dendl; } } else if (ret == -EEXIST || (ret == 0 && existed)) { - ret = -ERR_BUCKET_EXISTS; + ret = 0; + exist_ret = -ERR_BUCKET_EXISTS; } } @@ -1470,6 +1560,7 @@ int RGWDeleteBucket::verify_permission() void RGWDeleteBucket::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWDeleteBucket::execute() @@ -1734,6 +1825,7 @@ void RGWPutObj::dispose_processor(RGWPutObjProcessor *processor) void RGWPutObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } static int put_data_and_throttle(RGWPutObjProcessor *processor, bufferlist& data, off_t ofs, @@ -2061,6 +2153,7 @@ void RGWPostObj::dispose_processor(RGWPutObjProcessor *processor) void RGWPostObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWPostObj::execute() @@ -2304,6 +2397,7 @@ int RGWDeleteObj::verify_permission() void RGWDeleteObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWDeleteObj::execute() @@ -2351,7 +2445,6 @@ bool RGWCopyObj::parse_copy_location(const string& url_src, string& bucket_name, string dec_src; - url_decode(name_str, dec_src); const char *src = dec_src.c_str(); @@ -2510,6 +2603,7 @@ void RGWCopyObj::progress_cb(off_t ofs) void RGWCopyObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWCopyObj::execute() @@ -2568,6 +2662,7 @@ int RGWGetACLs::verify_permission() void RGWGetACLs::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWGetACLs::execute() @@ -2598,6 +2693,7 @@ int RGWPutACLs::verify_permission() void RGWPutACLs::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWPutACLs::execute() @@ -2863,6 +2959,7 @@ int RGWInitMultipart::verify_permission() void RGWInitMultipart::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWInitMultipart::execute() @@ -3097,6 +3194,7 @@ int RGWCompleteMultipart::verify_permission() void RGWCompleteMultipart::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWCompleteMultipart::execute() @@ -3296,6 +3394,7 @@ int RGWAbortMultipart::verify_permission() void RGWAbortMultipart::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWAbortMultipart::execute() @@ -3397,6 +3496,7 @@ int RGWListMultipart::verify_permission() void RGWListMultipart::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWListMultipart::execute() @@ -3430,6 +3530,7 @@ int RGWListBucketMultiparts::verify_permission() void RGWListBucketMultiparts::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWListBucketMultiparts::execute() @@ -3490,6 +3591,7 @@ int RGWDeleteMultiObj::verify_permission() void RGWDeleteMultiObj::pre_exec() { rgw_bucket_object_pre_exec(s); + ret = 0; } void RGWDeleteMultiObj::execute() @@ -3585,12 +3687,29 @@ int RGWHandler::init(RGWRados *_store, struct req_state *_s, RGWClientIO *cio) return 0; } +int RGWHandler::do_init_permissions() +{ + int ret = rgw_build_bucket_policies(store, s); + + if (ret < 0) { + ldout(s->cct, 10) << "read_permissions on " << s->bucket << " ret=" << ret << dendl; + if (ret == -ENODATA) + ret = -EACCES; + } + + return ret; +} + int RGWHandler::do_read_permissions(RGWOp *op, bool only_bucket) { - int ret = rgw_build_policies(store, s, only_bucket, op->prefetch_data()); + if (only_bucket) { + /* already read bucket info */ + return 0; + } + int ret = rgw_build_object_policies(store, s, op->prefetch_data()); if (ret < 0) { - ldout(s->cct, 10) << "read_permissions on " << s->bucket << ":" <object << " only_bucket=" << only_bucket << " ret=" << ret << dendl; + ldout(s->cct, 10) << "read_permissions on " << s->bucket << ":" << s->object << " ret=" << ret << dendl; if (ret == -ENODATA) ret = -EACCES; } @@ -3639,6 +3758,14 @@ void RGWHandler::put_op(RGWOp *op) delete op; } +int RGWOp::error_handler(int err_no, string *error_content) { + return dialect_handler->error_handler(err_no, error_content); +} + +int RGWHandler::error_handler(int err_no, string *error_content) { + // This is the do-nothing error handler + return err_no; +} /* Object Rename operation */ int RGWRenameObj::verify_permission() @@ -3659,7 +3786,7 @@ void RGWRenameObj::execute() s->err.ret = 0; rgw_obj_key orig_object, new_obj; orig_object.dss_duplicate(&(s->object)); - string copysource; + string copysource, raw_copy_source; int ret_orig, ret_newobj; RGWCopyObj_ObjStore_S3* copy_op = NULL; RGWDeleteObj_ObjStore_S3* del_op = NULL; @@ -3682,7 +3809,7 @@ void RGWRenameObj::execute() s->err.ret = -ERR_RENAME_OBJ_EXISTS; return; } - + raw_copy_source = get_raw_copy_source(); copysource = s->bucket_name_str; copysource.append("/"); copysource.append(orig_object.name); @@ -3691,10 +3818,12 @@ void RGWRenameObj::execute() s->info.env->set("HTTP_X_JCS_COPY_SOURCE", copysource.c_str()); s->info.env->set("HTTP_X_JCS_METADATA_DIRECTIVE", "COPY"); s->copy_source = s->info.env->get("HTTP_X_JCS_COPY_SOURCE"); + ldout(s->cct, 0) << "DSS INFO: The raw copy location to be parsed: " << raw_copy_source <copy_source) { - ret = RGWCopyObj::parse_copy_location(s->copy_source, s->src_bucket_name, s->src_object); + ret = RGWCopyObj::parse_copy_location(raw_copy_source, s->src_bucket_name, s->src_object); + ldout(s->cct, 0) << "DSS INFO: final source key name: " << s->src_object << " and bucket name: " << s->src_bucket_name << dendl; if (!ret) { //Surprizingly returns bool - ldout(s->cct, 0) << "DSS INFO: Rename op failed to parse copy location" << dendl; + ldout(s->cct, 0) << "DSS ERROR: Rename op failed to parse copy location" << dendl; s->err.ret = -ERR_RENAME_FAILED; s->err.http_ret = 403; return; @@ -3848,3 +3977,12 @@ void RGWRenameObj::delete_rgw_object(RGWOp* del_op) return; } +string RGWRenameObj::get_raw_copy_source() +{ + string uri = s->info.request_uri; + int start = uri.find('/'); + int end = uri.find('?'); + string raw_copy_source = uri.substr(start+1, end); + return raw_copy_source; +} + diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 8ce533fcc5f0b..e521fd08cd78f 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -39,6 +39,8 @@ enum RGWOpType { RGW_OP_GET_BUCKET_LOGGING, RGW_OP_GET_BUCKET_VERSIONING, RGW_OP_SET_BUCKET_VERSIONING, + RGW_OP_GET_BUCKET_WEBSITE, + RGW_OP_SET_BUCKET_WEBSITE, RGW_OP_STAT_BUCKET, RGW_OP_CREATE_BUCKET, RGW_OP_DELETE_BUCKET, @@ -74,6 +76,7 @@ class RGWOp { bool cors_exist; RGWQuotaInfo bucket_quota; RGWQuotaInfo user_quota; + int ret; virtual int init_quota(); public: @@ -112,6 +115,9 @@ class RGWOp { virtual RGWOpType get_type() { return RGW_OP_UNKNOWN; } virtual uint32_t op_mask() { return 0; } + + virtual int error_handler(int err_no, string *error_content); + int get_ret() { return ret; }; }; class RGWGetObj : public RGWOp { @@ -132,7 +138,6 @@ class RGWGetObj : public RGWOp { time_t *mod_ptr; time_t *unmod_ptr; map attrs; - int ret; bool get_data; bool partial_content; rgw_obj obj; @@ -157,7 +162,6 @@ class RGWGetObj : public RGWOp { unmod_ptr = NULL; get_data = false; partial_content = false; - ret = 0; } virtual bool prefetch_data() { return get_data; } @@ -174,25 +178,38 @@ class RGWGetObj : public RGWOp { int get_data_cb(bufferlist& bl, off_t ofs, off_t len); virtual int get_params() = 0; + virtual int send_response_data_error() = 0; virtual int send_response_data(bufferlist& bl, off_t ofs, off_t len) = 0; virtual const string name() { return "get_obj"; } virtual RGWOpType get_type() { return RGW_OP_GET_OBJ; } virtual uint32_t op_mask() { return RGW_OP_TYPE_READ; } + virtual bool need_object_expiration() { return false; } +}; + +class RGWGetObj_CB : public RGWGetDataCB +{ + RGWGetObj *op; +public: + RGWGetObj_CB(RGWGetObj *_op) : op(_op) {} + virtual ~RGWGetObj_CB() {} + + int handle_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { + return op->get_data_cb(bl, bl_ofs, bl_len); + } }; #define RGW_LIST_BUCKETS_LIMIT_MAX 10000 class RGWListBuckets : public RGWOp { protected: - int ret; bool sent_data; string marker; uint64_t limit; uint64_t limit_max; public: - RGWListBuckets() : ret(0), sent_data(false) { + RGWListBuckets() : sent_data(false) { limit = limit_max = RGW_LIST_BUCKETS_LIMIT_MAX; } @@ -214,7 +231,6 @@ class RGWListBuckets : public RGWOp { class RGWStatAccount : public RGWOp { protected: - int ret; uint32_t buckets_count; uint64_t buckets_objcount; uint64_t buckets_size; @@ -249,7 +265,6 @@ class RGWListBucket : public RGWOp { string encoding_type; bool list_versions; int max; - int ret; vector objs; map common_prefixes; @@ -259,7 +274,7 @@ class RGWListBucket : public RGWOp { int parse_max_keys(); public: - RGWListBucket() : list_versions(false), max(0), ret(0), + RGWListBucket() : list_versions(false), max(0), default_max(0), is_truncated(false) {} int verify_permission(); void pre_exec(); @@ -277,7 +292,7 @@ class RGWGetBucketLogging : public RGWOp { public: RGWGetBucketLogging() {} int verify_permission(); - void execute() {} + void execute() { ret = 0; } virtual void send_response() = 0; virtual const string name() { return "get_bucket_logging"; } @@ -290,7 +305,7 @@ class RGWGetBucketLocation : public RGWOp { RGWGetBucketLocation() {} ~RGWGetBucketLocation() {} int verify_permission(); - void execute() {} + void execute() { ret = 0; } virtual void send_response() = 0; virtual const string name() { return "get_bucket_location"; } @@ -317,9 +332,8 @@ class RGWGetBucketVersioning : public RGWOp { class RGWSetBucketVersioning : public RGWOp { protected: bool enable_versioning; - int ret; public: - RGWSetBucketVersioning() : enable_versioning(false), ret(0) {} + RGWSetBucketVersioning() : enable_versioning(false) {} int verify_permission(); void pre_exec(); @@ -333,13 +347,58 @@ class RGWSetBucketVersioning : public RGWOp { virtual uint32_t op_mask() { return RGW_OP_TYPE_WRITE; } }; +class RGWGetBucketWebsite : public RGWOp { +public: + RGWGetBucketWebsite() {} + + int verify_permission(); + void pre_exec(); + void execute(); + + virtual void send_response() = 0; + virtual const string name() { return "get_bucket_website"; } + virtual RGWOpType get_type() { return RGW_OP_GET_BUCKET_WEBSITE; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_READ; } +}; + +class RGWSetBucketWebsite : public RGWOp { +protected: + RGWBucketWebsiteConf website_conf; +public: + RGWSetBucketWebsite() {} + + int verify_permission(); + void pre_exec(); + void execute(); + + virtual int get_params() { return 0; } + + virtual void send_response() = 0; + virtual const string name() { return "set_bucket_website"; } + virtual RGWOpType get_type() { return RGW_OP_SET_BUCKET_WEBSITE; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_WRITE; } +}; + +class RGWDeleteBucketWebsite : public RGWOp { +public: + RGWDeleteBucketWebsite() {} + + int verify_permission(); + void pre_exec(); + void execute(); + + virtual void send_response() = 0; + virtual const string name() { return "delete_bucket_website"; } + virtual RGWOpType get_type() { return RGW_OP_SET_BUCKET_WEBSITE; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_WRITE; } +}; + class RGWStatBucket : public RGWOp { protected: - int ret; RGWBucketEnt bucket; public: - RGWStatBucket() : ret(0) {} + RGWStatBucket() {} ~RGWStatBucket() {} int verify_permission(); @@ -354,7 +413,6 @@ class RGWStatBucket : public RGWOp { class RGWCreateBucket : public RGWOp { protected: - int ret; RGWAccessControlPolicy policy; string location_constraint; string placement_rule; @@ -365,8 +423,10 @@ class RGWCreateBucket : public RGWOp { bufferlist in_data; + int exist_ret; + public: - RGWCreateBucket() : ret(0), has_cors(false) {} + RGWCreateBucket() : has_cors(false), exist_ret(0) {} int verify_permission(); void pre_exec(); @@ -384,12 +444,10 @@ class RGWCreateBucket : public RGWOp { class RGWDeleteBucket : public RGWOp { protected: - int ret; - RGWObjVersionTracker objv_tracker; public: - RGWDeleteBucket() : ret(0) {} + RGWDeleteBucket() {} int verify_permission(); void pre_exec(); @@ -406,7 +464,6 @@ class RGWPutObj : public RGWOp { friend class RGWPutObjProcessor; protected: - int ret; off_t ofs; const char *supplied_md5_b64; const char *supplied_etag; @@ -426,7 +483,6 @@ class RGWPutObj : public RGWOp { public: RGWPutObj() { - ret = 0; ofs = 0; supplied_md5_b64 = NULL; supplied_etag = NULL; @@ -467,7 +523,6 @@ class RGWPostObj : public RGWOp { protected: off_t min_len; off_t max_len; - int ret; int len; off_t ofs; const char *supplied_md5_b64; @@ -480,7 +535,7 @@ class RGWPostObj : public RGWOp { map attrs; public: - RGWPostObj() : min_len(0), max_len(LLONG_MAX), ret(0), len(0), ofs(0), + RGWPostObj() : min_len(0), max_len(LLONG_MAX), len(0), ofs(0), supplied_md5_b64(NULL), supplied_etag(NULL), data_pending(false) {} @@ -506,7 +561,6 @@ class RGWPostObj : public RGWOp { class RGWPutMetadata : public RGWOp { protected: - int ret; set rmattr_names; bool has_policy, has_cors; RGWAccessControlPolicy policy; @@ -541,7 +595,6 @@ class RGWSetTempUrl : public RGWOp { map temp_url_keys; public: RGWSetTempUrl() : ret(0) {} - int verify_permission(); void execute(); @@ -553,12 +606,11 @@ class RGWSetTempUrl : public RGWOp { class RGWDeleteObj : public RGWOp { protected: - int ret; bool delete_marker; string version_id; public: - RGWDeleteObj() : ret(0), delete_marker(false) {} + RGWDeleteObj() : delete_marker(false) {} int verify_permission(); void pre_exec(); @@ -581,6 +633,7 @@ class RGWRenameObj : public RGWOp { void perform_external_op(RGWOp*); void delete_rgw_object(RGWOp*); int check_obj(rgw_obj_key&); + string get_raw_copy_source(); virtual const string name() { return "Rename_obj"; } }; @@ -598,7 +651,6 @@ class RGWCopyObj : public RGWOp { time_t unmod_time; time_t *mod_ptr; time_t *unmod_ptr; - int ret; map attrs; string src_bucket_name; rgw_bucket src_bucket; @@ -667,11 +719,10 @@ class RGWCopyObj : public RGWOp { class RGWGetACLs : public RGWOp { protected: - int ret; string acls; public: - RGWGetACLs() : ret(0) {} + RGWGetACLs() {} int verify_permission(); void pre_exec(); @@ -685,7 +736,6 @@ class RGWGetACLs : public RGWOp { class RGWPutACLs : public RGWOp { protected: - int ret; size_t len; char *data; ACLOwner owner; @@ -714,10 +764,9 @@ class RGWPutACLs : public RGWOp { class RGWGetCORS : public RGWOp { protected: - int ret; public: - RGWGetCORS() : ret(0) {} + RGWGetCORS() {} int verify_permission(); void execute(); @@ -730,7 +779,6 @@ class RGWGetCORS : public RGWOp { class RGWPutCORS : public RGWOp { protected: - int ret; bufferlist cors_bl; public: @@ -751,10 +799,9 @@ class RGWPutCORS : public RGWOp { class RGWDeleteCORS : public RGWOp { protected: - int ret; public: - RGWDeleteCORS() : ret(0) {} + RGWDeleteCORS() {} int verify_permission(); void execute(); @@ -767,12 +814,11 @@ class RGWDeleteCORS : public RGWOp { class RGWOptionsCORS : public RGWOp { protected: - int ret; RGWCORSRule *rule; const char *origin, *req_hdrs, *req_meth; public: - RGWOptionsCORS() : ret(0), rule(NULL), origin(NULL), + RGWOptionsCORS() : rule(NULL), origin(NULL), req_hdrs(NULL), req_meth(NULL) { } @@ -788,7 +834,6 @@ class RGWOptionsCORS : public RGWOp { class RGWInitMultipart : public RGWOp { protected: - int ret; string upload_id; RGWAccessControlPolicy policy; @@ -814,7 +859,6 @@ class RGWInitMultipart : public RGWOp { class RGWCompleteMultipart : public RGWOp { protected: - int ret; string upload_id; string etag; char *data; @@ -843,10 +887,8 @@ class RGWCompleteMultipart : public RGWOp { class RGWAbortMultipart : public RGWOp { protected: - int ret; - public: - RGWAbortMultipart() : ret(0) {} + RGWAbortMultipart() {} int verify_permission(); void pre_exec(); @@ -860,7 +902,6 @@ class RGWAbortMultipart : public RGWOp { class RGWListMultipart : public RGWOp { protected: - int ret; string upload_id; map parts; int max_parts; @@ -969,7 +1010,6 @@ class RGWListBucketMultiparts : public RGWOp { RGWMultipartUploadEntry next_marker; int max_uploads; string delimiter; - int ret; vector uploads; map common_prefixes; bool is_truncated; @@ -1001,7 +1041,6 @@ class RGWListBucketMultiparts : public RGWOp { class RGWDeleteMultiObj : public RGWOp { protected: - int ret; int max_to_delete; size_t len; char *data; @@ -1040,6 +1079,7 @@ class RGWHandler { RGWRados *store; struct req_state *s; + int do_init_permissions(); int do_read_permissions(RGWOp *op, bool only_bucket); virtual RGWOp *op_get() { return NULL; } @@ -1057,8 +1097,16 @@ class RGWHandler { virtual int validate_bucket_name(const string& bucket, int name_strictness) { return 0; } virtual RGWOp *get_op(RGWRados *store); virtual void put_op(RGWOp *op); + virtual int init_permissions(RGWOp *op) { + return 0; + } + virtual int retarget(RGWOp *op, RGWOp **new_op) { + *new_op = op; + return 0; + } virtual int read_permissions(RGWOp *op) = 0; virtual int authorize() = 0; + virtual int error_handler(int err_no, string *error_content); }; #endif diff --git a/src/rgw/rgw_rados.h b/src/rgw/rgw_rados.h index 18265dea9fea3..05ea9de5ca9ad 100644 --- a/src/rgw/rgw_rados.h +++ b/src/rgw/rgw_rados.h @@ -916,6 +916,23 @@ struct RGWRegion { string default_placement; list hostnames; + list hostnames_s3website; + // TODO: Maybe convert hostnames to a map> for + // endpoint_type->hostnames +/* +20:05 < _robbat21irssi> maybe I do someting like: if (hostname_map.empty()) { populate all map keys from hostnames; }; +20:05 < _robbat21irssi> but that's a later compatability migration planning bit +20:06 < yehudasa> more like if (!hostnames.empty()) { +20:06 < yehudasa> for (list::iterator iter = hostnames.begin(); iter != hostnames.end(); ++iter) { +20:06 < yehudasa> hostname_map["s3"].append(iter->second); +20:07 < yehudasa> hostname_map["s3website"].append(iter->second); +20:07 < yehudasa> s/append/push_back/g +20:08 < _robbat21irssi> inner loop over APIs +20:08 < yehudasa> yeah, probably +20:08 < _robbat21irssi> s3, s3website, swift, swith_auth, swift_website +*/ + map > api_hostname_map; + map > api_endpoints_map; CephContext *cct; RGWRados *store; @@ -923,7 +940,7 @@ struct RGWRegion { RGWRegion() : is_master(false), cct(NULL), store(NULL) {} void encode(bufferlist& bl) const { - ENCODE_START(2, 1, bl); + ENCODE_START(3, 1, bl); ::encode(name, bl); ::encode(api_name, bl); ::encode(is_master, bl); @@ -933,11 +950,12 @@ struct RGWRegion { ::encode(placement_targets, bl); ::encode(default_placement, bl); ::encode(hostnames, bl); + ::encode(hostnames_s3website, bl); ENCODE_FINISH(bl); } void decode(bufferlist::iterator& bl) { - DECODE_START(2, bl); + DECODE_START(3, bl); ::decode(name, bl); ::decode(api_name, bl); ::decode(is_master, bl); @@ -949,6 +967,9 @@ struct RGWRegion { if (struct_v >= 2) { ::decode(hostnames, bl); } + if (struct_v >= 3) { + ::decode(hostnames_s3website, bl); + } DECODE_FINISH(bl); } diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index afc149f11c6ec..52215ee30a217 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -5,6 +5,7 @@ #include #include "common/Formatter.h" +#include "common/HTMLFormatter.h" #include "common/utf8.h" #include "include/str_list.h" #include "rgw_common.h" @@ -21,6 +22,8 @@ #include "rgw_client_io.h" #include "rgw_resolve.h" +#include + #define dout_subsys ceph_subsys_rgw @@ -40,6 +43,12 @@ static struct rgw_http_attr rgw_to_http_attr_list[] = { { RGW_ATTR_CONTENT_DISP, "Content-Disposition"}, { RGW_ATTR_CONTENT_ENC, "Content-Encoding"}, { RGW_ATTR_USER_MANIFEST, "X-Object-Manifest"}, + { RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION, "Location"}, + /* RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION header depends on access mode: + * S3 endpoint: x-amz-website-redirect-location + * S3Website endpoint: Location + */ + { RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION, "x-amz-website-redirect-location" }, { NULL, NULL}, }; @@ -161,7 +170,9 @@ string camelcase_dash_http_attr(const string& orig) return string(buf); } -static list hostnames_list; +/* avoid duplicate hostnames in hostnames lists */ +static set hostnames_set; +static set hostnames_s3website_set; void rgw_rest_init(CephContext *cct, RGWRegion& region) { @@ -196,15 +207,30 @@ void rgw_rest_init(CephContext *cct, RGWRegion& region) /* avoid duplicate hostnames in hostnames list */ map hostnames_map; if (!cct->_conf->rgw_dns_name.empty()) { - hostnames_map[cct->_conf->rgw_dns_name] = true; - } - for (list::iterator iter = region.hostnames.begin(); iter != region.hostnames.end(); ++iter) { - hostnames_map[*iter] = true; - } + hostnames_set.insert(cct->_conf->rgw_dns_name); + } + hostnames_set.insert(region.hostnames.begin(), region.hostnames.end()); + string s; + ldout(cct, 20) << "RGW hostnames: " << std::accumulate(hostnames_set.begin(), hostnames_set.end(), s) << dendl; + /* TODO: We should have a sanity check that no hostname matches the end of + * any other hostname, otherwise we will get ambigious results from + * rgw_find_host_in_domains. + * Eg: + * Hostnames: [A, B.A] + * Inputs: [Z.A, X.B.A] + * Z.A clearly splits to subdomain=Z, domain=Z + * X.B.A ambigously splits to both {X, B.A} and {X.B, A} + */ - for (map::iterator iter = hostnames_map.begin(); iter != hostnames_map.end(); ++iter) { - hostnames_list.push_back(iter->first); + if (!cct->_conf->rgw_dns_s3website_name.empty()) { + hostnames_s3website_set.insert(cct->_conf->rgw_dns_s3website_name); } + hostnames_s3website_set.insert(region.hostnames_s3website.begin(), region.hostnames_s3website.end()); + s.clear(); + ldout(cct, 20) << "RGW S3website hostnames: " << std::accumulate(hostnames_s3website_set.begin(), hostnames_s3website_set.end(), s) << dendl; + /* TODO: we should repeat the hostnames_set sanity check here + * and ALSO decide about overlap, if any + */ } static bool str_ends_with(const string& s, const string& suffix, size_t *pos) @@ -222,10 +248,14 @@ static bool str_ends_with(const string& s, const string& suffix, size_t *pos) return s.compare(p, len, suffix) == 0; } -static bool rgw_find_host_in_domains(const string& host, string *domain, string *subdomain) +static bool rgw_find_host_in_domains(const string& host, string *domain, string *subdomain, set valid_hostnames_set) { - list::iterator iter; - for (iter = hostnames_list.begin(); iter != hostnames_list.end(); ++iter) { + set::iterator iter; + /** TODO, Future optimization + * store hostnames_set elements _reversed_, and look for a prefix match, + * which is much faster than a suffix match. + */ + for (iter = valid_hostnames_set.begin(); iter != valid_hostnames_set.end(); ++iter) { size_t pos; if (!str_ends_with(host, *iter, &pos)) continue; @@ -248,6 +278,7 @@ static bool rgw_find_host_in_domains(const string& host, string *domain, string static void dump_status(struct req_state *s, const char *status, const char *status_name) { + s->formatter->set_status(status, status_name); int r = s->cio->send_status(status, status_name); if (r < 0) { ldout(s->cct, 0) << "ERROR: s->cio->send_status() returned err=" << r << dendl; @@ -257,6 +288,7 @@ static void dump_status(struct req_state *s, const char *status, const char *sta void rgw_flush_formatter_and_reset(struct req_state *s, Formatter *formatter) { std::ostringstream oss; + formatter->output_footer(); formatter->flush(oss); std::string outs(oss.str()); if (!outs.empty() && s->op != OP_HEAD) { @@ -283,6 +315,7 @@ void set_req_state_err(struct req_state *s, int err_no) if (err_no < 0) err_no = -err_no; s->err.ret = -err_no; + if (s->prot_flags & RGW_REST_SWIFT) { r = search_err(err_no, RGW_HTTP_SWIFT_ERRORS, ARRAY_LEN(RGW_HTTP_SWIFT_ERRORS)); if (r) { @@ -294,15 +327,11 @@ void set_req_state_err(struct req_state *s, int err_no) return; } } + r = search_err(err_no, RGW_HTTP_ERRORS, ARRAY_LEN(RGW_HTTP_ERRORS)); if (r) { s->err.http_ret = r->http_ret; s->err.s3_code = r->s3_code; - if (r->s3_err_message) { - if ((s->err.message).empty()) { - s->err.message = r->s3_err_message; - } - } return; } dout(0) << "WARNING: set_req_state_err err_no=" << err_no << " resorting to 500" << dendl; @@ -322,7 +351,7 @@ void dump_errno(struct req_state *s, int err) { char buf[32]; snprintf(buf, sizeof(buf), "%d", err); - dump_status(s, buf, http_status_names[s->err.http_ret]); + dump_status(s, buf, http_status_names[err]); } void dump_string_header(struct req_state *s, const char *name, const char *val) @@ -348,9 +377,10 @@ void dump_content_length(struct req_state *s, uint64_t len) void dump_etag(struct req_state *s, const char *etag) { int r; - if (s->prot_flags & RGW_REST_SWIFT) + if (s->prot_flags & RGW_REST_SWIFT) { r = s->cio->print("etag: %s\r\n", etag); - else + } + else r = s->cio->print("ETag: \"%s\"\r\n", etag); if (r < 0) { ldout(s->cct, 0) << "ERROR: s->cio->print() returned err=" << r << dendl; @@ -505,8 +535,7 @@ void dump_access_control_for_console(req_state *s, const char* origin, const cha void dump_start(struct req_state *s) { if (!s->content_started) { - if (s->format == RGW_FORMAT_XML) - s->formatter->write_raw_data(XMLFormatter::XML_1_DTD); + s->formatter->output_header(); s->content_started = true; } } @@ -522,7 +551,7 @@ void dump_trans_id(req_state *s) } void end_header(struct req_state *s, RGWOp *op, const char *content_type, const int64_t proposed_content_length, - bool force_content_type) + bool force_content_type, bool force_no_error) { string ctype; @@ -622,6 +651,9 @@ void end_header(struct req_state *s, RGWOp *op, const char *content_type, const case RGW_FORMAT_JSON: ctype = "application/json"; break; + case RGW_FORMAT_HTML: + ctype = "text/html"; + break; default: ctype = "text/plain"; break; @@ -630,16 +662,24 @@ void end_header(struct req_state *s, RGWOp *op, const char *content_type, const ctype.append("; charset=utf-8"); content_type = ctype.c_str(); } - if (s->err.is_err()) { + if (!force_no_error && s->err.is_err()) { dump_start(s); - s->formatter->open_object_section("Error"); + if (s->format != RGW_FORMAT_HTML) { + s->formatter->open_object_section("Error"); + } if (!s->err.s3_code.empty()) s->formatter->dump_string("Code", s->err.s3_code); if (!s->err.message.empty()) s->formatter->dump_string("Message", s->err.message); - if (!s->trans_id.empty()) - s->formatter->dump_string("RequestId", s->trans_id); - s->formatter->close_section(); + if (!s->bucket_name_str.empty()) // TODO: connect to expose_bucket + s->formatter->dump_string("BucketName", s->bucket_name_str); + if (!s->trans_id.empty()) // TODO: connect to expose_bucket or another toggle + s->formatter->dump_string("RequestId", s->trans_id); + s->formatter->dump_string("HostId", "FIXME-TODO-How-does-amazon-generate-HostId"); // TODO, FIXME + if (s->format != RGW_FORMAT_HTML) { + s->formatter->close_section(); + } + s->formatter->output_footer(); dump_content_length(s, s->formatter->get_len()); } else { if (proposed_content_length != NO_CONTENT_LENGTH) { @@ -663,32 +703,77 @@ void end_header(struct req_state *s, RGWOp *op, const char *content_type, const rgw_flush_formatter_and_reset(s, s->formatter); } -void abort_early(struct req_state *s, RGWOp *op, int err_no) +void abort_early(struct req_state *s, RGWOp *op, int err_no, RGWHandler* handler) { + string error_content(""); if (!s->formatter) { s->formatter = new JSONFormatter; s->format = RGW_FORMAT_JSON; } - set_req_state_err(s, err_no); - dump_errno(s); - dump_bucket_from_state(s); - if (err_no == -ERR_PERMANENT_REDIRECT && !s->region_endpoint.empty()) { - string dest_uri = s->region_endpoint; - /* - * reqest_uri is always start with slash, so we need to remove - * the unnecessary slash at the end of dest_uri. - */ - if (dest_uri[dest_uri.size() - 1] == '/') { - dest_uri = dest_uri.substr(0, dest_uri.size() - 1); + + // op->error_handler is responsible for calling it's handler error_handler + if (op != NULL) { + int new_err_no; + new_err_no = op->error_handler(err_no, &error_content); + ldout(s->cct, 20) << "op->ERRORHANDLER: err_no=" << err_no << " new_err_no=" << new_err_no << dendl; + err_no = new_err_no; + } else if (handler != NULL) { + int new_err_no; + new_err_no = handler->error_handler(err_no, &error_content); + ldout(s->cct, 20) << "handler->ERRORHANDLER: err_no=" << err_no << " new_err_no=" << new_err_no << dendl; + err_no = new_err_no; + } + + // If the error handler(s) above dealt with it completely, they should have + // returned 0. If non-zero, we need to continue here. + if (err_no) { + // Watch out, we might have a custom error state already set! + if (s->err.http_ret && s->err.http_ret != 200) { + dump_errno(s); + } else { + set_req_state_err(s, err_no); + dump_errno(s); + } + dump_bucket_from_state(s); + if (err_no == -ERR_PERMANENT_REDIRECT || err_no == -ERR_WEBSITE_REDIRECT) { + string dest_uri; + if (!s->redirect.empty()) { + dest_uri = s->redirect; + } else if (!s->region_endpoint.empty()) { + string dest_uri = s->region_endpoint; + /* + * reqest_uri is always start with slash, so we need to remove + * the unnecessary slash at the end of dest_uri. + */ + if (dest_uri[dest_uri.size() - 1] == '/') { + dest_uri = dest_uri.substr(0, dest_uri.size() - 1); + } + dest_uri += s->info.request_uri; + dest_uri += "?"; + dest_uri += s->info.request_params; + } + + if (!dest_uri.empty()) { + dump_redirect(s, dest_uri); + } } - dest_uri += s->info.request_uri; - dest_uri += "?"; - dest_uri += s->info.request_params; - dump_redirect(s, dest_uri); + if (!error_content.empty()) { + /* + * TODO we must add all error entries as headers here: + * when having a working errordoc, then the s3 error fields are + * rendered as HTTP headers, e.g.: + * x-amz-error-code: NoSuchKey + * x-amz-error-message: The specified key does not exist. + * x-amz-error-detail-Key: foo + */ + end_header(s, op, NULL, error_content.size(), false, true); + s->cio->write(error_content.c_str(), error_content.size()); + } else { + end_header(s, op); + } + rgw_flush_formatter(s, s->formatter); } - end_header(s, op); - rgw_flush_formatter_and_reset(s, s->formatter); perfcounter->inc(l_rgw_failed_req); } @@ -1247,6 +1332,8 @@ int RGWHandler_ObjStore::allocate_formatter(struct req_state *s, int default_typ s->format = RGW_FORMAT_XML; } else if (format_str.compare("json") == 0) { s->format = RGW_FORMAT_JSON; + } else if (format_str.compare("html") == 0) { + s->format = RGW_FORMAT_HTML; } else { const char *accept = s->info.env->get("HTTP_ACCEPT"); if (accept) { @@ -1260,6 +1347,8 @@ int RGWHandler_ObjStore::allocate_formatter(struct req_state *s, int default_typ s->format = RGW_FORMAT_XML; } else if (strcmp(format_buf, "application/json") == 0) { s->format = RGW_FORMAT_JSON; + } else if (strcmp(format_buf, "text/html") == 0) { + s->format = RGW_FORMAT_HTML; } } } @@ -1275,11 +1364,14 @@ int RGWHandler_ObjStore::allocate_formatter(struct req_state *s, int default_typ case RGW_FORMAT_JSON: s->formatter = new JSONFormatter(false); break; + case RGW_FORMAT_HTML: + s->formatter = new HTMLFormatter(s->prot_flags & RGW_REST_WEBSITE); + break; default: return -EINVAL; }; - s->formatter->reset(); + //s->formatter->reset(); // All formatters should reset on create already return 0; } @@ -1346,6 +1438,14 @@ static http_op op_from_method(const char *method) return OP_UNKNOWN; } +int RGWHandler_ObjStore::init_permissions(RGWOp *op) +{ + if (op->get_type() == RGW_OP_CREATE_BUCKET) + return 0; + + return do_init_permissions(); +} + int RGWHandler_ObjStore::read_permissions(RGWOp *op_obj) { bool only_bucket; @@ -1428,9 +1528,6 @@ RGWRESTMgr *RGWRESTMgr::get_resource_mgr(struct req_state *s, const string& uri, { *out_uri = uri; - if (resources_by_size.empty()) - return this; - multimap::reverse_iterator iter; for (iter = resources_by_size.rbegin(); iter != resources_by_size.rend(); ++iter) { @@ -1483,24 +1580,59 @@ int RGWREST::preprocess(struct req_state *s, RGWClientIO *cio) ldout(s->cct, 10) << "host=" << info.host << dendl; string domain; string subdomain; - bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, - &subdomain); - ldout(s->cct, 20) << "subdomain=" << subdomain << " domain=" << domain - << " in_hosted_domain=" << in_hosted_domain << dendl; + bool in_hosted_domain_s3website = false; + bool in_hosted_domain = rgw_find_host_in_domains(info.host, &domain, &subdomain, hostnames_set); + + bool s3website_enabled = g_conf->rgw_enable_apis.find("s3website") != std::string::npos; + string s3website_domain; + string s3website_subdomain; + + if (s3website_enabled) { + in_hosted_domain_s3website = rgw_find_host_in_domains(info.host, &s3website_domain, &s3website_subdomain, hostnames_s3website_set); + if (in_hosted_domain_s3website) { + in_hosted_domain = true; // TODO: should hostnames be a strict superset of hostnames_s3website? + domain = s3website_domain; + subdomain = s3website_subdomain; + s->prot_flags |= RGW_REST_WEBSITE; + } + } + + ldout(s->cct, 20) + << "subdomain=" << subdomain + << " domain=" << domain + << " in_hosted_domain=" << in_hosted_domain + << " in_hosted_domain_s3website=" << in_hosted_domain_s3website + << dendl; - if (g_conf->rgw_resolve_cname && !in_hosted_domain) { + if (g_conf->rgw_resolve_cname && !in_hosted_domain && !in_hosted_domain_s3website) { string cname; bool found; int r = rgw_resolver->resolve_cname(info.host, cname, &found); if (r < 0) { - ldout(s->cct, 0) << "WARNING: rgw_resolver->resolve_cname() returned r=" << r << dendl; + ldout(s->cct, 0) << "WARNING: rgw_resolver->resolve_cname() returned r=" << r << dendl; } + if (found) { ldout(s->cct, 5) << "resolved host cname " << info.host << " -> " << cname << dendl; - in_hosted_domain = rgw_find_host_in_domains(cname, &domain, &subdomain); - ldout(s->cct, 20) << "subdomain=" << subdomain << " domain=" << domain - << " in_hosted_domain=" << in_hosted_domain << dendl; + in_hosted_domain = rgw_find_host_in_domains(cname, &domain, &subdomain, hostnames_set); + + if (s3website_enabled && !in_hosted_domain_s3website) { + in_hosted_domain_s3website = rgw_find_host_in_domains(cname, &s3website_domain, &s3website_subdomain, hostnames_s3website_set); + if (in_hosted_domain_s3website) { + in_hosted_domain = true; // TODO: should hostnames be a strict superset of hostnames_s3website? + domain = s3website_domain; + subdomain = s3website_subdomain; + s->prot_flags |= RGW_REST_WEBSITE; + } + } + + ldout(s->cct, 20) + << "subdomain=" << subdomain + << " domain=" << domain + << " in_hosted_domain=" << in_hosted_domain + << " in_hosted_domain_s3website=" << in_hosted_domain_s3website + << dendl; } } diff --git a/src/rgw/rgw_rest.h b/src/rgw/rgw_rest.h index ae04ab595ab06..db417173a8dd1 100644 --- a/src/rgw/rgw_rest.h +++ b/src/rgw/rgw_rest.h @@ -309,7 +309,12 @@ class RGWHandler_ObjStore : public RGWHandler { public: RGWHandler_ObjStore() {} virtual ~RGWHandler_ObjStore() {} + int init_permissions(RGWOp *op); int read_permissions(RGWOp *op); + virtual int retarget(RGWOp *op, RGWOp **new_op) { + *new_op = op; + return 0; + } virtual int authorize() = 0; }; @@ -318,6 +323,7 @@ class RGWHandler_ObjStore_SWIFT; class RGWHandler_SWIFT_Auth; class RGWHandler_ObjStore_S3; + class RGWRESTMgr { bool should_log; protected: @@ -372,7 +378,8 @@ extern void end_header(struct req_state *s, RGWOp *op = NULL, const char *content_type = NULL, const int64_t proposed_content_length = NO_CONTENT_LENGTH, - bool force_content_type = false); + bool force_content_type = false, + bool force_no_error = false); extern void dump_start(struct req_state *s); extern void list_all_buckets_start(struct req_state *s); extern void dump_owner(struct req_state *s, string& id, string& name, const char *section = NULL); @@ -382,7 +389,7 @@ extern void dump_etag(struct req_state *s, const char *etag); extern void dump_epoch_header(struct req_state *s, const char *name, time_t t); extern void dump_time_header(struct req_state *s, const char *name, time_t t); extern void dump_last_modified(struct req_state *s, time_t t); -extern void abort_early(struct req_state *s, RGWOp *op, int err); +extern void abort_early(struct req_state *s, RGWOp *op, int err, RGWHandler* handler); extern void dump_range(struct req_state *s, uint64_t ofs, uint64_t end, uint64_t total_size); extern void dump_continue(struct req_state *s); extern void list_all_buckets_end(struct req_state *s); diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 77bd5f935cd09..5ebd854342aa1 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -12,6 +12,7 @@ #include "rgw_rest.h" #include "rgw_rest_s3.h" +#include "rgw_rest_s3website.h" #include "rgw_auth_s3.h" #include "rgw_policy_s3.h" #include "rgw_user.h" @@ -19,6 +20,8 @@ #include "rgw_cors_s3.h" #include "rgw_client_io.h" +#include // for 'typeid' + #define dout_subsys ceph_subsys_rgw using namespace ceph::crypto; @@ -73,6 +76,36 @@ static struct response_attr_param resp_attr_params[] = { {NULL, NULL}, }; +int RGWGetObj_ObjStore_S3Website::send_response_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { + map::iterator iter; + iter = attrs.find(RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION); + if (iter != attrs.end()) { + bufferlist &bl = iter->second; + s->redirect = string(bl.c_str(), bl.length()); + s->err.http_ret = 301; + ldout(s->cct, 20) << __CEPH_ASSERT_FUNCTION << " redirectng per x-dss-website-redirect-location=" << s->redirect << dendl; + ret = -ERR_WEBSITE_REDIRECT; + set_req_state_err(s, ret); + dump_errno(s); + dump_content_length(s, 0); + dump_redirect(s, s->redirect); + end_header(s, this); + return ret; + } else { + return RGWGetObj_ObjStore_S3::send_response_data(bl, bl_ofs, bl_len); + } +} + +int RGWGetObj_ObjStore_S3Website::send_response_data_error() +{ + return RGWGetObj_ObjStore_S3::send_response_data_error(); +} + +int RGWGetObj_ObjStore_S3::send_response_data_error() +{ + bufferlist bl; + return send_response_data(bl, 0 , 0); +} int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { @@ -177,6 +210,9 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, off_ } } + if (kmsdata) + s->cio->print("x-jcs-server-side-encryption: \"%s\"\r\n", "AES256"); + for (struct response_attr_param *p = resp_attr_params; p->param; p++) { bool exists; string val = s->info.args.get(p->param, &exists); @@ -213,9 +249,14 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, off_ } done: - set_req_state_err(s, (partial_content && !ret) ? STATUS_PARTIAL_CONTENT : ret); - - dump_errno(s); + if (custom_http_ret) { + set_req_state_err(s, 0); + dump_errno(s, custom_http_ret); + } else { + set_req_state_err(s, (partial_content && !ret) ? STATUS_PARTIAL_CONTENT + : ret); + dump_errno(s); + } for (riter = response_attrs.begin(); riter != response_attrs.end(); ++riter) { s->cio->print("%s: %s\r\n", riter->first.c_str(), riter->second.c_str()); @@ -562,6 +603,78 @@ void RGWSetBucketVersioning_ObjStore_S3::send_response() end_header(s); } +int RGWSetBucketWebsite_ObjStore_S3::get_params() +{ +#define GET_BUCKET_WEBSITE_BUF_MAX (128 * 1024) + + char *data; + int len = 0; + int r = rgw_rest_read_all_input(s, &data, &len, GET_BUCKET_WEBSITE_BUF_MAX); + if (r < 0) { + return r; + } + + bufferlist bl; + bl.append(data, len); + + RGWXMLDecoder::XMLParser parser; + parser.init(); + + if (!parser.parse(data, len, 1)) { + string str(data, len); + ldout(s->cct, 5) << "failed to parse xml: " << str << dendl; + return -EINVAL; + } + + try { + RGWXMLDecoder::decode_xml("WebsiteConfiguration", website_conf, &parser, true); + } catch (RGWXMLDecoder::err& err) { + string str(data, len); + ldout(s->cct, 5) << "unexpected xml: " << str << dendl; + return -EINVAL; + } + + return 0; +} + +void RGWSetBucketWebsite_ObjStore_S3::send_response() +{ + if (ret) + set_req_state_err(s, ret); + dump_errno(s); + end_header(s); +} + +void RGWDeleteBucketWebsite_ObjStore_S3::send_response() +{ + if (!ret) { + ret = STATUS_NO_CONTENT; + } + set_req_state_err(s, ret); + dump_errno(s); + end_header(s); +} + +void RGWGetBucketWebsite_ObjStore_S3::send_response() +{ + if (ret) + set_req_state_err(s, ret); + dump_errno(s); + end_header(s, this, "application/xml"); + dump_start(s); + + if (ret < 0) { + return; + } + + RGWBucketWebsiteConf& conf = s->bucket_info.website_conf; + + s->formatter->open_object_section_in_ns("WebsiteConfiguration", + "http://doc.s3.amazonaws.com/doc/2006-03-01/"); + conf.dump_xml(s->formatter); + s->formatter->close_section(); // WebsiteConfiguration + rgw_flush_formatter_and_reset(s, s->formatter); +} static void dump_bucket_metadata(struct req_state *s, RGWBucketEnt& bucket) { @@ -1195,6 +1308,21 @@ int RGWPostObj_ObjStore_S3::get_params() attrs[attr_name] = attr_bl; } + // TODO: refactor this and the above loop to share code + piter = parts.find(RGW_AMZ_WEBSITE_REDIRECT_LOCATION); + if (piter != parts.end()) { + string n = piter->first; + string attr_name = RGW_ATTR_PREFIX; + attr_name.append(n); + /* need to null terminate it */ + bufferlist& data = piter->second.data; + string str = string(data.c_str(), data.length()); + + bufferlist attr_bl; + attr_bl.append(str.c_str(), str.size() + 1); + + attrs[attr_name] = attr_bl; + } int r = get_policy(); if (r < 0) @@ -1253,10 +1381,13 @@ int RGWPostObj_ObjStore_S3::get_policy() } dout(1) << "DSS API LOGGING: Action="<< resource_info.getAction() <<" Resource="<< resource_info.getResourceName() << " Tenant=" << resource_info.getTenantName() << dendl; + string source_ip = s->info.env->get("HTTP_X_FORWARDED_FOR","0.0.0.0"); + keystone_validator.append_header("X-Forwarded-For",source_ip); if (isTokenBasedAuth) { keystone_result = keystone_validator.validate_request(resource_info.getAction(), resource_info.getResourceName(), resource_info.getTenantName(), + source_ip, false, /* Is sign auth */ false, /* Is copy */ false, /* Is cross account */ @@ -1274,6 +1405,7 @@ int RGWPostObj_ObjStore_S3::get_policy() keystone_result = keystone_validator.validate_request(resource_info.getAction(), resource_info.getResourceName(), resource_info.getTenantName(), + source_ip, true, /* Is sign auth */ false, /* Is copy */ false, /* Is cross account */ @@ -2082,6 +2214,7 @@ RGWOp *RGWHandler_ObjStore_Service_S3::op_head() RGWOp *RGWHandler_ObjStore_Bucket_S3::get_obj_op(bool get_data) { + // Non-website mode if (get_data) return new RGWListBucket_ObjStore_S3; else @@ -2099,6 +2232,13 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_get() if (s->info.args.sub_resource_exists("versioning")) return new RGWGetBucketVersioning_ObjStore_S3; + if (s->info.args.sub_resource_exists("website")) { + if (!s->cct->_conf->rgw_enable_static_website) { + return NULL; + } + return new RGWGetBucketWebsite_ObjStore_S3; + } + if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } else if (is_cors_op()) { @@ -2125,6 +2265,12 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_put() return NULL; if (s->info.args.sub_resource_exists("versioning")) return new RGWSetBucketVersioning_ObjStore_S3; + if (s->info.args.sub_resource_exists("website")) { + if (!s->cct->_conf->rgw_enable_static_website) { + return NULL; + } + return new RGWSetBucketWebsite_ObjStore_S3; + } if (is_acl_op()) { return new RGWPutACLs_ObjStore_S3; } else if (is_cors_op()) { @@ -2138,6 +2284,14 @@ RGWOp *RGWHandler_ObjStore_Bucket_S3::op_delete() if (is_cors_op()) { return new RGWDeleteCORS_ObjStore_S3; } + + if (s->info.args.sub_resource_exists("website")) { + if (!s->cct->_conf->rgw_enable_static_website) { + return NULL; + } + return new RGWDeleteBucketWebsite_ObjStore_S3; + } + return new RGWDeleteBucket_ObjStore_S3; } @@ -2445,6 +2599,7 @@ int RGWHandler_ObjStore_S3::init(RGWRados *store, struct req_state *s, RGWClient int RGW_Auth_S3_Keystone_ValidateToken::validate_request(const string& action, const string& resource_name, const string& tenant_name, + const string& source_ip, const bool& is_sign_auth, const bool& is_copy, const bool& is_cross_account, @@ -2479,7 +2634,6 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_request(const string& action, << localAction << dendl; return -ENOTRECOVERABLE; } - /* Set required headers for keystone request * Recursive calls already have headers set */ if (!is_copy && !is_cross_account) { @@ -2495,6 +2649,7 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_request(const string& action, append_header("Content-Type", "application/json"); } + append_header("X-Forwarded-For",source_ip); /* Handle special case of copy */ bool isCopyAction = false; isCopyAction = (localAction.compare("CopyObject") == 0); @@ -2506,7 +2661,7 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_request(const string& action, string copy_src_str = copy_src.substr(0, pos); string copy_src_tenant = copy_src.substr(pos + 1); dout(0) << "DSS INFO: Validating for copy source" << dendl; - ret = validate_request(localAction, copy_src_str, copy_src_tenant, is_sign_auth, + ret = validate_request(localAction, copy_src_str, copy_src_tenant, source_ip, is_sign_auth, true, is_cross_account, is_url_token, is_infini_url_token, copy_src, token, auth_id, auth_token, auth_sign, objectname, iamerror); if (ret < 0) { @@ -2650,7 +2805,7 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_request(const string& action, // This case requires cross account validation. // Make recursive call with is_cross_account set to true dout(0) << "DSS INFO: Validating for cross account access" << dendl; - ret = validate_request(localAction, resource_name, tenant_name, + ret = validate_request(localAction, resource_name, tenant_name, source_ip, is_sign_auth, is_copy, true, is_url_token, is_infini_url_token, copy_src, token, auth_id, auth_token, auth_sign, objectname, iamerror); @@ -2842,9 +2997,15 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s) qsr = true; } else { /* anonymous access */ + + bool is_s3website = (s->prot_flags & RGW_REST_WEBSITE); + if (is_s3website) + { + init_anon_user(s); + return 0; + } + else return -EPERM; - //init_anon_user(s); - //return 0; } } else { // strncmp returns 0 on match. If even one of AWS or JCS match, dont return -EINVAL. @@ -2892,16 +3053,17 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s) if (s != NULL) { resource_object_name = s->object.name; } + string source_ip = s->info.env->get("HTTP_X_FORWARDED_FOR","0.0.0.0"); dout(1) << "DSS API LOGGING: Action=" << resource_info.getAction() << " Resource="<< resource_info.getResourceName() << " Tenant=" << resource_info.getTenantName() << " Object=" << resource_object_name << dendl; - if (isTokenBasedAuth) { keystone_result = keystone_validator.validate_request(resource_info.getAction(), resource_info.getResourceName(), resource_info.getTenantName(), + source_ip, false, /* Is sign auth */ false, /* Is copy */ false, /* Is cross account */ @@ -2919,6 +3081,7 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s) keystone_result = keystone_validator.validate_request(resource_info.getAction(), resource_info.getResourceName(), resource_info.getTenantName(), + source_ip, true, /* Is sign auth */ false, /* Is copy */ false, /* Is cross account */ @@ -3080,17 +3243,237 @@ int RGWHandler_Auth_S3::init(RGWRados *store, struct req_state *state, RGWClient RGWHandler *RGWRESTMgr_S3::get_handler(struct req_state *s) { - int ret = RGWHandler_ObjStore_S3::init_from_header(s, RGW_FORMAT_XML, false); + bool is_s3website = enable_s3website && (s->prot_flags & RGW_REST_WEBSITE); + int ret = RGWHandler_ObjStore_S3::init_from_header(s, is_s3website ? RGW_FORMAT_HTML : RGW_FORMAT_XML, false); if (ret < 0) return NULL; - if (s->bucket_name_str.empty()) - return new RGWHandler_ObjStore_Service_S3; + RGWHandler* handler; + // TODO: Make this more readable + if (is_s3website) { + if (s->bucket_name_str.empty()) { + handler = new RGWHandler_ObjStore_Service_S3Website; + } else if (s->object.empty()) { + handler = new RGWHandler_ObjStore_Bucket_S3Website; + } else { + handler = new RGWHandler_ObjStore_Obj_S3Website; + } + } else { + if (s->bucket_name_str.empty()) { + handler = new RGWHandler_ObjStore_Service_S3; + } else if (s->object.empty()) { + handler = new RGWHandler_ObjStore_Bucket_S3; + } else { + handler = new RGWHandler_ObjStore_Obj_S3; + } + } + + ldout(s->cct, 20) << __func__ << " handler=" << typeid(*handler).name() << dendl; + return handler; +} + +int RGWHandler_ObjStore_S3Website::retarget(RGWOp *op, RGWOp **new_op) { + *new_op = op; + ldout(s->cct, 10) << __func__ << "Starting retarget" << dendl; + + if (!(s->prot_flags & RGW_REST_WEBSITE)) + return 0; + + RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); + int ret = store->get_bucket_info(obj_ctx, s->bucket_name_str, s->bucket_info, NULL, &s->bucket_attrs); + if (ret < 0) { + // TODO-FUTURE: if the bucket does not exist, maybe expose it here? + return -ERR_NO_SUCH_BUCKET; + } + if (!s->bucket_info.has_website) { + // TODO-FUTURE: if the bucket has no WebsiteConfig, expose it here + return -ERR_NO_SUCH_WEBSITE_CONFIGURATION; + } - if (s->object.empty()) - return new RGWHandler_ObjStore_Bucket_S3; + rgw_obj_key new_obj; + s->bucket_info.website_conf.get_effective_key(s->object.name, &new_obj.name); + ldout(s->cct, 10) << "retarget get_effective_key " << s->object << " -> " << new_obj << dendl; + + RGWBWRoutingRule rrule; + bool should_redirect = s->bucket_info.website_conf.should_redirect(new_obj.name, 0, &rrule); + + if (should_redirect) { + const string& hostname = s->info.env->get("HTTP_HOST", ""); + const string& protocol = (s->info.env->get("SERVER_PORT_SECURE") ? "https" : "http"); + int redirect_code = 0; + rrule.apply_rule(protocol, hostname, s->object.name, &s->redirect, &redirect_code); + // APply a custom HTTP response code + if (redirect_code > 0) + s->err.http_ret = redirect_code; // Apply a custom HTTP response code + ldout(s->cct, 10) << "retarget redirect code=" << redirect_code << " proto+host:" << protocol << "://" << hostname << " -> " << s->redirect << dendl; + return -ERR_WEBSITE_REDIRECT; + } + + /* + * FIXME: if s->object != new_obj, drop op and create a new op to handle operation. Or + * remove this comment if it's not applicable anymore + */ + + s->object = new_obj; + + return 0; +} + +RGWOp *RGWHandler_ObjStore_S3Website::op_get() +{ + return get_obj_op(true); +} + +RGWOp *RGWHandler_ObjStore_S3Website::op_head() +{ + return get_obj_op(false); +} + +int RGWHandler_ObjStore_S3Website::serve_errordoc(int http_ret, const string& errordoc_key) { + int ret = 0; + s->formatter->reset(); /* Try to throw it all away */ + + RGWGetObj_ObjStore_S3Website* getop = (RGWGetObj_ObjStore_S3Website*) op_get(); + if(!getop) { + return -1; // Trigger double error handler + } + getop->init(store, s, this); + /*getop->range_str = NULL; + getop->if_mod = NULL; + getop->if_unmod = NULL; + getop->if_match = NULL; + getop->if_nomatch = NULL;*/ + s->object = errordoc_key; - return new RGWHandler_ObjStore_Obj_S3; + ret = init_permissions(getop); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, init_permissions ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + ret = read_permissions(getop); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, read_permissions ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + if (http_ret) { + getop->set_custom_http_response(http_ret); + } + + ret = getop->init_processing(); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, init_processing ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + ret = getop->verify_op_mask(); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, verify_op_mask ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + ret = getop->verify_permission(); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, verify_permission ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + ret = getop->verify_params(); + if (ret < 0) { + ldout(s->cct, 20) << "serve_errordoc failed, verify_params ret=" << ret << dendl; + return -1; // Trigger double error handler + } + + // No going back now + getop->pre_exec(); + /* + * FIXME Missing headers: + * With a working errordoc, the s3 error fields are rendered as HTTP headers, + * x-amz-error-code: NoSuchKey + * x-amz-error-message: The specified key does not exist. + * x-amz-error-detail-Key: foo + */ + getop->execute(); + getop->complete(); + return 0; + +} + +int RGWHandler_ObjStore_S3Website::error_handler(int err_no, + string* error_content) { + int new_err_no = -1; + const struct rgw_http_errors* r; + int http_error_code = -1; + r = search_err(err_no > 0 ? err_no : -err_no, RGW_HTTP_ERRORS, ARRAY_LEN(RGW_HTTP_ERRORS)); + if (r) { + http_error_code = r->http_ret; + } + ldout(s->cct, 10) << "RGWHandler_ObjStore_S3Website::error_handler err_no=" << err_no << " http_ret=" << http_error_code << dendl; + + RGWBWRoutingRule rrule; + bool should_redirect = s->bucket_info.website_conf.should_redirect(s->object.name, http_error_code, &rrule); + + if (should_redirect) { + const string& hostname = s->info.env->get("HTTP_HOST", ""); + const string& protocol = (s->info.env->get("SERVER_PORT_SECURE") ? "https" : "http"); + int redirect_code = 0; + rrule.apply_rule(protocol, hostname, s->object.name, &s->redirect, &redirect_code); + // APply a custom HTTP response code + if (redirect_code > 0) + s->err.http_ret = redirect_code; // Apply a custom HTTP response code + ldout(s->cct, 10) << "error handler redirect code=" << redirect_code << " proto+host:" << protocol << "://" << hostname << " -> " << s->redirect << dendl; + return -ERR_WEBSITE_REDIRECT; + } else if (err_no == -ERR_WEBSITE_REDIRECT) { + // Do nothing here, this redirect will be handled in abort_early's ERR_WEBSITE_REDIRECT block + // Do NOT fire the ErrorDoc handler + } else if (!s->bucket_info.website_conf.error_doc.empty()) { + /* This serves an entire page! + On success, it will return zero, and no further content should be sent to the socket + On failure, we need the double-error handler + */ + new_err_no = RGWHandler_ObjStore_S3Website::serve_errordoc(http_error_code, s->bucket_info.website_conf.error_doc); + if(new_err_no && new_err_no != -1) { + err_no = new_err_no; + } + } else { + ldout(s->cct, 20) << "No special error handling today!" << dendl; + } + + return err_no; +} + +RGWOp *RGWHandler_ObjStore_Obj_S3Website::get_obj_op(bool get_data) +{ + /** If we are in website mode, then it is explicitly impossible to run GET or + * HEAD on the actual directory. We must convert the request to run on the + * suffix object instead! + */ + RGWGetObj_ObjStore_S3Website *op = new RGWGetObj_ObjStore_S3Website; + op->set_get_data(get_data); + return op; +} + +RGWOp *RGWHandler_ObjStore_Bucket_S3Website::get_obj_op(bool get_data) +{ + /** If we are in website mode, then it is explicitly impossible to run GET or + * HEAD on the actual directory. We must convert the request to run on the + * suffix object instead! + */ + RGWGetObj_ObjStore_S3Website *op = new RGWGetObj_ObjStore_S3Website; + op->set_get_data(get_data); + return op; +} + +RGWOp *RGWHandler_ObjStore_Service_S3Website::get_obj_op(bool get_data) +{ + /** If we are in website mode, then it is explicitly impossible to run GET or + * HEAD on the actual directory. We must convert the request to run on the + * suffix object instead! + */ + RGWGetObj_ObjStore_S3Website *op = new RGWGetObj_ObjStore_S3Website; + op->set_get_data(get_data); + return op; } /* diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index a792764402f10..a060bb2473d9b 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -20,11 +20,17 @@ void rgw_get_errno_s3(struct rgw_http_errors *e, int err_no); class RGWGetObj_ObjStore_S3 : public RGWGetObj_ObjStore { +protected: + // Serving a custom error page from an object is really a 200 response with + // just the status line altered. + int custom_http_ret = 0; public: RGWGetObj_ObjStore_S3() {} ~RGWGetObj_ObjStore_S3() {} + int send_response_data_error(); int send_response_data(bufferlist& bl, off_t ofs, off_t len); + void set_custom_http_response(int http_ret) { custom_http_ret = http_ret; } }; class RGWListBuckets_ObjStore_S3 : public RGWListBuckets_ObjStore { @@ -86,6 +92,31 @@ class RGWSetBucketVersioning_ObjStore_S3 : public RGWSetBucketVersioning { void send_response(); }; +class RGWGetBucketWebsite_ObjStore_S3 : public RGWGetBucketWebsite { +public: + RGWGetBucketWebsite_ObjStore_S3() {} + ~RGWGetBucketWebsite_ObjStore_S3() {} + + void send_response(); +}; + +class RGWSetBucketWebsite_ObjStore_S3 : public RGWSetBucketWebsite { +public: + RGWSetBucketWebsite_ObjStore_S3() {} + ~RGWSetBucketWebsite_ObjStore_S3() {} + + int get_params(); + void send_response(); +}; + +class RGWDeleteBucketWebsite_ObjStore_S3 : public RGWDeleteBucketWebsite { +public: + RGWDeleteBucketWebsite_ObjStore_S3() {} + ~RGWDeleteBucketWebsite_ObjStore_S3() {} + + void send_response(); +}; + class RGWStatBucket_ObjStore_S3 : public RGWStatBucket_ObjStore { public: RGWStatBucket_ObjStore_S3() {} @@ -352,6 +383,7 @@ class RGW_Auth_S3_Keystone_ValidateToken : public RGWHTTPClient { int validate_request(const string& action, const string& resource_name, const string& tenant_name, + const string& source_ip, const bool& is_sign_auth, const bool& is_copy, const bool& is_cross_account, @@ -408,6 +440,10 @@ class RGWHandler_ObjStore_S3 : public RGWHandler_ObjStore { virtual int authorize() { return RGW_Auth_S3::authorize(store, s); } + virtual int retarget(RGWOp *op, RGWOp **new_op) { + *new_op = op; + return 0; + } }; class RGWHandler_ObjStore_Service_S3 : public RGWHandler_ObjStore_S3 { @@ -471,8 +507,10 @@ class RGWHandler_ObjStore_Obj_S3 : public RGWHandler_ObjStore_S3 { }; class RGWRESTMgr_S3 : public RGWRESTMgr { +private: + bool enable_s3website; public: - RGWRESTMgr_S3() {} + RGWRESTMgr_S3(bool enable_s3website) : enable_s3website(false) { this->enable_s3website = enable_s3website; } virtual ~RGWRESTMgr_S3() {} virtual RGWRESTMgr *get_resource_mgr(struct req_state *s, const string& uri) { @@ -642,4 +680,6 @@ class dss_endpoint { dss_endpoint() { } ~dss_endpoint() { } }; +class RGWHandler_ObjStore_Obj_S3Website; + #endif diff --git a/src/rgw/rgw_rest_s3website.h b/src/rgw/rgw_rest_s3website.h new file mode 100644 index 0000000000000..f8ead0ba7c61b --- /dev/null +++ b/src/rgw/rgw_rest_s3website.h @@ -0,0 +1,97 @@ +// -*- 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) 2015 Robin H. Johnson + * + * 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_REST_S3WEBSITE_H +#define CEPH_RGW_REST_S3WEBSITE_H + +#include "rgw_rest_s3.h" + +class RGWHandler_ObjStore_S3Website : public RGWHandler_ObjStore_S3 { +protected: + int retarget(RGWOp *op, RGWOp **new_op); + // TODO: this should be virtual I think, and ensure that it's always + // overridden, but that conflates that op_get/op_head are defined in this + // class and call this; and don't need to be overridden later. + virtual RGWOp *get_obj_op(bool get_data) { return NULL; } + RGWOp *op_get(); + RGWOp *op_head(); + // Only allowed to use GET+HEAD + RGWOp *op_put() { return NULL; } + RGWOp *op_delete() { return NULL; } + RGWOp *op_post() { return NULL; } + RGWOp *op_copy() { return NULL; } + RGWOp *op_options() { return NULL; } + + int serve_errordoc(int http_ret, const string &errordoc_key); +public: + RGWHandler_ObjStore_S3Website() : RGWHandler_ObjStore_S3() {} + virtual ~RGWHandler_ObjStore_S3Website() {} + virtual int error_handler(int err_no, string *error_content); +}; + +class RGWHandler_ObjStore_Service_S3Website : public RGWHandler_ObjStore_S3Website { +protected: + virtual RGWOp *get_obj_op(bool get_data); +public: + RGWHandler_ObjStore_Service_S3Website() {} + virtual ~RGWHandler_ObjStore_Service_S3Website() {} +}; + +class RGWHandler_ObjStore_Obj_S3Website : public RGWHandler_ObjStore_S3Website { +protected: + virtual RGWOp *get_obj_op(bool get_data); +public: + RGWHandler_ObjStore_Obj_S3Website() {} + virtual ~RGWHandler_ObjStore_Obj_S3Website() {} +}; + +/* The cross-inheritance from Obj to Bucket is deliberate! + * S3Websites do NOT support any bucket operations + */ +class RGWHandler_ObjStore_Bucket_S3Website : public RGWHandler_ObjStore_S3Website { +protected: + RGWOp *get_obj_op(bool get_data); +public: + RGWHandler_ObjStore_Bucket_S3Website() {} + virtual ~RGWHandler_ObjStore_Bucket_S3Website() {} +}; + +// TODO: do we actually need this? +class RGWGetObj_ObjStore_S3Website : public RGWGetObj_ObjStore_S3 +{ + friend class RGWHandler_REST_S3Website; +private: + bool is_errordoc_request; +public: + RGWGetObj_ObjStore_S3Website() : is_errordoc_request(false) {} + RGWGetObj_ObjStore_S3Website(bool is_errordoc_request) : is_errordoc_request(false) { this->is_errordoc_request = is_errordoc_request; } + ~RGWGetObj_ObjStore_S3Website() {} + int send_response_data_error(); + int send_response_data(bufferlist& bl, off_t ofs, off_t len); + // We override RGWGetObj_ObjStore::get_params here, to allow ignoring all + // conditional params for error pages. + int get_params() { + if (is_errordoc_request) { + range_str = NULL; + if_mod = NULL; + if_unmod = NULL; + if_match = NULL; + if_nomatch = NULL; + return 0; + } else { + return RGWGetObj_ObjStore_S3::get_params(); + } + } +}; + +#endif diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc index bcbd45002c896..d9e9190ca5308 100644 --- a/src/rgw/rgw_rest_swift.cc +++ b/src/rgw/rgw_rest_swift.cc @@ -399,10 +399,11 @@ int RGWCreateBucket_ObjStore_SWIFT::get_params() void RGWCreateBucket_ObjStore_SWIFT::send_response() { - if (!ret) - ret = STATUS_CREATED; - else if (ret == -ERR_BUCKET_EXISTS) + if (exist_ret == -ERR_BUCKET_EXISTS) { ret = STATUS_ACCEPTED; + } else if (!ret) { + ret = STATUS_CREATED; + } set_req_state_err(s, ret); dump_errno(s); /* Propose ending HTTP header with 0 Content-Length header. */ @@ -692,6 +693,19 @@ void RGWCopyObj_ObjStore_SWIFT::send_response() } } +int RGWGetObj_ObjStore_SWIFT::get_params() +{ + const string& mm = s->info.args.get("multipart-manifest"); + + return RGWGetObj_ObjStore::get_params(); +} + +int RGWGetObj_ObjStore_SWIFT::send_response_data_error() +{ + bufferlist bl; + return send_response_data(bl, 0, 0); +} + int RGWGetObj_ObjStore_SWIFT::send_response_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { string content_type; diff --git a/src/rgw/rgw_rest_swift.h b/src/rgw/rgw_rest_swift.h index 19028ab337b43..ebd0d33d01a5c 100644 --- a/src/rgw/rgw_rest_swift.h +++ b/src/rgw/rgw_rest_swift.h @@ -13,6 +13,8 @@ class RGWGetObj_ObjStore_SWIFT : public RGWGetObj_ObjStore { RGWGetObj_ObjStore_SWIFT() {} ~RGWGetObj_ObjStore_SWIFT() {} + int get_params(); + int send_response_data_error(); int send_response_data(bufferlist& bl, off_t ofs, off_t len); }; diff --git a/src/rgw/rgw_website.cc b/src/rgw/rgw_website.cc new file mode 100644 index 0000000000000..a69ffe16d2981 --- /dev/null +++ b/src/rgw/rgw_website.cc @@ -0,0 +1,119 @@ +// -*- 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) 2015 Yehuda Sadeh + * Copyright (C) 2015 Robin H. Johnson + * + * 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 "common/debug.h" +#include "common/ceph_json.h" + +#include "acconfig.h" + +#include +#include +#include +#include "include/types.h" +#include "rgw_website.h" + +using namespace std; + + +bool RGWBWRoutingRuleCondition::check_key_condition(const string& key) { + return (key.size() >= key_prefix_equals.size() && + key.compare(0, key_prefix_equals.size(), key_prefix_equals) == 0); +} + + +void RGWBWRoutingRule::apply_rule(const string& default_protocol, const string& default_hostname, + const string& key, string *new_url, int *redirect_code) +{ + RGWRedirectInfo& redirect = redirect_info.redirect; + + string protocol = (!redirect.protocol.empty() ? redirect.protocol : default_protocol); + string hostname = (!redirect.hostname.empty() ? redirect.hostname : default_hostname); + + *new_url = protocol + "://" + hostname + "/"; + + if (!redirect_info.replace_key_prefix_with.empty()) { + *new_url += redirect_info.replace_key_prefix_with; + *new_url += key.substr(condition.key_prefix_equals.size()); + } else if (!redirect_info.replace_key_with.empty()) { + *new_url += redirect_info.replace_key_with; + } else { + *new_url += key; + } + + if(redirect.http_redirect_code > 0) + *redirect_code = redirect.http_redirect_code; +} + +bool RGWBWRoutingRules::check_key_and_error_code_condition(const string &key, int error_code, RGWBWRoutingRule **rule) +{ + for (list::iterator iter = rules.begin(); iter != rules.end(); ++iter) { + if (iter->check_key_condition(key) && iter->check_error_code_condition(error_code)) { + *rule = &(*iter); + return true; + } + } + return false; +} + +bool RGWBWRoutingRules::check_key_condition(const string& key, RGWBWRoutingRule **rule) +{ + for (list::iterator iter = rules.begin(); iter != rules.end(); ++iter) { + if (iter->check_key_condition(key)) { + *rule = &(*iter); + return true; + } + } + return false; +} + +bool RGWBWRoutingRules::check_error_code_condition(const int http_error_code, RGWBWRoutingRule **rule) +{ + for (list::iterator iter = rules.begin(); iter != rules.end(); ++iter) { + if (iter->check_error_code_condition(http_error_code)) { + *rule = &(*iter); + return true; + } + } + return false; +} + +bool RGWBucketWebsiteConf::should_redirect(const string& key, const int http_error_code, RGWBWRoutingRule *redirect) +{ + RGWBWRoutingRule *rule; + if(!redirect_all.hostname.empty()) { + RGWBWRoutingRule redirect_all_rule; + redirect_all_rule.redirect_info.redirect = redirect_all; + redirect_all.http_redirect_code = 301; + *redirect = redirect_all_rule; + return true; + } else if (!routing_rules.check_key_and_error_code_condition(key, http_error_code, &rule)) { + return false; + } + + *redirect = *rule; + + return true; +} + +void RGWBucketWebsiteConf::get_effective_key(const string& key, string *effective_key) +{ + + if (key.empty()) { + *effective_key = index_doc_suffix; + } else if (key[key.size() - 1] == '/') { + *effective_key = key + index_doc_suffix; + } else { + *effective_key = key; + } +} diff --git a/src/rgw/rgw_website.h b/src/rgw/rgw_website.h new file mode 100644 index 0000000000000..6c1a92bb47603 --- /dev/null +++ b/src/rgw/rgw_website.h @@ -0,0 +1,200 @@ +// -*- 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) 2015 Yehuda Sadeh + * Copyright (C) 2015 Robin H. Johnson + * + * 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 RGW_WEBSITE_H +#define RGW_WEBSITE_H + +#include "rgw_xml.h" + +struct RGWRedirectInfo +{ + string protocol; + string hostname; + uint16_t http_redirect_code; + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(protocol, bl); + ::encode(hostname, bl); + ::encode(http_redirect_code, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START(1, bl); + ::decode(protocol, bl); + ::decode(hostname, bl); + ::decode(http_redirect_code, bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + void decode_json(JSONObj *obj); +}; +WRITE_CLASS_ENCODER(RGWRedirectInfo) + + +struct RGWBWRedirectInfo +{ + RGWRedirectInfo redirect; + string replace_key_prefix_with; + string replace_key_with; + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(redirect, bl); + ::encode(replace_key_prefix_with, bl); + ::encode(replace_key_with, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START(1, bl); + ::decode(redirect, bl); + ::decode(replace_key_prefix_with, bl); + ::decode(replace_key_with, bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + void dump_xml(Formatter *f) const; + void decode_json(JSONObj *obj); + void decode_xml(XMLObj *obj); +}; +WRITE_CLASS_ENCODER(RGWBWRedirectInfo) + +struct RGWBWRoutingRuleCondition +{ + string key_prefix_equals; + uint16_t http_error_code_returned_equals; + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(key_prefix_equals, bl); + ::encode(http_error_code_returned_equals, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START(1, bl); + ::decode(key_prefix_equals, bl); + ::decode(http_error_code_returned_equals, bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + void dump_xml(Formatter *f) const; + void decode_json(JSONObj *obj); + void decode_xml(XMLObj *obj); + + bool check_key_condition(const string& key); + bool check_error_code_condition(const int error_code) { + return (uint16_t)error_code == http_error_code_returned_equals; + } +}; +WRITE_CLASS_ENCODER(RGWBWRoutingRuleCondition) + +struct RGWBWRoutingRule +{ + RGWBWRoutingRuleCondition condition; + RGWBWRedirectInfo redirect_info; + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(condition, bl); + ::encode(redirect_info, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START(1, bl); + ::decode(condition, bl); + ::decode(redirect_info, bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + void dump_xml(Formatter *f) const; + void decode_json(JSONObj *obj); + void decode_xml(XMLObj *obj); + + bool check_key_condition(const string& key) { + return condition.check_key_condition(key); + } + bool check_error_code_condition(int error_code) { + return condition.check_error_code_condition(error_code); + } + + void apply_rule(const string& default_protocol, const string& default_hostname, const string& key, string *redirect, int *redirect_code); +}; +WRITE_CLASS_ENCODER(RGWBWRoutingRule) + +struct RGWBWRoutingRules +{ + list rules; + + 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; + void dump_xml(Formatter *f) const; + void decode_json(JSONObj *obj); + + bool check_key_condition(const string& key, RGWBWRoutingRule **rule); + bool check_error_code_condition(int error_code, RGWBWRoutingRule **rule); + bool check_key_and_error_code_condition(const string& key, const int error_code, RGWBWRoutingRule **rule); +}; +WRITE_CLASS_ENCODER(RGWBWRoutingRules) + +struct RGWBucketWebsiteConf +{ + RGWRedirectInfo redirect_all; + string index_doc_suffix; + string error_doc; + RGWBWRoutingRules routing_rules; + + RGWBucketWebsiteConf() {} + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(index_doc_suffix, bl); + ::encode(error_doc, bl); + ::encode(routing_rules, bl); + ::encode(redirect_all, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator& bl) { + DECODE_START(1, bl); + ::decode(index_doc_suffix, bl); + ::decode(error_doc, bl); + ::decode(routing_rules, bl); + ::decode(redirect_all, bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + void decode_json(JSONObj *obj); + void decode_xml(XMLObj *obj); + void dump_xml(Formatter *f) const; + + bool should_redirect(const string& key, const int http_error_code, RGWBWRoutingRule *redirect); + void get_effective_key(const string& key, string *effective_key); +}; +WRITE_CLASS_ENCODER(RGWBucketWebsiteConf) + +#endif diff --git a/src/rgw/rgw_xml.cc b/src/rgw/rgw_xml.cc index 3e38f8070107f..eda0887528234 100644 --- a/src/rgw/rgw_xml.cc +++ b/src/rgw/rgw_xml.cc @@ -148,17 +148,21 @@ RGWXMLParser:: XML_ParserFree(p); free(buf); - vector::iterator iter; - for (iter = objs.begin(); iter != objs.end(); ++iter) { + list::iterator iter; + for (iter = allocated_objs.begin(); iter != allocated_objs.end(); ++iter) { XMLObj *obj = *iter; delete obj; } } + bool RGWXMLParser::xml_start(const char *el, const char **attr) { XMLObj * obj = alloc_obj(el); if (!obj) { - obj = new XMLObj(); + unallocated_objs.push_back(XMLObj()); + obj = &unallocated_objs.back(); + } else { + allocated_objs.push_back(obj); } if (!obj->xml_start(cur_obj, el, attr)) return false; @@ -238,3 +242,254 @@ bool RGWXMLParser::parse(const char *_buf, int len, int done) return success; } + +void decode_xml_obj(unsigned long& val, XMLObj *obj) +{ + string& s = obj->get_data(); + const char *start = s.c_str(); + char *p; + + errno = 0; + val = strtoul(start, &p, 10); + + /* Check for various possible errors */ + + if ((errno == ERANGE && val == ULONG_MAX) || + (errno != 0 && val == 0)) { + throw RGWXMLDecoder::err("failed to number"); + } + + if (p == start) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + while (*p != '\0') { + if (!isspace(*p)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + p++; + } +} + + +void decode_xml_obj(long& val, XMLObj *obj) +{ + string s = obj->get_data(); + const char *start = s.c_str(); + char *p; + + errno = 0; + val = strtol(start, &p, 10); + + /* Check for various possible errors */ + + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || + (errno != 0 && val == 0)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + if (p == start) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + while (*p != '\0') { + if (!isspace(*p)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + p++; + } +} + +void decode_xml_obj(long long& val, XMLObj *obj) +{ + string s = obj->get_data(); + const char *start = s.c_str(); + char *p; + + errno = 0; + val = strtoll(start, &p, 10); + + /* Check for various possible errors */ + + if ((errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN)) || + (errno != 0 && val == 0)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + if (p == start) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + while (*p != '\0') { + if (!isspace(*p)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + p++; + } +} + +void decode_xml_obj(unsigned long long& val, XMLObj *obj) +{ + string s = obj->get_data(); + const char *start = s.c_str(); + char *p; + + errno = 0; + val = strtoull(start, &p, 10); + + /* Check for various possible errors */ + + if ((errno == ERANGE && val == ULLONG_MAX) || + (errno != 0 && val == 0)) { + throw RGWXMLDecoder::err("failed to number"); + } + + if (p == start) { + throw RGWXMLDecoder::err("failed to parse number"); + } + + while (*p != '\0') { + if (!isspace(*p)) { + throw RGWXMLDecoder::err("failed to parse number"); + } + p++; + } +} + +void decode_xml_obj(int& val, XMLObj *obj) +{ + long l; + decode_xml_obj(l, obj); +#if LONG_MAX > INT_MAX + if (l > INT_MAX || l < INT_MIN) { + throw RGWXMLDecoder::err("integer out of range"); + } +#endif + + val = (int)l; +} + +void decode_xml_obj(unsigned& val, XMLObj *obj) +{ + unsigned long l; + decode_xml_obj(l, obj); +#if ULONG_MAX > UINT_MAX + if (l > UINT_MAX) { + throw RGWXMLDecoder::err("unsigned integer out of range"); + } +#endif + + val = (unsigned)l; +} + +void decode_xml_obj(bool& val, XMLObj *obj) +{ + string s = obj->get_data(); + if (strcasecmp(s.c_str(), "true") == 0) { + val = true; + return; + } + if (strcasecmp(s.c_str(), "false") == 0) { + val = false; + return; + } + int i; + decode_xml_obj(i, obj); + val = (bool)i; +} + +void decode_xml_obj(bufferlist& val, XMLObj *obj) +{ + string s = obj->get_data(); + + bufferlist bl; + bl.append(s.c_str(), s.size()); + try { + val.decode_base64(bl); + } catch (buffer::error& err) { + throw RGWXMLDecoder::err("failed to decode base64"); + } +} + +void decode_xml_obj(utime_t& val, XMLObj *obj) +{ + string s = obj->get_data(); + uint64_t epoch; + uint64_t nsec; + int r = utime_t::parse_date(s, &epoch, &nsec); + if (r == 0) { + val = utime_t(epoch, nsec); + } else { + throw RGWXMLDecoder::err("failed to decode utime_t"); + } +} + +void encode_xml(const char *name, const string& val, Formatter *f) +{ + f->dump_string(name, val); +} + +void encode_xml(const char *name, const char *val, Formatter *f) +{ + f->dump_string(name, val); +} + +void encode_xml(const char *name, bool val, Formatter *f) +{ + string s; + if (val) + s = "True"; + else + s = "False"; + + f->dump_string(name, s); +} + +void encode_xml(const char *name, int val, Formatter *f) +{ + f->dump_int(name, val); +} + +void encode_xml(const char *name, long val, Formatter *f) +{ + f->dump_int(name, val); +} + +void encode_xml(const char *name, unsigned val, Formatter *f) +{ + f->dump_unsigned(name, val); +} + +void encode_xml(const char *name, unsigned long val, Formatter *f) +{ + f->dump_unsigned(name, val); +} + +void encode_xml(const char *name, unsigned long long val, Formatter *f) +{ + f->dump_unsigned(name, val); +} + +void encode_xml(const char *name, long long val, Formatter *f) +{ + f->dump_int(name, val); +} + +void encode_xml(const char *name, const utime_t& val, Formatter *f) +{ + val.gmtime(f->dump_stream(name)); +} + +void encode_xml(const char *name, const bufferlist& bl, Formatter *f) +{ + /* need to copy data from bl, as it is const bufferlist */ + bufferlist src = bl; + + bufferlist b64; + src.encode_base64(b64); + + string s(b64.c_str(), b64.length()); + + encode_xml(name, s, f); +} + diff --git a/src/rgw/rgw_xml.h b/src/rgw/rgw_xml.h index 164e97a70dc52..35f257aa97a35 100644 --- a/src/rgw/rgw_xml.h +++ b/src/rgw/rgw_xml.h @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -67,8 +68,12 @@ class RGWXMLParser : public XMLObj int buf_len; XMLObj *cur_obj; vector objs; + list allocated_objs; + list unallocated_objs; protected: - virtual XMLObj *alloc_obj(const char *el) = 0; + virtual XMLObj *alloc_obj(const char *el) { + return NULL; + } public: RGWXMLParser(); virtual ~RGWXMLParser(); @@ -85,4 +90,191 @@ class RGWXMLParser : public XMLObj bool success; }; +class RGWXMLDecoder { +public: + struct err { + string message; + + err(const string& m) : message(m) {} + }; + + class XMLParser : public RGWXMLParser { + public: + XMLParser() {} + virtual ~XMLParser() {} + } parser; + + RGWXMLDecoder(bufferlist& bl) { + if (!parser.parse(bl.c_str(), bl.length(), 1)) { + cout << "RGWXMLDecoder::err()" << std::endl; + throw RGWXMLDecoder::err("failed to parse XML input"); + } + } + + template + static bool decode_xml(const char *name, T& val, XMLObj *obj, bool mandatory = false); + + template + static bool decode_xml(const char *name, C& container, void (*cb)(C&, XMLObj *obj), XMLObj *obj, bool mandatory = false); + + template + static void decode_xml(const char *name, T& val, T& default_val, XMLObj *obj); +}; + +template +void decode_xml_obj(T& val, XMLObj *obj) +{ + val.decode_xml(obj); +} + +static inline void decode_xml_obj(string& val, XMLObj *obj) +{ + val = obj->get_data(); +} + +void decode_xml_obj(unsigned long long& val, XMLObj *obj); +void decode_xml_obj(long long& val, XMLObj *obj); +void decode_xml_obj(unsigned long& val, XMLObj *obj); +void decode_xml_obj(long& val, XMLObj *obj); +void decode_xml_obj(unsigned& val, XMLObj *obj); +void decode_xml_obj(int& val, XMLObj *obj); +void decode_xml_obj(bool& val, XMLObj *obj); +void decode_xml_obj(bufferlist& val, XMLObj *obj); +class utime_t; +void decode_xml_obj(utime_t& val, XMLObj *obj); + +template +void do_decode_xml_obj(list& l, const string& name, XMLObj *obj) +{ + l.clear(); + + XMLObjIter iter = obj->find(name); + XMLObj *o; + + while ((o = iter.get_next())) { + T val; + decode_xml_obj(val, o); + l.push_back(val); + } +} + +template +void do_decode_xml_obj(vector& l, const string& name, XMLObj *obj) +{ + l.clear(); + + XMLObjIter iter = obj->find(name); + XMLObj *o; + + while (o = iter.get_next()) { + T val; + decode_xml_obj(val, o); + l.push_back(val); + } +} + +template +bool RGWXMLDecoder::decode_xml(const char *name, T& val, XMLObj *obj, bool mandatory) +{ + XMLObjIter iter = obj->find(name); + XMLObj *o = iter.get_next(); + if (!o) { + if (mandatory) { + string s = "missing mandatory field " + string(name); + throw err(s); + } + val = T(); + return false; + } + + try { + decode_xml_obj(val, o); + } catch (err& e) { + string s = string(name) + ": "; + s.append(e.message); + throw err(s); + } + + return true; +} + +template +bool RGWXMLDecoder::decode_xml(const char *name, C& container, void (*cb)(C&, XMLObj *), XMLObj *obj, bool mandatory) +{ + container.clear(); + + XMLObjIter iter = obj->find(name); + XMLObj *o = iter.get_next(); + if (!o) { + if (mandatory) { + string s = "missing mandatory field " + string(name); + throw err(s); + } + return false; + } + + try { + decode_xml_obj(container, cb, o); + } catch (err& e) { + string s = string(name) + ": "; + s.append(e.message); + throw err(s); + } + + return true; +} + +template +void RGWXMLDecoder::decode_xml(const char *name, T& val, T& default_val, XMLObj *obj) +{ + XMLObjIter iter = obj->find(name); + XMLObj *o = iter.get_next(); + if (!o) { + val = default_val; + return; + } + + try { + decode_xml_obj(val, o); + } catch (err& e) { + val = default_val; + string s = string(name) + ": "; + s.append(e.message); + throw err(s); + } +} + +template +static void encode_xml(const char *name, const T& val, ceph::Formatter *f) +{ + f->open_object_section(name); + val.dump_xml(f); + f->close_section(); +} + +void encode_xml(const char *name, const string& val, ceph::Formatter *f); +void encode_xml(const char *name, const char *val, ceph::Formatter *f); +void encode_xml(const char *name, bool val, ceph::Formatter *f); +void encode_xml(const char *name, int val, ceph::Formatter *f); +void encode_xml(const char *name, unsigned val, ceph::Formatter *f); +void encode_xml(const char *name, long val, ceph::Formatter *f); +void encode_xml(const char *name, unsigned long val, ceph::Formatter *f); +void encode_xml(const char *name, long long val, ceph::Formatter *f); +void encode_xml(const char *name, const utime_t& val, ceph::Formatter *f); +void encode_xml(const char *name, const bufferlist& bl, ceph::Formatter *f); +void encode_xml(const char *name, long long val, ceph::Formatter *f); +void encode_xml(const char *name, long long unsigned val, ceph::Formatter *f); + +template +static void do_encode_xml(const char *name, const std::list& l, const char *entry_name, ceph::Formatter *f) +{ + f->open_array_section(name); + for (typename std::list::const_iterator iter = l.begin(); iter != l.end(); ++iter) { + encode_xml(entry_name, *iter, f); + } + f->close_section(); +} + + + #endif diff --git a/src/rgw/rgw_xml_enc.cc b/src/rgw/rgw_xml_enc.cc new file mode 100644 index 0000000000000..ff64efca7223b --- /dev/null +++ b/src/rgw/rgw_xml_enc.cc @@ -0,0 +1,131 @@ +// -*- 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) 2015 Yehuda Sadeh + * Copyright (C) 2015 Robin H. Johnson + * + * 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 "rgw_common.h" +#include "rgw_xml.h" + +#include "common/Formatter.h" + +#define dout_subsys ceph_subsys_rgw + +void RGWBWRedirectInfo::dump_xml(Formatter *f) const +{ + if (!redirect.protocol.empty()) { + encode_xml("Protocol", redirect.protocol, f); + } + if (!redirect.hostname.empty()) { + encode_xml("HostName", redirect.hostname, f); + } + if (redirect.http_redirect_code > 0) { + encode_xml("HttpRedirectCode", (int)redirect.http_redirect_code, f); + } + if (!replace_key_prefix_with.empty()) { + encode_xml("ReplaceKeyPrefixWith", replace_key_prefix_with, f); + } + if (!replace_key_with.empty()) { + encode_xml("ReplaceKeyWith", replace_key_with, f); + } +} + +void RGWBWRedirectInfo::decode_xml(XMLObj *obj) { + RGWXMLDecoder::decode_xml("Protocol", redirect.protocol, obj); + RGWXMLDecoder::decode_xml("HostName", redirect.hostname, obj); + int code = 0; + RGWXMLDecoder::decode_xml("HttpRedirectCode", code, obj); + redirect.http_redirect_code = code; + RGWXMLDecoder::decode_xml("ReplaceKeyPrefixWith", replace_key_prefix_with, obj); + RGWXMLDecoder::decode_xml("ReplaceKeyWith", replace_key_with, obj); +} + +void RGWBWRoutingRuleCondition::dump_xml(Formatter *f) const +{ + if (!key_prefix_equals.empty()) { + encode_xml("KeyPrefixEquals", key_prefix_equals, f); + } + if (http_error_code_returned_equals > 0) { + encode_xml("HttpErrorCodeReturnedEquals", (int)http_error_code_returned_equals, f); + } +} + +void RGWBWRoutingRuleCondition::decode_xml(XMLObj *obj) { + RGWXMLDecoder::decode_xml("KeyPrefixEquals", key_prefix_equals, obj); + int code = 0; + RGWXMLDecoder::decode_xml("HttpErrorCodeReturnedEquals", code, obj); + http_error_code_returned_equals = code; +} + +void RGWBWRoutingRule::dump_xml(Formatter *f) const +{ + encode_xml("Condition", condition, f); + encode_xml("Redirect", redirect_info, f); +} + +void RGWBWRoutingRule::decode_xml(XMLObj *obj) { + RGWXMLDecoder::decode_xml("Condition", condition, obj); + RGWXMLDecoder::decode_xml("Redirect", redirect_info, obj); +} + +static void encode_xml(const char *name, const std::list& l, ceph::Formatter *f) +{ + do_encode_xml("RoutingRules", l, "RoutingRule", f); +} + +void RGWBucketWebsiteConf::dump_xml(Formatter *f) const +{ + if (!redirect_all.hostname.empty()) { + f->open_object_section("RedirectAllRequestsTo"); + encode_xml("HostName", redirect_all.hostname, f); + if (!redirect_all.protocol.empty()) { + encode_xml("Protocol", redirect_all.protocol, f); + } + f->close_section(); + } + if (!index_doc_suffix.empty()) { + f->open_object_section("IndexDocument"); + encode_xml("Suffix", index_doc_suffix, f); + f->close_section(); + } + if (!error_doc.empty()) { + f->open_object_section("ErrorDocument"); + encode_xml("Key", error_doc, f); + f->close_section(); + } + if (!routing_rules.rules.empty()) { + encode_xml("RoutingRules", routing_rules.rules, f); + } +} + +void decode_xml_obj(list& l, XMLObj *obj) +{ + do_decode_xml_obj(l, "RoutingRule", obj); +} + +void RGWBucketWebsiteConf::decode_xml(XMLObj *obj) { + XMLObj *o = obj->find_first("RedirectAllRequestsTo"); + if (o) { + RGWXMLDecoder::decode_xml("HostName", redirect_all.hostname, o, true); + RGWXMLDecoder::decode_xml("Protocol", redirect_all.protocol, o); + } else { + o = obj->find_first("IndexDocument"); + if (o) { + RGWXMLDecoder::decode_xml("Suffix", index_doc_suffix, o); + } + o = obj->find_first("ErrorDocument"); + if (o) { + RGWXMLDecoder::decode_xml("Key", error_doc, o); + } + RGWXMLDecoder::decode_xml("RoutingRules", routing_rules.rules, obj); + } +} + diff --git a/src/test/formatter.cc b/src/test/formatter.cc index aab7e59259504..3913cc6f15420 100644 --- a/src/test/formatter.cc +++ b/src/test/formatter.cc @@ -14,6 +14,7 @@ #include "test/unit.h" #include "common/Formatter.h" +#include "common/HTMLFormatter.h" #include #include @@ -130,7 +131,7 @@ TEST(XmlFormatter, DTD) { ostringstream oss; XMLFormatter fmt(false); - fmt.write_raw_data(XMLFormatter::XML_1_DTD); + fmt.output_header(); fmt.open_array_section("foo"); fmt.dump_stream("blah") << "hithere"; fmt.dump_float("pi", 3.14); @@ -144,7 +145,7 @@ TEST(XmlFormatter, Clear) { ostringstream oss; XMLFormatter fmt(false); - fmt.write_raw_data(XMLFormatter::XML_1_DTD); + fmt.output_header(); fmt.open_array_section("foo"); fmt.dump_stream("blah") << "hithere"; fmt.dump_float("pi", 3.14); @@ -167,7 +168,7 @@ TEST(XmlFormatter, NamespaceTest) { ostringstream oss; XMLFormatter fmt(false); - fmt.write_raw_data(XMLFormatter::XML_1_DTD); + fmt.output_header(); fmt.open_array_section_in_ns("foo", "http://s3.amazonaws.com/doc/2006-03-01/"); fmt.dump_stream("blah") << "hithere"; @@ -197,3 +198,145 @@ TEST(XmlFormatter, DumpFormatNameSpaceTest) { fmt.flush(oss2); ASSERT_EQ(oss2.str(),"bar"); } + +TEST(HtmlFormatter, Simple1) { + ostringstream oss; + HTMLFormatter fmt(false); + fmt.open_object_section("foo"); + fmt.dump_int("a", 1); + fmt.dump_int("b", 2); + fmt.dump_int("c", 3); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "
  • a: 1
  • b: 2
  • c: 3
  • "); +} + +TEST(HtmlFormatter, Simple2) { + ostringstream oss; + HTMLFormatter fmt(false); + fmt.open_object_section("foo"); + fmt.open_object_section("bar"); + fmt.dump_int("int", 0xf00000000000ll); + fmt.dump_unsigned("unsigned", 0x8000000000000001llu); + fmt.dump_float("float", 1.234); + fmt.close_section(); + fmt.dump_string("string", "str"); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "\ +
  • int: 263882790666240
  • \ +
  • unsigned: 9223372036854775809
  • \ +
  • float: 1.234
  • \ +
  • string: str
  • \ +
    "); +} + +TEST(HtmlFormatter, Empty) { + ostringstream oss; + HTMLFormatter fmt(false); + fmt.flush(oss); + ASSERT_EQ(oss.str(), ""); +} + +TEST(HtmlFormatter, DumpStream1) { + ostringstream oss; + HTMLFormatter fmt(false); + fmt.dump_stream("blah") << "hithere"; + fmt.flush(oss); + ASSERT_EQ(oss.str(), "
  • blah: hithere
  • "); +} + +TEST(HtmlFormatter, DumpStream2) { + ostringstream oss; + HTMLFormatter fmt(false); + + fmt.open_array_section("foo"); + fmt.dump_stream("blah") << "hithere"; + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "
  • blah: hithere
  • "); +} + +TEST(HtmlFormatter, DumpStream3) { + ostringstream oss; + HTMLFormatter fmt(false); + + fmt.open_array_section("foo"); + fmt.dump_stream("blah") << "hithere"; + fmt.dump_float("pi", 3.14); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "
  • blah: hithere
  • pi: 3.14
  • "); +} + +TEST(HtmlFormatter, DTD) { + ostringstream oss; + HTMLFormatter fmt(false); + + fmt.write_raw_data(HTMLFormatter::XML_1_DTD); + fmt.open_array_section("foo"); + fmt.dump_stream("blah") << "hithere"; + fmt.dump_float("pi", 3.14); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "" + "
  • blah: hithere
  • pi: 3.14
  • "); +} + +TEST(HtmlFormatter, Clear) { + ostringstream oss; + HTMLFormatter fmt(false); + + fmt.write_raw_data(HTMLFormatter::XML_1_DTD); + fmt.open_array_section("foo"); + fmt.dump_stream("blah") << "hithere"; + fmt.dump_float("pi", 3.14); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "" + "
  • blah: hithere
  • pi: 3.14
  • "); + + ostringstream oss2; + fmt.flush(oss2); + ASSERT_EQ(oss2.str(), ""); + + ostringstream oss3; + fmt.reset(); + fmt.flush(oss3); + ASSERT_EQ(oss3.str(), ""); +} + +TEST(HtmlFormatter, NamespaceTest) { + ostringstream oss; + HTMLFormatter fmt(false); + + fmt.write_raw_data(HTMLFormatter::XML_1_DTD); + fmt.open_array_section_in_ns("foo", + "http://s3.amazonaws.com/doc/2006-03-01/"); + fmt.dump_stream("blah") << "hithere"; + fmt.dump_float("pi", 3.14); + fmt.close_section(); + fmt.flush(oss); + ASSERT_EQ(oss.str(), "" + "" + "
  • blah: hithere
  • pi: 3.14
  • "); +} + +TEST(HtmlFormatter, DumpFormatNameSpaceTest) { + ostringstream oss1; + HTMLFormatter fmt(false); + + fmt.dump_format_ns("foo", + "http://s3.amazonaws.com/doc/2006-03-01/", + "%s","bar"); + fmt.flush(oss1); + ASSERT_EQ(oss1.str(), + "
  • foo: bar
  • "); + + // Testing with a null ns..should be same as dump format + ostringstream oss2; + fmt.reset(); + fmt.dump_format_ns("foo",NULL,"%s","bar"); + fmt.flush(oss2); + ASSERT_EQ(oss2.str(),"
  • foo: bar
  • "); +} diff --git a/src/tracing/Makefile.am b/src/tracing/Makefile.am index 16d300ec1dbbb..510c0bc75732e 100644 --- a/src/tracing/Makefile.am +++ b/src/tracing/Makefile.am @@ -22,7 +22,7 @@ nodist_libosd_tp_la_SOURCES = \ pg.h \ pg.c endif -libosd_tp_la_LIBADD = -llttng-ust -ldl +libosd_tp_la_LIBADD = -ldl -llttng-ust libosd_tp_la_CPPFLAGS = -DTRACEPOINT_PROBE_DYNAMIC_LINKAGE libosd_tp_la_LDFLAGS = @@ -31,7 +31,7 @@ nodist_librados_tp_la_SOURCES = \ librados.c \ librados.h endif -librados_tp_la_LIBADD = -llttng-ust -ldl +librados_tp_la_LIBADD = -ldl -llttng-ust librados_tp_la_CPPFLAGS = -DTRACEPOINT_PROBE_DYNAMIC_LINKAGE librados_tp_la_CFLAGS = -I$(top_srcdir)/src $(AM_CFLAGS) librados_tp_la_LDFLAGS = @@ -41,7 +41,7 @@ nodist_librbd_tp_la_SOURCES = \ librbd.c \ librbd.h endif -librbd_tp_la_LIBADD = -llttng-ust -ldl +librbd_tp_la_LIBADD = -ldl -llttng-ust librbd_tp_la_CPPFLAGS = -DTRACEPOINT_PROBE_DYNAMIC_LINKAGE librbd_tp_la_CFLAGS = -I$(top_srcdir)/src $(AM_CFLAGS) librbd_tp_la_LDFLAGS = @@ -51,7 +51,7 @@ nodist_libos_tp_la_SOURCES = \ objectstore.c \ objectstore.h endif -libos_tp_la_LIBADD = -llttng-ust -ldl +libos_tp_la_LIBADD = -ldl -llttng-ust libos_tp_la_CPPFLAGS = -DTRACEPOINT_PROBE_DYNAMIC_LINKAGE libos_tp_la_CFLAGS = -I$(top_srcdir)/src $(AM_CFLAGS) libos_tp_la_LDFLAGS =