]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: implement get/put object tags for S3
authorAbhishek Lekshmanan <abhishek@suse.com>
Wed, 21 Jun 2017 12:09:49 +0000 (14:09 +0200)
committerAbhishek Lekshmanan <abhishek@suse.com>
Wed, 21 Jun 2017 12:09:49 +0000 (14:09 +0200)
- 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 <abhishek@suse.com>
 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

14 files changed:
src/rgw/CMakeLists.txt
src/rgw/rgw_auth_s3.cc
src/rgw/rgw_common.cc
src/rgw/rgw_common.h
src/rgw/rgw_op.cc
src/rgw/rgw_op.h
src/rgw/rgw_rest.cc
src/rgw/rgw_rest.h
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_tag.cc [new file with mode: 0644]
src/rgw/rgw_tag.h [new file with mode: 0644]
src/rgw/rgw_tag_s3.cc [new file with mode: 0644]
src/rgw/rgw_tag_s3.h [new file with mode: 0644]

index c6fa7c0bac8863eebe41bc5a4f076457b52a8342..c5017c7654e9aebba382853d3b41653de9d7c4a3 100644 (file)
@@ -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
index 0788974635df73ed8c4cc9a2e0b8b20ba57fe8d4..de32a888fc20fb736eb03020e4fde39dd3f41bf0 100644 (file)
@@ -38,6 +38,7 @@ static const auto signed_subresources = {
   "response-content-language",
   "response-content-type",
   "response-expires",
+  "tagging",
   "torrent",
   "uploadId",
   "uploads",
index b6b38fbb46e99cb84b07a463a158fb2fa2d023d4..d4e494073ca6e5a793a0213c4e3907f4119c733e 100644 (file)
@@ -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) ||
index b9fdccc606b0f9b79e6f56fca623fc3cd45d3a83..bf05d13a12f880ef621f4150e86809bfcf1777b1 100644 (file)
@@ -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);
index f700339f01470bf6e868e57993fa7074ee6f045f..3590bdd50721c7a42c426088f4a1a66dd2f9d9fa 100644 (file)
@@ -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<string,bufferlist> 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 <string, bufferlist> attrs;
+    map <string, bufferlist> 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
index de48abcd0d12decbe49c280b184a67bbb8adc5eb..632367b9b0acc2314b855a40197e911f2d5c938b 100644 (file)
@@ -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 <RGWObjTags> obj_tags;
   const char *dlo_manifest;
   RGWSLOInfo *slo_info;
   map<string, bufferlist> attrs;
@@ -1881,6 +1926,20 @@ static inline void encode_delete_at_attr(boost::optional<ceph::real_time> delete
   attrs[RGW_ATTR_DELETE_AT] = delatbl;
 } /* encode_delete_at_attr */
 
+static inline void encode_obj_tags_attr(RGWObjTags* obj_tags, map<string, bufferlist>& 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<string, bufferlist>& attrs)
 {
index 591c0a52e72943a66ddf6e9eb809646a1ada2d43..a039034be6302a07a92eb6de47bfe33a6322b37f 100644 (file)
@@ -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;
index 11c951e38b3038adf763750a9b2638811ab4fa36..615ececc595c6d5c320ea170c43ab25b5bdc5606 100644 (file)
@@ -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() {}
index 155f1d37c7b39a0f4b6f272353bebf7cc783a17a..fe6f5d4271ae0543ba1673f1f78075570108c6e8 100644 (file)
@@ -10,6 +10,7 @@
 #include "common/utf8.h"
 #include "common/ceph_json.h"
 #include "common/safe_io.h"
+#include "common/backport14.h"
 #include <boost/algorithm/string.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/utility/string_view.hpp>
@@ -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<RGWGetDataCB> *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<char, decltype(free)*>{data, free};
+
+  if (!parser.parse(data, len, 1)) {
+    return -ERR_MALFORMED_XML;
+  }
+
+  RGWObjTagSet_S3 *obj_tags_s3;
+  RGWObjTagging_S3 *tagging;
+
+  tagging = static_cast<RGWObjTagging_S3 *>(parser.find_first("Tagging"));
+  obj_tags_s3 = static_cast<RGWObjTagSet_S3 *>(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<RGWObjTags>();
+    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<RGWObjTagging_S3 *>(parser.find_first("Tagging"));
+    obj_tags_s3 = static_cast<RGWObjTagSet_S3 *>(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())
index 65d1b36ad1531139d3b5eb70c55f8a5b509c4e0b..7c93e2f16bdff3dd7a48f71957c5312e165420a7 100644 (file)
@@ -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 (file)
index 0000000..682088e
--- /dev/null
@@ -0,0 +1,52 @@
+
+#include <map>
+#include <string>
+
+#include <common/errno.h>
+#include <boost/algorithm/string.hpp>
+
+#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 <string> 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 (file)
index 0000000..93b5d9a
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef RGW_TAG_H
+#define RGW_TAG_H
+
+#include <map>
+#include <string>
+#include <include/types.h>
+
+#include "rgw_common.h"
+
+class RGWObjTags
+{
+ protected:
+  std::map <std::string, std::string> 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 <std::string,std::string>& 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 (file)
index 0000000..b29aed5
--- /dev/null
@@ -0,0 +1,83 @@
+#include <map>
+#include <string>
+#include <iostream>
+
+#include "include/types.h"
+
+#include "rgw_tag_s3.h"
+
+bool RGWObjTagEntry_S3::xml_end(const char*){
+  RGWObjTagKey_S3 *key_obj = static_cast<RGWObjTagKey_S3 *>(find_first("Key"));
+  RGWObjTagValue_S3 *val_obj = static_cast<RGWObjTagValue_S3 *>(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<RGWObjTagEntry_S3 *>(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<RGWObjTagEntry_S3 *>(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<RGWObjTagSet_S3 *> (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 (file)
index 0000000..f20ad94
--- /dev/null
@@ -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 <map>
+#include <string>
+#include <iostream>
+#include <include/types.h>
+#include <common/Formatter.h>
+#include <expat.h>
+
+#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 */