Skip to content

Commit 87e28c9

Browse files
authored
Merge pull request #8338 from shirady/nsfs-nc-versioning-head-get-version-id-delete-marker
NC | NSFS | Versioning | Fix Bug | Return 405 for Get/Head Specific Delete-Marker
2 parents cb6beec + 8492113 commit 87e28c9

File tree

4 files changed

+60
-30
lines changed

4 files changed

+60
-30
lines changed

src/endpoint/s3/s3_errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ S3Error.RPC_ERRORS_TO_S3 = Object.freeze({
609609
NO_SUCH_TAG: S3Error.NoSuchTagSet,
610610
INVALID_ENCODING_TYPE: S3Error.InvalidEncodingType,
611611
INVALID_TARGET_BUCKET: S3Error.InvalidTargetBucketForLogging,
612+
METHOD_NOT_ALLOWED: S3Error.MethodNotAllowed,
612613
});
613614

614615
exports.S3Error = S3Error;

src/endpoint/s3/s3_rest.js

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ function parse_op_name(req) {
397397
return `${method}_object`;
398398
}
399399

400-
function handle_error(req, res, err) {
400+
function _prepare_error(req, res, err) {
401401
let s3err =
402402
((err instanceof S3Error) && err) ||
403403
new S3Error(S3Error.RPC_ERRORS_TO_S3[err.rpc_code] || S3Error.InternalError);
@@ -407,19 +407,26 @@ function handle_error(req, res, err) {
407407
s3err.detail = err.rpc_data.detail;
408408
}
409409

410-
if (s3err.rpc_data) {
411-
if (s3err.rpc_data.etag) {
410+
if (err.rpc_data) {
411+
if (err.rpc_data.etag) {
412412
if (res.headersSent) {
413413
dbg.log0('Sent reply in body, bit too late for Etag header');
414414
} else {
415-
res.setHeader('ETag', s3err.rpc_data.etag);
415+
res.setHeader('ETag', err.rpc_data.etag);
416416
}
417417
}
418-
if (s3err.rpc_data.last_modified) {
418+
if (err.rpc_data.last_modified) {
419419
if (res.headersSent) {
420420
dbg.log0('Sent reply in body, bit too late for Last-Modified header');
421421
} else {
422-
res.setHeader('Last-Modified', time_utils.format_http_header_date(new Date(s3err.rpc_data.last_modified)));
422+
res.setHeader('Last-Modified', time_utils.format_http_header_date(new Date(err.rpc_data.last_modified)));
423+
}
424+
}
425+
if (err.rpc_data.delete_marker) {
426+
if (res.headersSent) {
427+
dbg.log0('Sent reply in body, bit too late for x-amz-delete-marker header');
428+
} else {
429+
res.setHeader('x-amz-delete-marker', String(err.rpc_data.delete_marker));
423430
}
424431
}
425432
}
@@ -432,6 +439,12 @@ function handle_error(req, res, err) {
432439
usage_report.s3_errors_info.total_errors += 1;
433440
usage_report.s3_errors_info[s3err.code] = (usage_report.s3_errors_info[s3err.code] || 0) + 1;
434441

442+
return s3err;
443+
}
444+
445+
function handle_error(req, res, err) {
446+
const s3err = _prepare_error(req, res, err);
447+
435448
const reply = s3err.reply(req.originalUrl, req.request_id);
436449
dbg.error('S3 ERROR', reply,
437450
req.method, req.originalUrl,
@@ -450,26 +463,7 @@ function handle_error(req, res, err) {
450463
}
451464

452465
async function _handle_html_response(req, res, err) {
453-
let s3err =
454-
((err instanceof S3Error) && err) ||
455-
new S3Error(S3Error.RPC_ERRORS_TO_S3[err.rpc_code] || S3Error.InternalError);
456-
457-
if (s3err.rpc_data) {
458-
if (s3err.rpc_data.etag) {
459-
res.setHeader('ETag', s3err.rpc_data.etag);
460-
}
461-
if (s3err.rpc_data.last_modified) {
462-
res.setHeader('Last-Modified', time_utils.format_http_header_date(new Date(s3err.rpc_data.last_modified)));
463-
}
464-
}
465-
466-
// md_conditions used for PUT/POST/DELETE should return PreconditionFailed instead of NotModified
467-
if (s3err.code === 'NotModified' && req.method !== 'HEAD' && req.method !== 'GET') {
468-
s3err = new S3Error(S3Error.PreconditionFailed);
469-
}
470-
471-
usage_report.s3_errors_info.total_errors += 1;
472-
usage_report.s3_errors_info[s3err.code] = (usage_report.s3_errors_info[s3err.code] || 0) + 1;
466+
const s3err = _prepare_error(req, res, err);
473467

474468
const reply = `<html> \
475469
<head><title>${s3err.http_code} ${s3err.code}</title></head> \

src/sdk/namespace_fs.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ class NamespaceFS {
943943
stat = { ...dir_content_path_stat, xattr };
944944
}
945945
}
946-
this._throw_if_delete_marker(stat);
946+
this._throw_if_delete_marker(stat, params);
947947
return this._get_object_info(params.bucket, params.key, stat, isDir);
948948
} catch (err) {
949949
if (this._should_update_issues_report(params, file_path, err)) {
@@ -989,7 +989,7 @@ class NamespaceFS {
989989
);
990990

991991
const stat = await file.stat(fs_context);
992-
this._throw_if_delete_marker(stat);
992+
this._throw_if_delete_marker(stat, params);
993993
// await this._fail_if_archived_or_sparse_file(fs_context, file_path, stat);
994994

995995
const start = Number(params.start) || 0;
@@ -2714,11 +2714,19 @@ class NamespaceFS {
27142714
return versioned_path;
27152715
}
27162716

2717-
_throw_if_delete_marker(stat) {
2717+
_throw_if_delete_marker(stat, params) {
27182718
if (this.versioning === versioning_status_enum.VER_ENABLED || this.versioning === versioning_status_enum.VER_SUSPENDED) {
27192719
const xattr_delete_marker = stat.xattr[XATTR_DELETE_MARKER];
27202720
if (xattr_delete_marker) {
2721-
throw error_utils.new_error_code('ENOENT', 'Entry is a delete marker');
2721+
const basic_err = error_utils.new_error_code('ENOENT', 'Entry is a delete marker');
2722+
if (params.version_id) {
2723+
// If the specified version in the request is a delete marker,
2724+
// the response returns a 405 Method Not Allowed error and the Last-Modified: timestamp response header.
2725+
throw new RpcError('METHOD_NOT_ALLOWED',
2726+
'Method not allowed, delete object id of entry delete marker',
2727+
{ last_modified: new Date(stat.mtime), delete_marker: true });
2728+
}
2729+
throw basic_err;
27222730
}
27232731
}
27242732
}

src/test/unit_tests/test_bucketspace_versioning.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,33 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
26642664
assert.fail(`Failed with an error: ${err.Code}`);
26652665
}
26662666
});
2667+
2668+
mocha.it('head object, with version enabled, version id specified delete marker - should throw error with code 405', async function() {
2669+
try {
2670+
await s3_client.headObject({Bucket: bucket_name, Key: en_version_key, VersionId: versionID_1});
2671+
assert.fail('Should fail');
2672+
} catch (err) {
2673+
assert.strictEqual(err.$metadata.httpStatusCode, 405);
2674+
// In headObject the AWS SDK doesn't return the err.Code
2675+
// In AWS CLI it looks:
2676+
// An error occurred (405) when calling the HeadObject operation: Method Not Allowed
2677+
// in the docs: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html
2678+
// if the HEAD request generates an error, it returns a generic code, such as ...
2679+
// 405 Method Not Allowed, ... It's not possible to retrieve the exact exception of these error codes.
2680+
}
2681+
});
2682+
2683+
mocha.it('get object, with version enabled, version id specified delete marker - should throw error with code 405', async function() {
2684+
try {
2685+
await s3_client.getObject({Bucket: bucket_name, Key: en_version_key, VersionId: versionID_1});
2686+
assert.fail('Should fail');
2687+
} catch (err) {
2688+
assert.strictEqual(err.$metadata.httpStatusCode, 405);
2689+
assert.strictEqual(err.Code, 'MethodNotAllowed');
2690+
// In AWS CLI it looks:
2691+
// An error occurred (MethodNotAllowed) when calling the GetObject operation: The specified method is not allowed against this resource.
2692+
}
2693+
});
26672694
});
26682695
});
26692696

0 commit comments

Comments
 (0)