Skip to content

Commit d5690b2

Browse files
committed
Added x-amz-expiration, missing HTTP header in the response of object GET/PUT/HEAD
Signed-off-by: Aayush Chouhan <achouhan@redhat.com>
1 parent be61cd9 commit d5690b2

File tree

6 files changed

+76
-0
lines changed

6 files changed

+76
-0
lines changed

src/api/bucket_api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,7 @@ module.exports = {
12151215
$ref: 'common_api#/definitions/bucket_policy'
12161216
},
12171217
replication_policy_id: { objectid: true },
1218+
lifecycle_configuration_rules: { $ref: 'common_api#/definitions/bucket_lifecycle_configuration' },
12181219
}
12191220
},
12201221

src/endpoint/s3/ops/s3_get_object.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async function get_object(req, res) {
4949
}
5050
}
5151
http_utils.set_response_headers_from_request(req, res);
52+
http_utils.set_expiration_header(req, res, object_md);
5253
const obj_size = object_md.size;
5354
const params = {
5455
object_md,

src/endpoint/s3/ops/s3_head_object.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ async function head_object(req, res) {
2929
s3_utils.set_response_object_md(res, object_md);
3030
s3_utils.set_encryption_response_headers(req, res, object_md.encryption);
3131
http_utils.set_response_headers_from_request(req, res);
32+
http_utils.set_expiration_header(req, res, object_md);
3233
}
3334

3435
module.exports = {

src/sdk/object_sdk.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ class ObjectSDK {
223223
return policy_info;
224224
}
225225

226+
async read_bucket_lifecycle_config_info(name) {
227+
const { bucket } = await bucket_namespace_cache.get_with_cache({ sdk: this, name });
228+
return bucket.bucket_info.lifecycle_configuration_rules;
229+
}
230+
226231
async read_bucket_usage_info(name) {
227232
const { bucket } = await bucket_namespace_cache.get_with_cache({ sdk: this, name });
228233
return bucket.bucket_info.data;

src/server/system_services/bucket_server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,7 @@ function get_bucket_info({
16531653
website: bucket.website,
16541654
s3_policy: bucket.s3_policy,
16551655
replication_policy_id: bucket.replication_policy_id,
1656+
lifecycle_configuration_rules: bucket.lifecycle_configuration_rules,
16561657
};
16571658

16581659
const metrics = _calc_metrics({ bucket, nodes_aggregate_pool, hosts_aggregate_pool, tiering_pools_status, info });

src/util/http_utils.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,72 @@ function set_amz_headers(req, res) {
664664
res.setHeader('x-amz-id-2', req.request_id);
665665
}
666666

667+
/**
668+
* @param {Object} req
669+
* @param {http.ServerResponse} res
670+
* @param {Object} object_md
671+
*/
672+
async function set_expiration_header(req, res, object_md) {
673+
if (req.method === 'HEAD' || req.method === 'GET' || req.method === 'PUT') {
674+
const rules = req.params.bucket && await req.object_sdk.read_bucket_lifecycle_config_info(req.params.bucket);
675+
if (rules?.length > 0) {
676+
for (const rule of rules) {
677+
if (rule?.status !== 'Enabled') continue;
678+
679+
const filter = rule?.filter || {};
680+
681+
if (filter.prefix && !object_md?.key.startsWith(filter.prefix)) continue;
682+
683+
if (filter.object_size_greater_than && object_md?.size <= filter.object_size_greater_than) continue;
684+
if (filter.object_size_less_than && object_md?.size >= filter.object_size_less_than) continue;
685+
686+
if (filter.tagging && Array.isArray(filter.tagging)) {
687+
const obj_tags = object_md?.tagging || [];
688+
689+
const matches_all_tags = filter.tagging.every(filter_tag =>
690+
obj_tags.some(obj_tag => obj_tag.key === filter_tag.key && obj_tag.value === filter_tag.value)
691+
);
692+
693+
if (!matches_all_tags) continue;
694+
}
695+
696+
const expiration_head = parse_expiration_header(rule?.expiration, rule?.id);
697+
if (expiration_head) {
698+
dbg.log0('set x_amz_expiration header from applied rule: ', rule);
699+
res.setHeader('x-amz-expiration', expiration_head);
700+
break; // apply only for first matching rule
701+
}
702+
}
703+
}
704+
}
705+
}
706+
707+
/**
708+
* parse_expiration_header converts an expiration rule (either with `date` or `days`)
709+
* into an s3 style `x-amz-expiration` header value
710+
*
711+
* @param {Object} expiration - expiration object from lifecycle config
712+
* @param {string} rule_id - id of the lifecycle rule
713+
* @returns {string|undefined}
714+
*
715+
* Example output:
716+
* expiry-date="Thu, 10 Apr 2025 00:00:00 GMT", rule-id="rule_id"
717+
*/
718+
function parse_expiration_header(expiration, rule_id) {
719+
if (!expiration || (!expiration.date && !expiration.days)) return undefined;
720+
721+
const expiration_date = expiration.date ?
722+
new Date(expiration.date) :
723+
new Date(Date.UTC(
724+
new Date().getUTCFullYear(),
725+
new Date().getUTCMonth(),
726+
new Date().getUTCDate() + expiration.days
727+
));
728+
729+
return `expiry-date="${expiration_date.toUTCString()}", rule-id="${rule_id}"`;
730+
}
731+
732+
667733
/**
668734
* @typedef {{
669735
* allow_origin: string;
@@ -945,6 +1011,7 @@ exports.set_keep_alive_whitespace_interval = set_keep_alive_whitespace_interval;
9451011
exports.parse_xml_to_js = parse_xml_to_js;
9461012
exports.check_headers = check_headers;
9471013
exports.set_amz_headers = set_amz_headers;
1014+
exports.set_expiration_header = set_expiration_header;
9481015
exports.set_cors_headers = set_cors_headers;
9491016
exports.set_cors_headers_s3 = set_cors_headers_s3;
9501017
exports.set_cors_headers_sts = set_cors_headers_sts;

0 commit comments

Comments
 (0)