Skip to content

Commit b0aadfc

Browse files
committed
Added x-amz-expiration, missing HTTP header in the response of object GET/PUT/HEAD
Signed-off-by: Aayush Chouhan <[email protected]>
1 parent 2e31d10 commit b0aadfc

File tree

5 files changed

+92
-0
lines changed

5 files changed

+92
-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/s3_rest.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ async function handle_request(req, res) {
139139
authenticate_request(req);
140140
await authorize_request(req);
141141

142+
await http_utils.set_expiration_header(req, res);
143+
142144
dbg.log1('S3 REQUEST', req.method, req.originalUrl, 'op', op_name, 'request_id', req.request_id, req.headers);
143145
usage_report.s3_usage_info.total_calls += 1;
144146
usage_report.s3_usage_info[op_name] = (usage_report.s3_usage_info[op_name] || 0) + 1;

src/sdk/object_sdk.js

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

226+
async read_bucket_lifecycle_config_info(name) {
227+
try {
228+
const { bucket } = await bucket_namespace_cache.get_with_cache({ sdk: this, name });
229+
return bucket.bucket_info.lifecycle_configuration_rules;
230+
} catch (error) {
231+
if (error.rpc_code === 'NO_SUCH_BUCKET') return undefined;
232+
throw error;
233+
}
234+
}
235+
226236
async read_bucket_usage_info(name) {
227237
const { bucket } = await bucket_namespace_cache.get_with_cache({ sdk: this, name });
228238
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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const net_utils = require('./net_utils');
2323
const time_utils = require('./time_utils');
2424
const cloud_utils = require('./cloud_utils');
2525
const ssl_utils = require('../util/ssl_utils');
26+
const s3_utils = require('../endpoint/s3/s3_utils');
2627
const RpcError = require('../rpc/rpc_error');
2728
const S3Error = require('../endpoint/s3/s3_errors').S3Error;
2829

@@ -664,6 +665,82 @@ function set_amz_headers(req, res) {
664665
res.setHeader('x-amz-id-2', req.request_id);
665666
}
666667

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

0 commit comments

Comments
 (0)