From: Abhishek Lekshmanan Date: Wed, 21 Jun 2017 12:09:49 +0000 (+0200) Subject: rgw: implement get/put object tags for S3 X-Git-Tag: v12.1.2~1^2~48^2~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=5fb068114bb3da2f8fabea89160a8453f861dc96;p=ceph.git rgw: implement get/put object tags for S3 - New api for get/put/delete object tags - PUT Object supports x-amz-tagging - POST API supports x-amz-tagging This is similar to AWS S3's get/put object tags, tags are stored as an xattr under the user.rgw.tags key, and are restricted to max 10 tags per object and key size of 128 and a val size of 256. The API does not reuse the existing `x-amz-meta` tags. For PUT obj to optionally accept `x-amz-tagging` and this will end up storing the tags under x-amz-tagging xattr, the input is expected to be in the url encoded kv format, we return InvalidTagError if we cannot parse the tags A future TODO: use the same xml parser for put and post apis Signed-off-by: Abhishek Lekshmanan Conflicts: src/rgw/rgw_rest_s3.cc src/rgw/rgw_tag.cc mostly trivial conflicts from AWSv4 rework, importantly url_decode's signature was changed for the nicer, and moved rgw_tag.cc to the newer url_decode format Conflicts: src/rgw/rgw_common.h conflict as another PR that used up the error code before this was merged --- diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index c6fa7c0bac88..c5017c7654e9 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -111,6 +111,8 @@ set(rgw_a_srcs rgw_rest_user.cc rgw_role.cc rgw_swift_auth.cc + rgw_tag.cc + rgw_tag_s3.cc rgw_tools.cc rgw_usage.cc rgw_user.cc diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 0788974635df..de32a888fc20 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -38,6 +38,7 @@ static const auto signed_subresources = { "response-content-language", "response-content-type", "response-expires", + "tagging", "torrent", "uploadId", "uploads", diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index b6b38fbb46e9..d4e494073ca6 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -73,6 +73,7 @@ rgw_http_errors rgw_http_s3_errors({ { ERR_TOO_MANY_BUCKETS, {400, "TooManyBuckets" }}, { ERR_MALFORMED_XML, {400, "MalformedXML" }}, { ERR_AMZ_CONTENT_SHA256_MISMATCH, {400, "XAmzContentSHA256Mismatch" }}, + { ERR_INVALID_TAG, {400, "InvalidTag"}}, { ERR_LENGTH_REQUIRED, {411, "MissingContentLength" }}, { EACCES, {403, "AccessDenied" }}, { EPERM, {403, "AccessDenied" }}, @@ -94,6 +95,7 @@ rgw_http_errors rgw_http_s3_errors({ { ERR_USER_EXIST, {409, "UserAlreadyExists" }}, { ERR_EMAIL_EXIST, {409, "EmailExists" }}, { ERR_KEY_EXIST, {409, "KeyExists"}}, + { ERR_TAG_CONFLICT, {409, "OperationAborted"}}, { ERR_INVALID_SECRET_KEY, {400, "InvalidSecretKey"}}, { ERR_INVALID_KEY_TYPE, {400, "InvalidKeyType"}}, { ERR_INVALID_CAP, {400, "InvalidCapability"}}, @@ -945,7 +947,8 @@ void RGWHTTPArgs::append(const string& name, const string& val) (name.compare("versioning") == 0) || (name.compare("website") == 0) || (name.compare("requestPayment") == 0) || - (name.compare("torrent") == 0)) { + (name.compare("torrent") == 0) || + (name.compare("tagging") == 0)) { sub_resources[name] = val; } else if (name[0] == 'r') { // root of all evil if ((name.compare("response-content-type") == 0) || diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index b9fdccc606b0..bf05d13a12f8 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -49,6 +49,7 @@ using ceph::crypto::MD5; #define RGW_AMZ_PREFIX "x-amz-" #define RGW_AMZ_META_PREFIX RGW_AMZ_PREFIX "meta-" #define RGW_AMZ_WEBSITE_REDIRECT_LOCATION RGW_AMZ_PREFIX "website-redirect-location" +#define RGW_AMZ_TAG_COUNT RGW_AMZ_PREFIX "tagging-count" #define RGW_SYS_PARAM_PREFIX "rgwx-" @@ -78,6 +79,7 @@ using ceph::crypto::MD5; #define RGW_ATTR_PG_VER RGW_ATTR_PREFIX "pg_ver" #define RGW_ATTR_SOURCE_ZONE RGW_ATTR_PREFIX "source_zone" +#define RGW_ATTR_TAGS RGW_ATTR_PREFIX RGW_AMZ_PREFIX "tagging" #define RGW_ATTR_TEMPURL_KEY1 RGW_ATTR_META_PREFIX "temp-url-key" #define RGW_ATTR_TEMPURL_KEY2 RGW_ATTR_META_PREFIX "temp-url-key-2" @@ -206,6 +208,8 @@ using ceph::crypto::MD5; #define ERR_DELETE_CONFLICT 2206 #define ERR_NO_SUCH_BUCKET_POLICY 2207 #define ERR_INVALID_LOCATION_CONSTRAINT 2208 +#define ERR_TAG_CONFLICT 2209 +#define ERR_INVALID_TAG 2210 #define ERR_BUSY_RESHARDING 2300 @@ -472,7 +476,9 @@ enum RGWOpType { RGW_OP_PUT_BUCKET_POLICY, RGW_OP_GET_BUCKET_POLICY, RGW_OP_DELETE_BUCKET_POLICY, - + RGW_OP_PUT_OBJ_TAGGING, + RGW_OP_GET_OBJ_TAGGING, + RGW_OP_DELETE_OBJ_TAGGING, /* rgw specific */ RGW_OP_ADMIN_SET_METADATA, RGW_OP_GET_OBJ_LAYOUT, @@ -2221,7 +2227,6 @@ extern std::string url_decode(const boost::string_view& src_str, extern void url_encode(const std::string& src, string& dst); extern std::string url_encode(const std::string& src); - /* destination should be CEPH_CRYPTO_HMACSHA1_DIGESTSIZE bytes long */ extern void calc_hmac_sha1(const char *key, int key_len, const char *msg, int msg_len, char *dest); diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index f700339f0147..3590bdd50721 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -40,6 +40,7 @@ #include "rgw_client_io.h" #include "rgw_compression.h" #include "rgw_role.h" +#include "rgw_tag_s3.h" #include "cls/lock/cls_lock_client.h" #include "cls/rgw/cls_rgw_client.h" @@ -690,6 +691,95 @@ int RGWOp::verify_op_mask() return 0; } +int RGWGetObjTags::verify_permission() +{ + if (!verify_object_permission(s, RGW_PERM_READ)) + return -EACCES; + + return 0; +} + +void RGWGetObjTags::pre_exec(){ + rgw_bucket_object_pre_exec(s); +} + +void RGWGetObjTags::execute() +{ + rgw_obj obj; + map attrs; + + obj = rgw_obj(s->bucket, s->object); + + store->set_atomic(s->obj_ctx, obj); + + op_ret = get_obj_attrs(store, s, obj, attrs); + auto tags = attrs.find(RGW_ATTR_TAGS); + if(tags != attrs.end()){ + has_tags = true; + tags_bl.append(tags->second); + } + send_response_data(tags_bl); +} + +int RGWPutObjTags::verify_permission() +{ + if (!verify_object_permission(s, RGW_PERM_WRITE)) { + return -EACCES; + } + return 0; +} + +void RGWPutObjTags::execute() +{ + + op_ret = get_params(); + if (op_ret < 0) + return; + + + if (s->object.empty()){ + op_ret= -EINVAL; // we only support tagging on existing objects + return; + } + + rgw_obj obj; + obj = rgw_obj(s->bucket, s->object); + store->set_atomic(s->obj_ctx, obj); + op_ret = modify_obj_attr(store, s, obj, RGW_ATTR_TAGS, tags_bl); + if (op_ret == -ECANCELED){ + op_ret = -ERR_TAG_CONFLICT; + } +} + +void RGWDeleteObjTags::pre_exec(){ + rgw_bucket_object_pre_exec(s); +} + + +int RGWDeleteObjTags::verify_permission(){ + + if (!s->object.empty()){ + if(!verify_object_permission(s, RGW_PERM_WRITE)) { + return -EACCES; + } + } + return 0; +} + +void RGWDeleteObjTags::execute() { + if (s->object.empty()) + return; + + rgw_obj obj; + obj = rgw_obj(s->bucket, s->object); + store->set_atomic(s->obj_ctx, obj); + map attrs; + map rmattr; + bufferlist bl; + rmattr[RGW_ATTR_TAGS] = bl; + op_ret = store->set_attrs(s->obj_ctx, s->bucket_info, obj, attrs, &rmattr); +} + int RGWOp::do_aws4_auth_completion() { ldout(s->cct, 5) << "NOTICE: call to do_aws4_auth_completion" << dendl; @@ -3375,6 +3465,7 @@ void RGWPutObj::execute() populate_with_generic_attrs(s, attrs); rgw_get_request_metadata(s->cct, s->info, attrs); encode_delete_at_attr(delete_at, attrs); + encode_obj_tags_attr(obj_tags.get(), attrs); /* Add a custom metadata to expose the information whether an object * is an SLO or not. Appending the attribute must be performed AFTER diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index de48abcd0d12..632367b9b0ac 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -41,6 +41,7 @@ #include "rgw_lc.h" #include "rgw_torrent.h" +#include "rgw_tag.h" #include "include/assert.h" @@ -326,6 +327,49 @@ public: } }; +class RGWGetObjTags : public RGWOp { + protected: + bufferlist tags_bl; + bool has_tags{false}; + public: + int verify_permission(); + void execute(); + void pre_exec(); + + virtual void send_response_data(bufferlist& bl) = 0; + virtual const string name() noexcept override { return "get_obj_tags"; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_READ; } + RGWOpType get_type() { return RGW_OP_GET_OBJ_TAGGING; } + +}; + +class RGWPutObjTags : public RGWOp { + protected: + bufferlist tags_bl; + public: + int verify_permission(); + void execute(); + + virtual void send_response() = 0; + virtual int get_params() = 0; + virtual const string name() { return "put_obj_tags"; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_WRITE; } + RGWOpType get_type() { return RGW_OP_PUT_OBJ_TAGGING; } + +}; + +class RGWDeleteObjTags: public RGWOp { + public: + void pre_exec(); + int verify_permission(); + void execute(); + + virtual void send_response() = 0; + virtual const string name() { return "delete_obj_tags"; } + virtual uint32_t op_mask() { return RGW_OP_TYPE_DELETE; } + RGWOpType get_type() { return RGW_OP_DELETE_OBJ_TAGGING;} +}; + class RGWBulkDelete : public RGWOp { public: struct acct_path_t { @@ -931,6 +975,7 @@ protected: string etag; bool chunked_upload; RGWAccessControlPolicy policy; + std::unique_ptr obj_tags; const char *dlo_manifest; RGWSLOInfo *slo_info; map attrs; @@ -1881,6 +1926,20 @@ static inline void encode_delete_at_attr(boost::optional delete attrs[RGW_ATTR_DELETE_AT] = delatbl; } /* encode_delete_at_attr */ +static inline void encode_obj_tags_attr(RGWObjTags* obj_tags, map& attrs) +{ + if (obj_tags == nullptr){ + // we assume the user submitted a tag format which we couldn't parse since + // this wouldn't be parsed later by get/put obj tags, lets delete if the + // attr was populated + return; + } + + bufferlist tagsbl; + obj_tags->encode(tagsbl); + attrs[RGW_ATTR_TAGS] = tagsbl; +} + static inline int encode_dlo_manifest_attr(const char * const dlo_manifest, map& attrs) { diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index 591c0a52e729..a039034be630 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -1941,7 +1941,9 @@ int RGWHandler_REST::read_permissions(RGWOp* op_obj) only_bucket = true; break; case OP_DELETE: - only_bucket = true; + if (!s->info.args.exists("tagging")){ + only_bucket = true; + } break; case OP_OPTIONS: only_bucket = true; diff --git a/src/rgw/rgw_rest.h b/src/rgw/rgw_rest.h index 11c951e38b30..615ececc595c 100644 --- a/src/rgw/rgw_rest.h +++ b/src/rgw/rgw_rest.h @@ -161,6 +161,18 @@ public: int get_params() override; }; +class RGWGetObjTags_ObjStore : public RGWGetObjTags { +public: + RGWGetObjTags_ObjStore() {}; + ~RGWGetObjTags_ObjStore() {}; +}; + +class RGWPutObjTags_ObjStore: public RGWPutObjTags { +public: + RGWPutObjTags_ObjStore() {}; + ~RGWPutObjTags_ObjStore() {}; +}; + class RGWListBuckets_ObjStore : public RGWListBuckets { public: RGWListBuckets_ObjStore() {} diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 155f1d37c7b3..fe6f5d4271ae 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -10,6 +10,7 @@ #include "common/utf8.h" #include "common/ceph_json.h" #include "common/safe_io.h" +#include "common/backport14.h" #include #include #include @@ -23,6 +24,7 @@ #include "rgw_user.h" #include "rgw_cors.h" #include "rgw_cors_s3.h" +#include "rgw_tag_s3.h" #include "rgw_client_io.h" @@ -290,6 +292,15 @@ int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, /* User custom metadata. */ name += sizeof(RGW_ATTR_PREFIX) - 1; dump_header(s, name, iter->second); + } else if (iter->first.compare(RGW_ATTR_TAGS) == 0) { + RGWObjTags obj_tags; + try{ + bufferlist::iterator it = iter->second.begin(); + obj_tags.decode(it); + } catch (buffer::error &err) { + ldout(s->cct,0) << "Error caught buffer::error couldn't decode TagSet " << dendl; + } + dump_header(s, RGW_AMZ_TAG_COUNT, obj_tags.count()); } } } @@ -346,6 +357,97 @@ int RGWGetObj_ObjStore_S3::get_decrypt_filter(std::unique_ptr *fil return res; } +void RGWGetObjTags_ObjStore_S3::send_response_data(bufferlist& bl) +{ + dump_errno(s); + end_header(s, this, "application/xml"); + dump_start(s); + + s->formatter->open_object_section_in_ns("Tagging", XMLNS_AWS_S3); + s->formatter->open_object_section("TagSet"); + if (has_tags){ + RGWObjTagSet_S3 tagset; + bufferlist::iterator iter = bl.begin(); + try { + tagset.decode(iter); + } catch (buffer::error& err) { + ldout(s->cct,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl; + op_ret= -EIO; + return; + } + tagset.dump_xml(s->formatter); + } + s->formatter->close_section(); + s->formatter->close_section(); + rgw_flush_formatter_and_reset(s, s->formatter); +} + + +int RGWPutObjTags_ObjStore_S3::get_params() +{ + RGWObjTagsXMLParser parser; + + if (!parser.init()){ + return -EINVAL; + } + + char *data=nullptr; + int len=0; + + const auto max_size = s->cct->_conf->rgw_max_put_param_size; + int r = rgw_rest_read_all_input(s, &data, &len, max_size, false); + + if (r < 0) + return r; + + auto data_deleter = std::unique_ptr{data, free}; + + if (!parser.parse(data, len, 1)) { + return -ERR_MALFORMED_XML; + } + + RGWObjTagSet_S3 *obj_tags_s3; + RGWObjTagging_S3 *tagging; + + tagging = static_cast(parser.find_first("Tagging")); + obj_tags_s3 = static_cast(tagging->find_first("TagSet")); + if(!obj_tags_s3){ + return -ERR_MALFORMED_XML; + } + + RGWObjTags obj_tags; + r = obj_tags_s3->rebuild(obj_tags); + if (r < 0) + return r; + + obj_tags.encode(tags_bl); + ldout(s->cct, 20) << "Read " << obj_tags.count() << "tags" << dendl; + + return 0; +} + +void RGWPutObjTags_ObjStore_S3::send_response() +{ + if (op_ret) + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this, "application/xml"); + dump_start(s); + +} + +void RGWDeleteObjTags_ObjStore_S3::send_response() +{ + int r = op_ret; + if (r == -ENOENT) + r = 0; + if (!r) + r = STATUS_NO_CONTENT; + + set_req_state_err(s, r); + dump_errno(s); + end_header(s, this); +} void RGWListBuckets_ObjStore_S3::send_response_begin(bool has_buckets) { @@ -1192,6 +1294,21 @@ int RGWPutObj_ObjStore_S3::get_params() } /* copy_source */ + /* handle object tagging */ + auto tag_str = s->info.env->get("HTTP_X_AMZ_TAGGING"); + if (tag_str){ + obj_tags = ceph::make_unique(); + ret = obj_tags->set_from_string(tag_str); + if (ret < 0){ + ldout(s->cct,0) << "setting obj tags failed with " << ret << dendl; + if (ret == -ERR_INVALID_TAG){ + ret = -EINVAL; //s3 returns only -EINVAL for PUT requests + } + + return ret; + } + } + return RGWPutObj_ObjStore::get_params(); } @@ -1501,9 +1618,56 @@ int RGWPostObj_ObjStore_S3::get_params() if (r < 0) return r; + r = get_tags(); + if (r < 0) + return r; + + min_len = post_policy.min_length; max_len = post_policy.max_length; + + + return 0; +} + +int RGWPostObj_ObjStore_S3::get_tags() +{ + string tags_str; + if (part_str(parts, "tagging", &tags_str)) { + RGWObjTagsXMLParser parser; + if (!parser.init()){ + ldout(s->cct, 0) << "Couldn't init RGWObjTags XML parser" << dendl; + err_msg = "Server couldn't process the request"; + return -EINVAL; // TODO: This class of errors in rgw code should be a 5XX error + } + if (!parser.parse(tags_str.c_str(), tags_str.size(), 1)) { + ldout(s->cct,0 ) << "Invalid Tagging XML" << dendl; + err_msg = "Invalid Tagging XML"; + return -EINVAL; + } + + RGWObjTagSet_S3 *obj_tags_s3; + RGWObjTagging_S3 *tagging; + + tagging = static_cast(parser.find_first("Tagging")); + obj_tags_s3 = static_cast(tagging->find_first("TagSet")); + if(!obj_tags_s3){ + return -ERR_MALFORMED_XML; + } + + RGWObjTags obj_tags; + int r = obj_tags_s3->rebuild(obj_tags); + if (r < 0) + return r; + + bufferlist tags_bl; + obj_tags.encode(tags_bl); + ldout(s->cct, 20) << "Read " << obj_tags.count() << "tags" << dendl; + attrs[RGW_ATTR_TAGS] = tags_bl; + } + + return 0; } @@ -2886,6 +3050,8 @@ RGWOp *RGWHandler_REST_Obj_S3::op_get() return new RGWListMultipart_ObjStore_S3; } else if (s->info.args.exists("layout")) { return new RGWGetObjLayout_ObjStore_S3; + } else if (is_tagging_op()) { + return new RGWGetObjTags_ObjStore_S3; } return get_obj_op(true); } @@ -2904,7 +3070,10 @@ RGWOp *RGWHandler_REST_Obj_S3::op_put() { if (is_acl_op()) { return new RGWPutACLs_ObjStore_S3; + } else if (is_tagging_op()) { + return new RGWPutObjTags_ObjStore_S3; } + if (s->init_state.src_bucket.empty()) return new RGWPutObj_ObjStore_S3; else @@ -2913,6 +3082,9 @@ RGWOp *RGWHandler_REST_Obj_S3::op_put() RGWOp *RGWHandler_REST_Obj_S3::op_delete() { + if (is_tagging_op()) { + return new RGWDeleteObjTags_ObjStore_S3; + } string upload_id = s->info.args.get("uploadId"); if (upload_id.empty()) diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 65d1b36ad153..7c93e2f16bdf 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -59,6 +59,33 @@ public: bufferlist* manifest_bl) override; }; +class RGWGetObjTags_ObjStore_S3 : public RGWGetObjTags_ObjStore +{ + bufferlist tags_bl; +public: + RGWGetObjTags_ObjStore_S3() {} + ~RGWGetObjTags_ObjStore_S3() {} + + void send_response_data(bufferlist &bl) override; +}; + +class RGWPutObjTags_ObjStore_S3 : public RGWPutObjTags_ObjStore +{ +public: + RGWPutObjTags_ObjStore_S3() {} + ~RGWPutObjTags_ObjStore_S3() {} + + int get_params() override; + void send_response() override; +}; + +class RGWDeleteObjTags_ObjStore_S3 : public RGWDeleteObjTags +{ +public: + ~RGWDeleteObjTags_ObjStore_S3() override {} + void send_response() override; +}; + class RGWListBuckets_ObjStore_S3 : public RGWListBuckets_ObjStore { public: RGWListBuckets_ObjStore_S3() {} @@ -209,6 +236,7 @@ class RGWPostObj_ObjStore_S3 : public RGWPostObj_ObjStore { const rgw::auth::StrategyRegistry* auth_registry_ptr = nullptr; int get_policy(); + int get_tags(); void rebuild_key(string& key); std::string get_current_filename() const override; @@ -552,8 +580,11 @@ protected: bool is_cors_op() { return s->info.args.exists("cors"); } + bool is_tagging_op() { + return s->info.args.exists("tagging"); + } bool is_obj_update_op() override { - return is_acl_op(); + return is_acl_op() || is_tagging_op() ; } RGWOp *get_obj_op(bool get_data); diff --git a/src/rgw/rgw_tag.cc b/src/rgw/rgw_tag.cc new file mode 100644 index 000000000000..682088e569d8 --- /dev/null +++ b/src/rgw/rgw_tag.cc @@ -0,0 +1,52 @@ + +#include +#include + +#include +#include + +#include "rgw_tag.h" + +static constexpr uint32_t MAX_OBJ_TAGS=10; +static constexpr uint32_t MAX_TAG_KEY_SIZE=128; +static constexpr uint32_t MAX_TAG_VAL_SIZE=256; + +bool RGWObjTags::add_tag(const string&key, const string& val){ + return tags.emplace(std::make_pair(key,val)).second; +} + +int RGWObjTags::check_and_add_tag(const string&key, const string& val){ + if (tags.size() == MAX_OBJ_TAGS || + key.size() > MAX_TAG_KEY_SIZE || + val.size() > MAX_TAG_VAL_SIZE || + key.size() == 0){ + return -ERR_INVALID_TAG; + } + + // if we get a conflicting key, either the XML is malformed or the user + // supplied an invalid string + if (!add_tag(key,val)) + return -EINVAL; + + return 0; +} + +int RGWObjTags::set_from_string(const string& input){ + int ret=0; + vector kvs; + boost::split(kvs, input, boost::is_any_of("&")); + for (const auto& kv: kvs){ + auto p = kv.find("="); + string key,val; + if (p != string::npos) { + ret = check_and_add_tag(url_decode(kv.substr(0,p)), + url_decode(kv.substr(p+1))); + } else { + ret = check_and_add_tag(url_decode(kv)); + } + + if (ret < 0) + return ret; + } + return ret; +} diff --git a/src/rgw/rgw_tag.h b/src/rgw/rgw_tag.h new file mode 100644 index 000000000000..93b5d9a29217 --- /dev/null +++ b/src/rgw/rgw_tag.h @@ -0,0 +1,39 @@ +#ifndef RGW_TAG_H +#define RGW_TAG_H + +#include +#include +#include + +#include "rgw_common.h" + +class RGWObjTags +{ + protected: + std::map tags; + public: + RGWObjTags() {} + ~RGWObjTags() {} + + void encode(bufferlist& bl) const { + ENCODE_START(1,1,bl); + ::encode(tags, bl); + ENCODE_FINISH(bl); + } + + void decode(bufferlist::iterator &bl) { + DECODE_START_LEGACY_COMPAT_LEN(1, 1, 1, bl); + ::decode(tags,bl); + DECODE_FINISH(bl); + } + + void dump(Formatter *f) const; + bool add_tag(const std::string& key, const std::string& val=""); + int check_and_add_tag(const std::string& key, const std::string& val=""); + size_t count() const {return tags.size();} + int set_from_string(const std::string& input); + const map & get_tags() const {return tags;} +}; +WRITE_CLASS_ENCODER(RGWObjTags) + +#endif /* RGW_TAG_H */ diff --git a/src/rgw/rgw_tag_s3.cc b/src/rgw/rgw_tag_s3.cc new file mode 100644 index 000000000000..b29aed510991 --- /dev/null +++ b/src/rgw/rgw_tag_s3.cc @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "include/types.h" + +#include "rgw_tag_s3.h" + +bool RGWObjTagEntry_S3::xml_end(const char*){ + RGWObjTagKey_S3 *key_obj = static_cast(find_first("Key")); + RGWObjTagValue_S3 *val_obj = static_cast(find_first("Value")); + + if (!key_obj) + return false; + + string s = key_obj->get_data(); + if (s.empty()){ + return false; + } + + key = s; + if (val_obj) { + val = val_obj->get_data(); + } + + return true; +} + +bool RGWObjTagSet_S3::xml_end(const char*){ + XMLObjIter iter = find("Tag"); + RGWObjTagEntry_S3 *tagentry = static_cast(iter.get_next()); + while (tagentry) { + const std::string& key = tagentry->get_key(); + const std::string& val = tagentry->get_val(); + if (!add_tag(key,val)) + return false; + + tagentry = static_cast(iter.get_next()); + } + return true; +} + +int RGWObjTagSet_S3::rebuild(RGWObjTags& dest){ + int ret; + for (const auto &it: tags){ + ret = dest.check_and_add_tag(it.first, it.second); + if (ret < 0) + return ret; + } + return 0; +} + +bool RGWObjTagging_S3::xml_end(const char*){ + RGWObjTagSet_S3 *tagset = static_cast (find_first("TagSet")); + return tagset != nullptr; + +} + +void RGWObjTagSet_S3::dump_xml(Formatter *f){ + for (const auto& tag: tags){ + f->open_object_section("Tag"); + f->dump_string("Key", tag.first); + f->dump_string("Value", tag.second); + f->close_section(); + } +} + +XMLObj *RGWObjTagsXMLParser::alloc_obj(const char *el){ + XMLObj* obj = nullptr; + if(strcmp(el,"Tagging") == 0) { + obj = new RGWObjTagging_S3(); + } else if (strcmp(el,"TagSet") == 0) { + obj = new RGWObjTagSet_S3(); + } else if (strcmp(el,"Tag") == 0) { + obj = new RGWObjTagEntry_S3(); + } else if (strcmp(el,"Key") == 0) { + obj = new RGWObjTagKey_S3(); + } else if (strcmp(el,"Value") == 0) { + obj = new RGWObjTagValue_S3(); + } + + return obj; +} diff --git a/src/rgw/rgw_tag_s3.h b/src/rgw/rgw_tag_s3.h new file mode 100644 index 000000000000..f20ad94e277b --- /dev/null +++ b/src/rgw/rgw_tag_s3.h @@ -0,0 +1,62 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef RGW_TAG_S3_H +#define RGW_TAG_S3_H + +#include +#include +#include +#include +#include +#include + +#include "rgw_tag.h" +#include "rgw_xml.h" + +struct RGWObjTagKey_S3: public XMLObj +{ +}; + +struct RGWObjTagValue_S3: public XMLObj +{ +}; + +class RGWObjTagEntry_S3: public XMLObj +{ + std::string key; + std::string val; +public: + RGWObjTagEntry_S3() {} + RGWObjTagEntry_S3(std::string k,std::string v):key(k),val(v) {}; + ~RGWObjTagEntry_S3() {} + + bool xml_end(const char*) override; + const std::string& get_key () const { return key;} + const std::string& get_val () const { return val;} + //void to_xml(CephContext *cct, ostream& out) const; +}; + +class RGWObjTagSet_S3: public RGWObjTags, public XMLObj +{ +public: + bool xml_end(const char*) override; + void dump_xml(Formatter *f); + int rebuild(RGWObjTags& dest); +}; + +class RGWObjTagging_S3: public XMLObj +{ +public: + bool xml_end(const char*) override; +}; + +class RGWObjTagsXMLParser : public RGWXMLParser +{ + XMLObj *alloc_obj(const char *el); +public: + RGWObjTagsXMLParser() {} + ~RGWObjTagsXMLParser() {} +}; + +#endif /* RGW_TAG_S3_H */