@payloadcms/plugin-cloud-storage 4.0.0-canary.33 → 4.0.0-canary.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/utilities.d.ts +1 -1
- package/dist/exports/utilities.d.ts.map +1 -1
- package/dist/exports/utilities.js +1 -1
- package/dist/exports/utilities.js.map +1 -1
- package/dist/fields/getFields.d.ts.map +1 -1
- package/dist/fields/getFields.js +25 -1
- package/dist/fields/getFields.js.map +1 -1
- package/dist/hooks/afterChange.d.ts +3 -1
- package/dist/hooks/afterChange.d.ts.map +1 -1
- package/dist/hooks/afterChange.js +51 -8
- package/dist/hooks/afterChange.js.map +1 -1
- package/dist/hooks/afterChange.spec.js +95 -0
- package/dist/hooks/afterChange.spec.js.map +1 -0
- package/dist/hooks/afterDelete.d.ts +3 -1
- package/dist/hooks/afterDelete.d.ts.map +1 -1
- package/dist/hooks/afterDelete.js +16 -2
- package/dist/hooks/afterDelete.js.map +1 -1
- package/dist/hooks/afterRead.d.ts.map +1 -1
- package/dist/hooks/afterRead.js +15 -2
- package/dist/hooks/afterRead.js.map +1 -1
- package/dist/hooks/beforeChange.d.ts.map +1 -1
- package/dist/hooks/beforeChange.js +12 -1
- package/dist/hooks/beforeChange.js.map +1 -1
- package/dist/hooks/normalizeUploadPrefix.d.ts +11 -0
- package/dist/hooks/normalizeUploadPrefix.d.ts.map +1 -0
- package/dist/hooks/normalizeUploadPrefix.js +42 -0
- package/dist/hooks/normalizeUploadPrefix.js.map +1 -0
- package/dist/hooks/normalizeUploadPrefix.spec.js +154 -0
- package/dist/hooks/normalizeUploadPrefix.spec.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +14 -6
- package/dist/plugin.js.map +1 -1
- package/dist/plugin.spec.js +166 -0
- package/dist/plugin.spec.js.map +1 -0
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities/buildPrefixWithObjectKey.d.ts +8 -0
- package/dist/utilities/buildPrefixWithObjectKey.d.ts.map +1 -0
- package/dist/utilities/buildPrefixWithObjectKey.js +13 -0
- package/dist/utilities/buildPrefixWithObjectKey.js.map +1 -0
- package/dist/utilities/buildStoragePathData.d.ts +49 -0
- package/dist/utilities/buildStoragePathData.d.ts.map +1 -0
- package/dist/utilities/buildStoragePathData.js +67 -0
- package/dist/utilities/buildStoragePathData.js.map +1 -0
- package/dist/utilities/buildStoragePathData.spec.js +241 -0
- package/dist/utilities/buildStoragePathData.spec.js.map +1 -0
- package/dist/utilities/getFilePrefix.d.ts +18 -10
- package/dist/utilities/getFilePrefix.d.ts.map +1 -1
- package/dist/utilities/getFilePrefix.js +69 -29
- package/dist/utilities/getFilePrefix.js.map +1 -1
- package/dist/utilities/getFilePrefix.spec.js +174 -31
- package/dist/utilities/getFilePrefix.spec.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.d.ts +12 -1
- package/dist/utilities/resolveSignedURLKey.d.ts.map +1 -1
- package/dist/utilities/resolveSignedURLKey.js +42 -8
- package/dist/utilities/resolveSignedURLKey.js.map +1 -1
- package/dist/utilities/resolveSignedURLKey.spec.js +74 -0
- package/dist/utilities/resolveSignedURLKey.spec.js.map +1 -0
- package/dist/utilities/sanitizePrefix.d.ts +1 -9
- package/dist/utilities/sanitizePrefix.d.ts.map +1 -1
- package/dist/utilities/sanitizePrefix.js +1 -21
- package/dist/utilities/sanitizePrefix.js.map +1 -1
- package/package.json +4 -4
- package/dist/utilities/getFileKey.d.ts +0 -22
- package/dist/utilities/getFileKey.d.ts.map +0 -1
- package/dist/utilities/getFileKey.js +0 -23
- package/dist/utilities/getFileKey.js.map +0 -1
- package/dist/utilities/getFileKey.spec.js +0 -174
- package/dist/utilities/getFileKey.spec.js.map +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createClientUploadReceipt, getSafeFileName } from 'payload/internal';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { resolveSignedURLKey } from './resolveSignedURLKey.js';
|
|
4
|
+
vi.mock('payload/internal', ()=>({
|
|
5
|
+
createClientUploadReceipt: vi.fn(()=>'receipt'),
|
|
6
|
+
getSafeFileName: vi.fn(async ({ desiredFilename })=>desiredFilename)
|
|
7
|
+
}));
|
|
8
|
+
describe('resolveSignedURLKey', ()=>{
|
|
9
|
+
beforeEach(()=>{
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
it('checks the normalized filename before resolving the storage key', async ()=>{
|
|
13
|
+
vi.mocked(getSafeFileName).mockResolvedValueOnce('photo-1.png');
|
|
14
|
+
const result = await resolveSignedURLKey({
|
|
15
|
+
collectionPrefix: 'media',
|
|
16
|
+
collectionSlug: 'uploads',
|
|
17
|
+
filename: 'nested\\folder/photo.png',
|
|
18
|
+
req: {}
|
|
19
|
+
});
|
|
20
|
+
expect(getSafeFileName).toHaveBeenCalledWith({
|
|
21
|
+
collectionSlug: 'uploads',
|
|
22
|
+
desiredFilename: 'photo.png',
|
|
23
|
+
req: {}
|
|
24
|
+
});
|
|
25
|
+
expect(createClientUploadReceipt).toHaveBeenCalledWith({
|
|
26
|
+
_objectKey: expect.stringMatching(/^[0-9a-f-]+$/),
|
|
27
|
+
collectionSlug: 'uploads',
|
|
28
|
+
filePrefix: 'media',
|
|
29
|
+
filename: 'photo-1.png',
|
|
30
|
+
req: {},
|
|
31
|
+
storageFilePath: expect.stringMatching(/^media\/[0-9a-f-]+\/photo-1\.png$/)
|
|
32
|
+
});
|
|
33
|
+
expect(result).toEqual({
|
|
34
|
+
sanitizedDocPrefix: 'media',
|
|
35
|
+
sanitizedFilename: 'photo-1.png',
|
|
36
|
+
storageFilePath: expect.stringMatching(/^media\/[0-9a-f-]+\/photo-1\.png$/),
|
|
37
|
+
uploadReference: {
|
|
38
|
+
_objectKey: expect.stringMatching(/^[0-9a-f-]+$/),
|
|
39
|
+
prefix: 'media',
|
|
40
|
+
signedReceipt: 'receipt'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('should mint the receipt for a key contained by the collection prefix', async ()=>{
|
|
45
|
+
vi.mocked(getSafeFileName).mockResolvedValueOnce('photo-1.jpg');
|
|
46
|
+
const result = await resolveSignedURLKey({
|
|
47
|
+
collectionPrefix: 'media',
|
|
48
|
+
collectionSlug: 'media',
|
|
49
|
+
docPrefix: 'media-archive',
|
|
50
|
+
filename: 'photo.jpg',
|
|
51
|
+
req: {}
|
|
52
|
+
});
|
|
53
|
+
expect(createClientUploadReceipt).toHaveBeenCalledWith({
|
|
54
|
+
_objectKey: expect.stringMatching(/^[0-9a-f-]+$/),
|
|
55
|
+
collectionSlug: 'media',
|
|
56
|
+
filePrefix: result.sanitizedDocPrefix,
|
|
57
|
+
filename: 'photo-1.jpg',
|
|
58
|
+
req: {},
|
|
59
|
+
storageFilePath: result.storageFilePath
|
|
60
|
+
});
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
sanitizedDocPrefix: 'media/media-archive',
|
|
63
|
+
sanitizedFilename: 'photo-1.jpg',
|
|
64
|
+
storageFilePath: expect.stringMatching(/^media\/media-archive\/[0-9a-f-]+\/photo-1\.jpg$/),
|
|
65
|
+
uploadReference: {
|
|
66
|
+
_objectKey: expect.stringMatching(/^[0-9a-f-]+$/),
|
|
67
|
+
prefix: 'media/media-archive',
|
|
68
|
+
signedReceipt: 'receipt'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=resolveSignedURLKey.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/resolveSignedURLKey.spec.ts"],"sourcesContent":["import { createClientUploadReceipt, getSafeFileName } from 'payload/internal'\nimport { beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport { resolveSignedURLKey } from './resolveSignedURLKey.js'\n\nvi.mock('payload/internal', () => ({\n createClientUploadReceipt: vi.fn(() => 'receipt'),\n getSafeFileName: vi.fn(async ({ desiredFilename }) => desiredFilename),\n}))\n\ndescribe('resolveSignedURLKey', () => {\n beforeEach(() => {\n vi.clearAllMocks()\n })\n\n it('checks the normalized filename before resolving the storage key', async () => {\n vi.mocked(getSafeFileName).mockResolvedValueOnce('photo-1.png')\n\n const result = await resolveSignedURLKey({\n collectionPrefix: 'media',\n collectionSlug: 'uploads',\n filename: 'nested\\\\folder/photo.png',\n req: {} as never,\n })\n\n expect(getSafeFileName).toHaveBeenCalledWith({\n collectionSlug: 'uploads',\n desiredFilename: 'photo.png',\n req: {},\n })\n expect(createClientUploadReceipt).toHaveBeenCalledWith({\n _objectKey: expect.stringMatching(/^[0-9a-f-]+$/),\n collectionSlug: 'uploads',\n filePrefix: 'media',\n filename: 'photo-1.png',\n req: {},\n storageFilePath: expect.stringMatching(/^media\\/[0-9a-f-]+\\/photo-1\\.png$/),\n })\n expect(result).toEqual({\n sanitizedDocPrefix: 'media',\n sanitizedFilename: 'photo-1.png',\n storageFilePath: expect.stringMatching(/^media\\/[0-9a-f-]+\\/photo-1\\.png$/),\n uploadReference: {\n _objectKey: expect.stringMatching(/^[0-9a-f-]+$/),\n prefix: 'media',\n signedReceipt: 'receipt',\n },\n })\n })\n\n it('should mint the receipt for a key contained by the collection prefix', async () => {\n vi.mocked(getSafeFileName).mockResolvedValueOnce('photo-1.jpg')\n\n const result = await resolveSignedURLKey({\n collectionPrefix: 'media',\n collectionSlug: 'media',\n docPrefix: 'media-archive',\n filename: 'photo.jpg',\n req: {} as never,\n })\n\n expect(createClientUploadReceipt).toHaveBeenCalledWith({\n _objectKey: expect.stringMatching(/^[0-9a-f-]+$/),\n collectionSlug: 'media',\n filePrefix: result.sanitizedDocPrefix,\n filename: 'photo-1.jpg',\n req: {},\n storageFilePath: result.storageFilePath,\n })\n expect(result).toEqual({\n sanitizedDocPrefix: 'media/media-archive',\n sanitizedFilename: 'photo-1.jpg',\n storageFilePath: expect.stringMatching(/^media\\/media-archive\\/[0-9a-f-]+\\/photo-1\\.jpg$/),\n uploadReference: {\n _objectKey: expect.stringMatching(/^[0-9a-f-]+$/),\n prefix: 'media/media-archive',\n signedReceipt: 'receipt',\n },\n })\n })\n})\n"],"names":["createClientUploadReceipt","getSafeFileName","beforeEach","describe","expect","it","vi","resolveSignedURLKey","mock","fn","desiredFilename","clearAllMocks","mocked","mockResolvedValueOnce","result","collectionPrefix","collectionSlug","filename","req","toHaveBeenCalledWith","_objectKey","stringMatching","filePrefix","storageFilePath","toEqual","sanitizedDocPrefix","sanitizedFilename","uploadReference","prefix","signedReceipt","docPrefix"],"mappings":"AAAA,SAASA,yBAAyB,EAAEC,eAAe,QAAQ,mBAAkB;AAC7E,SAASC,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE7D,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9DD,GAAGE,IAAI,CAAC,oBAAoB,IAAO,CAAA;QACjCR,2BAA2BM,GAAGG,EAAE,CAAC,IAAM;QACvCR,iBAAiBK,GAAGG,EAAE,CAAC,OAAO,EAAEC,eAAe,EAAE,GAAKA;IACxD,CAAA;AAEAP,SAAS,uBAAuB;IAC9BD,WAAW;QACTI,GAAGK,aAAa;IAClB;IAEAN,GAAG,mEAAmE;QACpEC,GAAGM,MAAM,CAACX,iBAAiBY,qBAAqB,CAAC;QAEjD,MAAMC,SAAS,MAAMP,oBAAoB;YACvCQ,kBAAkB;YAClBC,gBAAgB;YAChBC,UAAU;YACVC,KAAK,CAAC;QACR;QAEAd,OAAOH,iBAAiBkB,oBAAoB,CAAC;YAC3CH,gBAAgB;YAChBN,iBAAiB;YACjBQ,KAAK,CAAC;QACR;QACAd,OAAOJ,2BAA2BmB,oBAAoB,CAAC;YACrDC,YAAYhB,OAAOiB,cAAc,CAAC;YAClCL,gBAAgB;YAChBM,YAAY;YACZL,UAAU;YACVC,KAAK,CAAC;YACNK,iBAAiBnB,OAAOiB,cAAc,CAAC;QACzC;QACAjB,OAAOU,QAAQU,OAAO,CAAC;YACrBC,oBAAoB;YACpBC,mBAAmB;YACnBH,iBAAiBnB,OAAOiB,cAAc,CAAC;YACvCM,iBAAiB;gBACfP,YAAYhB,OAAOiB,cAAc,CAAC;gBAClCO,QAAQ;gBACRC,eAAe;YACjB;QACF;IACF;IAEAxB,GAAG,wEAAwE;QACzEC,GAAGM,MAAM,CAACX,iBAAiBY,qBAAqB,CAAC;QAEjD,MAAMC,SAAS,MAAMP,oBAAoB;YACvCQ,kBAAkB;YAClBC,gBAAgB;YAChBc,WAAW;YACXb,UAAU;YACVC,KAAK,CAAC;QACR;QAEAd,OAAOJ,2BAA2BmB,oBAAoB,CAAC;YACrDC,YAAYhB,OAAOiB,cAAc,CAAC;YAClCL,gBAAgB;YAChBM,YAAYR,OAAOW,kBAAkB;YACrCR,UAAU;YACVC,KAAK,CAAC;YACNK,iBAAiBT,OAAOS,eAAe;QACzC;QACAnB,OAAOU,QAAQU,OAAO,CAAC;YACrBC,oBAAoB;YACpBC,mBAAmB;YACnBH,iBAAiBnB,OAAOiB,cAAc,CAAC;YACvCM,iBAAiB;gBACfP,YAAYhB,OAAOiB,cAAc,CAAC;gBAClCO,QAAQ;gBACRC,eAAe;YACjB;QACF;IACF;AACF"}
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Normalizes a storage prefix for use in object keys and URLs.
|
|
3
|
-
*
|
|
4
|
-
* Decodes URI components once (so query-style `%2F` becomes `/`), rejects
|
|
5
|
-
* values that still contain percent-encodings after decoding (e.g. `%252f`),
|
|
6
|
-
* then normalizes slashes, drops `.` / `..` segments, strips leading slashes,
|
|
7
|
-
* and removes control characters.
|
|
8
|
-
*/
|
|
9
|
-
export declare function sanitizePrefix(prefix: string): string;
|
|
1
|
+
export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared';
|
|
10
2
|
//# sourceMappingURL=sanitizePrefix.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitizePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizePrefix.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sanitizePrefix.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizePrefix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
* Normalizes a storage prefix for use in object keys and URLs.
|
|
3
|
-
*
|
|
4
|
-
* Decodes URI components once (so query-style `%2F` becomes `/`), rejects
|
|
5
|
-
* values that still contain percent-encodings after decoding (e.g. `%252f`),
|
|
6
|
-
* then normalizes slashes, drops `.` / `..` segments, strips leading slashes,
|
|
7
|
-
* and removes control characters.
|
|
8
|
-
*/ export function sanitizePrefix(prefix) {
|
|
9
|
-
let decodedPrefix;
|
|
10
|
-
try {
|
|
11
|
-
decodedPrefix = decodeURIComponent(prefix);
|
|
12
|
-
} catch {
|
|
13
|
-
return '';
|
|
14
|
-
}
|
|
15
|
-
// Reject multi-encoded values (e.g. `%252f`) by allowing only a single decode pass.
|
|
16
|
-
if (/%[0-9a-f]{2}/i.test(decodedPrefix)) {
|
|
17
|
-
return '';
|
|
18
|
-
}
|
|
19
|
-
return decodedPrefix.replace(/\\/g, '/').split('/').filter((segment)=>segment !== '..' && segment !== '.').join('/').replace(/^\/+/, '')// eslint-disable-next-line no-control-regex
|
|
20
|
-
.replace(/[\x00-\x1f\x80-\x9f]/g, '');
|
|
21
|
-
}
|
|
1
|
+
export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared';
|
|
22
2
|
|
|
23
3
|
//# sourceMappingURL=sanitizePrefix.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/sanitizePrefix.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/sanitizePrefix.ts"],"sourcesContent":["export { sanitizeUploadPrefix as sanitizePrefix } from 'payload/shared'\n"],"names":["sanitizeUploadPrefix","sanitizePrefix"],"mappings":"AAAA,SAASA,wBAAwBC,cAAc,QAAQ,iBAAgB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/plugin-cloud-storage",
|
|
3
|
-
"version": "4.0.0-canary.
|
|
3
|
+
"version": "4.0.0-canary.34",
|
|
4
4
|
"description": "The official cloud storage plugin for Payload CMS",
|
|
5
5
|
"homepage": "https://payloadcms.com",
|
|
6
6
|
"repository": {
|
|
@@ -51,18 +51,18 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"find-node-modules": "^2.1.3",
|
|
53
53
|
"range-parser": "^1.2.1",
|
|
54
|
-
"@payloadcms/ui": "4.0.0-canary.
|
|
54
|
+
"@payloadcms/ui": "4.0.0-canary.34"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/find-node-modules": "^2.1.2",
|
|
58
58
|
"@types/react": "19.2.14",
|
|
59
59
|
"@types/react-dom": "19.2.3",
|
|
60
|
-
"payload": "4.0.0-canary.
|
|
60
|
+
"payload": "4.0.0-canary.34"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": "^19.0.1 || ^19.1.2 || ^19.2.1",
|
|
64
64
|
"react-dom": "^19.0.1 || ^19.1.2 || ^19.2.1",
|
|
65
|
-
"payload": "4.0.0-canary.
|
|
65
|
+
"payload": "4.0.0-canary.34"
|
|
66
66
|
},
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
type GetFileKeyArgs = {
|
|
2
|
-
collectionPrefix?: string;
|
|
3
|
-
docPrefix?: string;
|
|
4
|
-
filename: string;
|
|
5
|
-
useCompositePrefixes?: boolean;
|
|
6
|
-
};
|
|
7
|
-
type GetFileKeyResult = {
|
|
8
|
-
fileKey: string;
|
|
9
|
-
sanitizedCollectionPrefix: string;
|
|
10
|
-
sanitizedDocPrefix: string;
|
|
11
|
-
sanitizedFilename: string;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Computes the file key (path) for storage.
|
|
15
|
-
*
|
|
16
|
-
* In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.
|
|
17
|
-
* In composite mode (useCompositePrefixes: true), both are combined.
|
|
18
|
-
* Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.
|
|
19
|
-
*/
|
|
20
|
-
export declare function getFileKey({ collectionPrefix, docPrefix, filename, useCompositePrefixes, }: GetFileKeyArgs): GetFileKeyResult;
|
|
21
|
-
export {};
|
|
22
|
-
//# sourceMappingURL=getFileKey.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getFileKey.d.ts","sourceRoot":"","sources":["../../src/utilities/getFileKey.ts"],"names":[],"mappings":"AAKA,KAAK,cAAc,GAAG;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,yBAAyB,EAAE,MAAM,CAAA;IACjC,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iBAAiB,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,gBAAgB,EAChB,SAAS,EACT,QAAQ,EACR,oBAA4B,GAC7B,EAAE,cAAc,GAAG,gBAAgB,CAenC"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { sanitizeFilename } from 'payload/shared';
|
|
3
|
-
import { sanitizePrefix } from './sanitizePrefix.js';
|
|
4
|
-
/**
|
|
5
|
-
* Computes the file key (path) for storage.
|
|
6
|
-
*
|
|
7
|
-
* In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.
|
|
8
|
-
* In composite mode (useCompositePrefixes: true), both are combined.
|
|
9
|
-
* Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.
|
|
10
|
-
*/ export function getFileKey({ collectionPrefix, docPrefix, filename, useCompositePrefixes = false }) {
|
|
11
|
-
const safeCollectionPrefix = sanitizePrefix(collectionPrefix || '');
|
|
12
|
-
const safeDocPrefix = sanitizePrefix(docPrefix || '');
|
|
13
|
-
const safeFilename = sanitizeFilename(filename);
|
|
14
|
-
const fileKey = useCompositePrefixes ? path.posix.join(safeCollectionPrefix, safeDocPrefix, safeFilename) : path.posix.join(safeDocPrefix || safeCollectionPrefix, safeFilename);
|
|
15
|
-
return {
|
|
16
|
-
fileKey,
|
|
17
|
-
sanitizedCollectionPrefix: safeCollectionPrefix,
|
|
18
|
-
sanitizedDocPrefix: safeDocPrefix,
|
|
19
|
-
sanitizedFilename: safeFilename
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//# sourceMappingURL=getFileKey.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/getFileKey.ts"],"sourcesContent":["import path from 'path'\nimport { sanitizeFilename } from 'payload/shared'\n\nimport { sanitizePrefix } from './sanitizePrefix.js'\n\ntype GetFileKeyArgs = {\n collectionPrefix?: string\n docPrefix?: string\n filename: string\n useCompositePrefixes?: boolean\n}\n\ntype GetFileKeyResult = {\n fileKey: string\n sanitizedCollectionPrefix: string\n sanitizedDocPrefix: string\n sanitizedFilename: string\n}\n\n/**\n * Computes the file key (path) for storage.\n *\n * In non-composite mode (useCompositePrefixes: false), docPrefix overrides collectionPrefix.\n * In composite mode (useCompositePrefixes: true), both are combined.\n * Both prefixes are passed through {@link sanitizePrefix} so keys stay normalized.\n */\nexport function getFileKey({\n collectionPrefix,\n docPrefix,\n filename,\n useCompositePrefixes = false,\n}: GetFileKeyArgs): GetFileKeyResult {\n const safeCollectionPrefix = sanitizePrefix(collectionPrefix || '')\n const safeDocPrefix = sanitizePrefix(docPrefix || '')\n const safeFilename = sanitizeFilename(filename)\n\n const fileKey = useCompositePrefixes\n ? path.posix.join(safeCollectionPrefix, safeDocPrefix, safeFilename)\n : path.posix.join(safeDocPrefix || safeCollectionPrefix, safeFilename)\n\n return {\n fileKey,\n sanitizedCollectionPrefix: safeCollectionPrefix,\n sanitizedDocPrefix: safeDocPrefix,\n sanitizedFilename: safeFilename,\n }\n}\n"],"names":["path","sanitizeFilename","sanitizePrefix","getFileKey","collectionPrefix","docPrefix","filename","useCompositePrefixes","safeCollectionPrefix","safeDocPrefix","safeFilename","fileKey","posix","join","sanitizedCollectionPrefix","sanitizedDocPrefix","sanitizedFilename"],"mappings":"AAAA,OAAOA,UAAU,OAAM;AACvB,SAASC,gBAAgB,QAAQ,iBAAgB;AAEjD,SAASC,cAAc,QAAQ,sBAAqB;AAgBpD;;;;;;CAMC,GACD,OAAO,SAASC,WAAW,EACzBC,gBAAgB,EAChBC,SAAS,EACTC,QAAQ,EACRC,uBAAuB,KAAK,EACb;IACf,MAAMC,uBAAuBN,eAAeE,oBAAoB;IAChE,MAAMK,gBAAgBP,eAAeG,aAAa;IAClD,MAAMK,eAAeT,iBAAiBK;IAEtC,MAAMK,UAAUJ,uBACZP,KAAKY,KAAK,CAACC,IAAI,CAACL,sBAAsBC,eAAeC,gBACrDV,KAAKY,KAAK,CAACC,IAAI,CAACJ,iBAAiBD,sBAAsBE;IAE3D,OAAO;QACLC;QACAG,2BAA2BN;QAC3BO,oBAAoBN;QACpBO,mBAAmBN;IACrB;AACF"}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { getFileKey } from './getFileKey.js';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
describe('getFileKey', ()=>{
|
|
4
|
-
describe('non-composite mode (useCompositePrefixes: false)', ()=>{
|
|
5
|
-
it('should use docPrefix when provided, ignoring collectionPrefix', ()=>{
|
|
6
|
-
const result = getFileKey({
|
|
7
|
-
collectionPrefix: 'collection',
|
|
8
|
-
docPrefix: 'document',
|
|
9
|
-
filename: 'test.png',
|
|
10
|
-
useCompositePrefixes: false
|
|
11
|
-
});
|
|
12
|
-
expect(result).toEqual({
|
|
13
|
-
fileKey: 'document/test.png',
|
|
14
|
-
sanitizedCollectionPrefix: 'collection',
|
|
15
|
-
sanitizedDocPrefix: 'document',
|
|
16
|
-
sanitizedFilename: 'test.png'
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
it('should fallback to collectionPrefix when docPrefix is empty', ()=>{
|
|
20
|
-
const result = getFileKey({
|
|
21
|
-
collectionPrefix: 'collection',
|
|
22
|
-
docPrefix: '',
|
|
23
|
-
filename: 'test.png',
|
|
24
|
-
useCompositePrefixes: false
|
|
25
|
-
});
|
|
26
|
-
expect(result).toEqual({
|
|
27
|
-
fileKey: 'collection/test.png',
|
|
28
|
-
sanitizedCollectionPrefix: 'collection',
|
|
29
|
-
sanitizedDocPrefix: '',
|
|
30
|
-
sanitizedFilename: 'test.png'
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
it('should fallback to collectionPrefix when docPrefix is undefined', ()=>{
|
|
34
|
-
const result = getFileKey({
|
|
35
|
-
collectionPrefix: 'collection',
|
|
36
|
-
filename: 'test.png',
|
|
37
|
-
useCompositePrefixes: false
|
|
38
|
-
});
|
|
39
|
-
expect(result).toEqual({
|
|
40
|
-
fileKey: 'collection/test.png',
|
|
41
|
-
sanitizedCollectionPrefix: 'collection',
|
|
42
|
-
sanitizedDocPrefix: '',
|
|
43
|
-
sanitizedFilename: 'test.png'
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
it('should return only filename when both prefixes are empty', ()=>{
|
|
47
|
-
const result = getFileKey({
|
|
48
|
-
filename: 'test.png',
|
|
49
|
-
useCompositePrefixes: false
|
|
50
|
-
});
|
|
51
|
-
expect(result).toEqual({
|
|
52
|
-
fileKey: 'test.png',
|
|
53
|
-
sanitizedCollectionPrefix: '',
|
|
54
|
-
sanitizedDocPrefix: '',
|
|
55
|
-
sanitizedFilename: 'test.png'
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe('composite mode (useCompositePrefixes: true)', ()=>{
|
|
60
|
-
it('should combine collectionPrefix and docPrefix', ()=>{
|
|
61
|
-
const result = getFileKey({
|
|
62
|
-
collectionPrefix: 'collection',
|
|
63
|
-
docPrefix: 'document',
|
|
64
|
-
filename: 'test.png',
|
|
65
|
-
useCompositePrefixes: true
|
|
66
|
-
});
|
|
67
|
-
expect(result).toEqual({
|
|
68
|
-
fileKey: 'collection/document/test.png',
|
|
69
|
-
sanitizedCollectionPrefix: 'collection',
|
|
70
|
-
sanitizedDocPrefix: 'document',
|
|
71
|
-
sanitizedFilename: 'test.png'
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
it('should work with only collectionPrefix', ()=>{
|
|
75
|
-
const result = getFileKey({
|
|
76
|
-
collectionPrefix: 'collection',
|
|
77
|
-
filename: 'test.png',
|
|
78
|
-
useCompositePrefixes: true
|
|
79
|
-
});
|
|
80
|
-
expect(result).toEqual({
|
|
81
|
-
fileKey: 'collection/test.png',
|
|
82
|
-
sanitizedCollectionPrefix: 'collection',
|
|
83
|
-
sanitizedDocPrefix: '',
|
|
84
|
-
sanitizedFilename: 'test.png'
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
it('should work with only docPrefix', ()=>{
|
|
88
|
-
const result = getFileKey({
|
|
89
|
-
docPrefix: 'document',
|
|
90
|
-
filename: 'test.png',
|
|
91
|
-
useCompositePrefixes: true
|
|
92
|
-
});
|
|
93
|
-
expect(result).toEqual({
|
|
94
|
-
fileKey: 'document/test.png',
|
|
95
|
-
sanitizedCollectionPrefix: '',
|
|
96
|
-
sanitizedDocPrefix: 'document',
|
|
97
|
-
sanitizedFilename: 'test.png'
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
it('should return only filename when both prefixes are empty', ()=>{
|
|
101
|
-
const result = getFileKey({
|
|
102
|
-
filename: 'test.png',
|
|
103
|
-
useCompositePrefixes: true
|
|
104
|
-
});
|
|
105
|
-
expect(result).toEqual({
|
|
106
|
-
fileKey: 'test.png',
|
|
107
|
-
sanitizedCollectionPrefix: '',
|
|
108
|
-
sanitizedDocPrefix: '',
|
|
109
|
-
sanitizedFilename: 'test.png'
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe('sanitization', ()=>{
|
|
114
|
-
it('should remove path traversal segments from collectionPrefix', ()=>{
|
|
115
|
-
const result = getFileKey({
|
|
116
|
-
collectionPrefix: '../../../etc',
|
|
117
|
-
filename: 'test.png',
|
|
118
|
-
useCompositePrefixes: false
|
|
119
|
-
});
|
|
120
|
-
expect(result).toEqual({
|
|
121
|
-
fileKey: 'etc/test.png',
|
|
122
|
-
sanitizedCollectionPrefix: 'etc',
|
|
123
|
-
sanitizedDocPrefix: '',
|
|
124
|
-
sanitizedFilename: 'test.png'
|
|
125
|
-
});
|
|
126
|
-
expect(result.fileKey).not.toContain('..');
|
|
127
|
-
});
|
|
128
|
-
it('should remove path traversal segments from docPrefix', ()=>{
|
|
129
|
-
const result = getFileKey({
|
|
130
|
-
docPrefix: 'a/../../outside',
|
|
131
|
-
filename: 'test.png',
|
|
132
|
-
useCompositePrefixes: false
|
|
133
|
-
});
|
|
134
|
-
expect(result).toEqual({
|
|
135
|
-
fileKey: 'a/outside/test.png',
|
|
136
|
-
sanitizedCollectionPrefix: '',
|
|
137
|
-
sanitizedDocPrefix: 'a/outside',
|
|
138
|
-
sanitizedFilename: 'test.png'
|
|
139
|
-
});
|
|
140
|
-
expect(result.fileKey).not.toContain('..');
|
|
141
|
-
});
|
|
142
|
-
it('should remove control characters from prefixes', ()=>{
|
|
143
|
-
const result = getFileKey({
|
|
144
|
-
collectionPrefix: 'test\x00\x1fprefix',
|
|
145
|
-
filename: 'test.png',
|
|
146
|
-
useCompositePrefixes: false
|
|
147
|
-
});
|
|
148
|
-
expect(result).toEqual({
|
|
149
|
-
fileKey: 'testprefix/test.png',
|
|
150
|
-
sanitizedCollectionPrefix: 'testprefix',
|
|
151
|
-
sanitizedDocPrefix: '',
|
|
152
|
-
sanitizedFilename: 'test.png'
|
|
153
|
-
});
|
|
154
|
-
expect(result.fileKey).not.toMatch(/[\x00-\x1f]/);
|
|
155
|
-
});
|
|
156
|
-
it('should sanitize both prefixes in composite mode', ()=>{
|
|
157
|
-
const result = getFileKey({
|
|
158
|
-
collectionPrefix: '../collection',
|
|
159
|
-
docPrefix: '../../doc',
|
|
160
|
-
filename: 'test.png',
|
|
161
|
-
useCompositePrefixes: true
|
|
162
|
-
});
|
|
163
|
-
expect(result).toEqual({
|
|
164
|
-
fileKey: 'collection/doc/test.png',
|
|
165
|
-
sanitizedCollectionPrefix: 'collection',
|
|
166
|
-
sanitizedDocPrefix: 'doc',
|
|
167
|
-
sanitizedFilename: 'test.png'
|
|
168
|
-
});
|
|
169
|
-
expect(result.fileKey).not.toContain('..');
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
//# sourceMappingURL=getFileKey.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/getFileKey.spec.ts"],"sourcesContent":["import { getFileKey } from './getFileKey.js'\nimport { describe, expect, it } from 'vitest'\n\ndescribe('getFileKey', () => {\n describe('non-composite mode (useCompositePrefixes: false)', () => {\n it('should use docPrefix when provided, ignoring collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'document/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: 'document',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should fallback to collectionPrefix when docPrefix is empty', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: '',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should fallback to collectionPrefix when docPrefix is undefined', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = getFileKey({\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n })\n })\n\n describe('composite mode (useCompositePrefixes: true)', () => {\n it('should combine collectionPrefix and docPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/document/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: 'document',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should work with only collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: 'collection',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should work with only docPrefix', () => {\n const result = getFileKey({\n docPrefix: 'document',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'document/test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: 'document',\n sanitizedFilename: 'test.png',\n })\n })\n\n it('should return only filename when both prefixes are empty', () => {\n const result = getFileKey({\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n })\n })\n\n describe('sanitization', () => {\n it('should remove path traversal segments from collectionPrefix', () => {\n const result = getFileKey({\n collectionPrefix: '../../../etc',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'etc/test.png',\n sanitizedCollectionPrefix: 'etc',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n\n it('should remove path traversal segments from docPrefix', () => {\n const result = getFileKey({\n docPrefix: 'a/../../outside',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'a/outside/test.png',\n sanitizedCollectionPrefix: '',\n sanitizedDocPrefix: 'a/outside',\n sanitizedFilename: 'test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n\n it('should remove control characters from prefixes', () => {\n const result = getFileKey({\n collectionPrefix: 'test\\x00\\x1fprefix',\n filename: 'test.png',\n useCompositePrefixes: false,\n })\n expect(result).toEqual({\n fileKey: 'testprefix/test.png',\n sanitizedCollectionPrefix: 'testprefix',\n sanitizedDocPrefix: '',\n sanitizedFilename: 'test.png',\n })\n expect(result.fileKey).not.toMatch(/[\\x00-\\x1f]/)\n })\n\n it('should sanitize both prefixes in composite mode', () => {\n const result = getFileKey({\n collectionPrefix: '../collection',\n docPrefix: '../../doc',\n filename: 'test.png',\n useCompositePrefixes: true,\n })\n expect(result).toEqual({\n fileKey: 'collection/doc/test.png',\n sanitizedCollectionPrefix: 'collection',\n sanitizedDocPrefix: 'doc',\n sanitizedFilename: 'test.png',\n })\n expect(result.fileKey).not.toContain('..')\n })\n })\n})\n"],"names":["getFileKey","describe","expect","it","result","collectionPrefix","docPrefix","filename","useCompositePrefixes","toEqual","fileKey","sanitizedCollectionPrefix","sanitizedDocPrefix","sanitizedFilename","not","toContain","toMatch"],"mappings":"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"}
|