#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
#include "common/Formatter.h"
#include <common/errno.h>
#include "rgw_zone.h"
#include "rgw_string.h"
+// this seems safe to use, at least for now--arguably, we should
+// prefer header-only fmt, in general
+#undef FMT_HEADER_ONLY
+#define FMT_HEADER_ONLY 1
+#include "seastar/fmt/include/fmt/format.h"
+
#include "services/svc_sys_obj.h"
#define dout_context g_ceph_context
return ret;
}
-}
+std::string s3_expiration_header(
+ DoutPrefixProvider* dpp,
+ const rgw_obj_key& obj_key,
+ const RGWObjTags& obj_tagset,
+ const ceph::real_time& mtime,
+ const std::map<std::string, buffer::list>& bucket_attrs)
+{
+ CephContext* cct = dpp->get_cct();
+ RGWLifecycleConfiguration config(cct); // TODO: save in bucket info
+ std::string hdr{""};
+
+ const auto& aiter = bucket_attrs.find(RGW_ATTR_LC);
+ if (aiter == bucket_attrs.end())
+ return hdr;
+
+ bufferlist::const_iterator iter{&aiter->second};
+ try {
+ config.decode(iter);
+ } catch (const buffer::error& e) {
+ ldpp_dout(dpp, 0) << __func__
+ << "() decode life cycle config failed"
+ << dendl;
+ return hdr;
+ } /* catch */
+
+ boost::optional<ceph::real_time> expiration_date;
+ boost::optional<std::string> rule_id;
+
+ const auto& rule_map = config.get_rule_map();
+ for (const auto& ri : rule_map) {
+ const auto& rule = ri.second;
+ auto& id = rule.get_id();
+ auto& prefix = rule.get_prefix();
+ auto& filter = rule.get_filter();
+ auto& expiration = rule.get_expiration();
+ auto& noncur_expiration = rule.get_noncur_expiration();
+
+ ldpp_dout(dpp, 10) << "rule: " << ri.first
+ << " prefix: " << prefix
+ << " expiration: "
+ << " date: " << expiration.get_date()
+ << " days: " << expiration.get_days()
+ << " noncur_expiration: "
+ << " date: " << noncur_expiration.get_date()
+ << " days: " << noncur_expiration.get_days()
+ << dendl;
+
+ /* skip if rule !enabled
+ * if rule has prefix, skip iff object !match prefix
+ * if rule has tags, skip iff object !match tags
+ * note if object is current or non-current, compare accordingly
+ * if rule has days, construct date expression and save iff older
+ * than last saved
+ * if rule has date, convert date expression and save iff older
+ * than last saved
+ * if the date accum has a value, format it into hdr
+ */
+
+ if (!rule.is_enabled())
+ continue;
+
+ if(!prefix.empty()) {
+ if (!boost::starts_with(obj_key.name, prefix))
+ continue;
+ }
+
+ if (filter.has_tags()) {
+ bool tag_match = true;
+ const RGWObjTags& rule_tagset = filter.get_tags();
+ RGWObjTags::tag_map_t obj_tag_map = obj_tagset.get_tags();
+ for (auto& tag : rule_tagset.get_tags()) {
+ if (obj_tag_map.find(tag.first) == obj_tag_map.end()) {
+ tag_match = false;
+ break;
+ }
+ }
+ if (! tag_match)
+ continue;
+ }
+
+ // compute a uniform expiration date
+ boost::optional<ceph::real_time> rule_expiration_date;
+ const LCExpiration& rule_expiration =
+ (obj_key.instance.empty()) ? expiration : noncur_expiration;
+
+ if (rule_expiration.has_date()) {
+ rule_expiration_date =
+ boost::optional<ceph::real_time>(
+ ceph::from_iso_8601(rule.get_expiration().get_date()));
+ rule_id = id;
+ } else {
+ if (rule_expiration.has_days()) {
+ rule_expiration_date =
+ boost::optional<ceph::real_time>(
+ mtime + make_timespan(rule_expiration.get_days()*24*60*60));
+ rule_id = id;
+ }
+ }
+
+ // update earliest expiration
+ if (rule_expiration_date) {
+ if ((! expiration_date) ||
+ ((expiration_date &&
+ (*expiration_date < *rule_expiration_date)))) {
+ expiration_date =
+ boost::optional<ceph::real_time>(rule_expiration_date);
+ }
+ }
+ }
+
+ // cond format header
+ if (expiration_date && rule_id) {
+ // Fri, 23 Dec 2012 00:00:00 GMT
+ char exp_buf[100];
+ time_t exp = ceph::real_clock::to_time_t(*expiration_date);
+ if (std::strftime(exp_buf, sizeof(exp_buf),
+ "%c", std::gmtime(&exp))) {
+ hdr = fmt::format("expiry-date=\"{0}\", rule-id=\"{1}\"", exp_buf,
+ *rule_id);
+ } else {
+ ldpp_dout(dpp, 0) << __func__ <<
+ "() strftime of life cycle expiration header failed"
+ << dendl;
+ }
+ }
+
+ return hdr;
+
+} /* rgwlc_s3_expiration_header */
+
+} /* namespace rgw::lc */
}
}
+static inline std::string get_s3_expiration_header(
+ struct req_state* s,
+ const ceph::real_time& mtime)
+{
+ return rgw::lc::s3_expiration_header(
+ s, s->object, s->tagset, mtime, s->bucket_attrs);
+}
+
struct response_attr_param {
const char *param;
const char *http_attr;
map<string, string>::iterator riter;
bufferlist metadata_bl;
+ string expires = get_s3_expiration_header(s, lastmod);
+
if (sent_header)
goto send_data;
dump_content_length(s, total_len);
dump_last_modified(s, lastmod);
dump_header_if_nonempty(s, "x-amz-version-id", version_id);
+ dump_header_if_nonempty(s, "x-amz-expiration", expires);
+
if (attrs.find(RGW_ATTR_APPEND_PART_NUM) != attrs.end()) {
dump_header(s, "x-rgw-object-type", "Appendable");
dump_header(s, "x-rgw-next-append-position", s->obj_size);
s->cct->_conf->rgw_s3_success_create_obj_status);
set_req_state_err(s, op_ret);
}
+
+ string expires = get_s3_expiration_header(s, mtime);
+
if (copy_source.empty()) {
dump_errno(s);
dump_etag(s, etag);
dump_content_length(s, 0);
dump_header_if_nonempty(s, "x-amz-version-id", version_id);
+ dump_header_if_nonempty(s, "x-amz-expiration", expires);
for (auto &it : crypt_http_responses)
dump_header(s, it.first, it.second);
} else {
dump_errno(s);
dump_header_if_nonempty(s, "x-amz-version-id", version_id);
+ dump_header_if_nonempty(s, "x-amz-expiration", expires);
end_header(s, this, "application/xml");
dump_start(s);
struct tm tmp;