@@ -23,6 +23,7 @@ const net_utils = require('./net_utils');
2323const time_utils = require ( './time_utils' ) ;
2424const cloud_utils = require ( './cloud_utils' ) ;
2525const ssl_utils = require ( '../util/ssl_utils' ) ;
26+ const s3_utils = require ( '../endpoint/s3/s3_utils' ) ;
2627const RpcError = require ( '../rpc/rpc_error' ) ;
2728const 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;
9451022exports . parse_xml_to_js = parse_xml_to_js ;
9461023exports . check_headers = check_headers ;
9471024exports . set_amz_headers = set_amz_headers ;
1025+ exports . set_expiration_header = set_expiration_header ;
9481026exports . set_cors_headers = set_cors_headers ;
9491027exports . set_cors_headers_s3 = set_cors_headers_s3 ;
9501028exports . set_cors_headers_sts = set_cors_headers_sts ;
0 commit comments