diff --git a/package.json b/package.json index b993ed09..ee0fe691 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scality/cloudserverclient", - "version": "1.0.7", + "version": "1.0.8", "engines": { "node": ">=20" }, @@ -50,14 +50,17 @@ "@typescript-eslint/parser": "^6.21.0", "eslint": "^9.37.0", "jest": "^30.2.0", - "ts-jest": "^29.4.4", + "ts-jest": "^29.4.9", "typescript": "^5.7.3", "typescript-eslint": "^8.49.0" }, "dependencies": { "@aws-sdk/client-s3": "^3.1009.0", "JSONStream": "^1.3.5", - "fast-xml-parser": "^4.3.2" + "fast-xml-parser": "^4.5.5" + }, + "resolutions": { + "flatted": "^3.4.2" }, "author": "Scality", "license": "Apache-2.0", diff --git a/src/clients/backbeatRoutes.ts b/src/clients/backbeatRoutes.ts index 95089064..fa19b571 100644 --- a/src/clients/backbeatRoutes.ts +++ b/src/clients/backbeatRoutes.ts @@ -7,7 +7,10 @@ import { createCustomErrorMiddleware } from '../utils'; export * from '../../build/smithy/cloudserverBackbeatRoutes/typescript-codegen'; export class BackbeatRoutesClient extends CloudserverBackbeatRoutesClient { constructor(config: CloudserverBackbeatRoutesClientConfig) { - super(config); + super({ + ...config, + signingEscapePath: false, + }); this.middlewareStack.add(createCustomErrorMiddleware(), { step: 'deserialize', diff --git a/src/clients/bucketQuota.ts b/src/clients/bucketQuota.ts index 6e7af6a9..1036dc14 100644 --- a/src/clients/bucketQuota.ts +++ b/src/clients/bucketQuota.ts @@ -15,6 +15,9 @@ export { export class BucketQuotaClient extends CloudserverBucketQuotaClient { constructor(config: CloudserverBucketQuotaClientConfig) { - super(config); + super({ + ...config, + signingEscapePath: false, + }); } } diff --git a/src/clients/proxyBackbeatApis.ts b/src/clients/proxyBackbeatApis.ts index 2328baa8..00408c38 100644 --- a/src/clients/proxyBackbeatApis.ts +++ b/src/clients/proxyBackbeatApis.ts @@ -59,6 +59,9 @@ export type { export class ProxyBackbeatApisClient extends CloudserverProxyBackbeatApisClient { constructor(config: CloudserverProxyBackbeatApisClientConfig) { - super(config); + super({ + ...config, + signingEscapePath: false, + }); } } diff --git a/tests/testSigningSpecialChars.test.ts b/tests/testSigningSpecialChars.test.ts new file mode 100644 index 00000000..cc82254b --- /dev/null +++ b/tests/testSigningSpecialChars.test.ts @@ -0,0 +1,66 @@ +import { PutObjectCommand } from '@aws-sdk/client-s3'; +import { GetMetadataCommand } from '../src/clients/backbeatRoutes'; +import { GetFailedObjectCommand } from '../src/clients/proxyBackbeatApis'; +import { createTestClient, testConfig } from './testSetup'; +import { describeForBackbeatSetup } from './testHelpers'; + +const specialCharKeys = [ + { name: 'spaces', key: 'file with spaces.txt' }, + { name: 'parentheses', key: 'file(1).txt' }, + { name: 'exclamation', key: 'important!.txt' }, + { name: 'single quote', key: "it's a file.txt" }, + { name: 'asterisk', key: 'wild*card.txt' }, + { name: 'plus sign', key: 'a+b.txt' }, + { name: 'multiple spaces', key: 'Screenshot from 2026-04-14 17-59-19.png' }, + { name: 'unicode', key: 'données café.txt' }, + { name: 'equals sign', key: 'key=value.txt' }, + { name: 'at sign', key: 'user@domain.txt' }, + { name: 'hash', key: 'section#1.txt' }, + { name: 'percent literal', key: 'file%20already-encoded.txt' }, + { name: 'mixed special chars', key: 'file (1) copy!.txt' }, +]; + +describe('SigV4 signing with special character keys', () => { + const { backbeatRoutesClient, proxyBackbeatApisClient, s3client } = createTestClient(); + + beforeAll(async () => { + await Promise.all(specialCharKeys.map(({ key }) => + s3client.send(new PutObjectCommand({ + Bucket: testConfig.bucketName, + Key: key, + Body: 'test', + })), + )); + }); + + describe('BackbeatRoutesClient', () => { + it.each(specialCharKeys)( + 'should not double-encode $name in path', + async ({ key }) => { + const result = await backbeatRoutesClient.send( + new GetMetadataCommand({ + Bucket: testConfig.bucketName, + Key: key, + }), + ); + expect(result.Body).toBeDefined(); + }, + ); + }); + + describeForBackbeatSetup('ProxyBackbeatApisClient', () => { + it.each(specialCharKeys)( + 'should not double-encode $name in path', + async ({ key }) => { + const result = await proxyBackbeatApisClient.send( + new GetFailedObjectCommand({ + Bucket: testConfig.bucketName, + Key: key, + VersionId: testConfig.versionID, + }), + ); + expect(result.$metadata.httpStatusCode).toBe(200); + }, + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index c55b71cf..ec668cfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2766,12 +2766,12 @@ fast-xml-parser@5.4.1: fast-xml-builder "^1.0.0" strnum "^2.1.2" -fast-xml-parser@^4.3.2: - version "4.5.3" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz#c54d6b35aa0f23dc1ea60b6c884340c006dc6efb" - integrity sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig== +fast-xml-parser@^4.5.5: + version "4.5.6" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz#4ff57d4aca13a2d11aa42ad460495cf00f32b655" + integrity sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A== dependencies: - strnum "^1.1.1" + strnum "^1.0.5" fastq@^1.6.0: version "1.19.1" @@ -2830,10 +2830,10 @@ flat-cache@^4.0.0: flatted "^3.2.9" keyv "^4.5.4" -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +flatted@^3.2.9, flatted@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== foreground-child@^3.1.0: version "3.3.1" @@ -2938,10 +2938,10 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -handlebars@^4.7.8: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== +handlebars@^4.7.9: + version "4.7.9" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" + integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== dependencies: minimist "^1.2.5" neo-async "^2.6.2" @@ -3939,11 +3939,16 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.2, semver@^7.7.3: +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.2: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== +semver@^7.7.4: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4067,7 +4072,7 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strnum@^1.1.1: +strnum@^1.0.5: version "1.1.2" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.1.2.tgz#57bca4fbaa6f271081715dbc9ed7cee5493e28e4" integrity sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== @@ -4142,18 +4147,18 @@ ts-api-utils@^2.1.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== -ts-jest@^29.4.4: - version "29.4.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.5.tgz#a6b0dc401e521515d5342234be87f1ca96390a6f" - integrity sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q== +ts-jest@^29.4.9: + version "29.4.9" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.9.tgz#47dc33d0f5c36bddcedd16afefae285e0b049d2d" + integrity sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ== dependencies: bs-logger "^0.2.6" fast-json-stable-stringify "^2.1.0" - handlebars "^4.7.8" + handlebars "^4.7.9" json5 "^2.2.3" lodash.memoize "^4.1.2" make-error "^1.3.6" - semver "^7.7.3" + semver "^7.7.4" type-fest "^4.41.0" yargs-parser "^21.1.1"