@nocobase/plugin-file-manager 2.1.0-beta.9 → 2.1.1
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/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/867ada653cd02a3e.mjs +6 -0
- package/dist/client/876.ca16d7a6e6387862.js +11 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +1 -1
- package/dist/client/locale/index.d.ts +2 -1
- package/dist/client/previewer/filePreviewTypes.d.ts +1 -31
- package/dist/client/templates/file.d.ts +1 -1
- package/dist/client-v2/125.0b8eef1f19b87042.js +10 -0
- package/dist/client-v2/229.bd72c2d7aa088310.js +10 -0
- package/dist/client-v2/336.1dd1b32466d0c778.js +10 -0
- package/dist/client-v2/43.eb45d53ba3e9828b.js +10 -0
- package/dist/client-v2/450.30cc3ed6973d8c4d.js +10 -0
- package/dist/client-v2/867ada653cd02a3e.mjs +6 -0
- package/dist/client-v2/876.22cd8e41ac8631ed.js +11 -0
- package/dist/client-v2/929.d7e783304cc1f236.js +10 -0
- package/dist/client-v2/942.f36d807d763a1b53.js +10 -0
- package/dist/{client/StorageOptions.d.ts → client-v2/components/BaseUrlField.d.ts} +1 -1
- package/dist/client-v2/components/DefaultField.d.ts +18 -0
- package/dist/client-v2/components/FileSizeField.d.ts +10 -0
- package/dist/client-v2/components/MimetypeField.d.ts +10 -0
- package/dist/client-v2/components/NameField.d.ts +10 -0
- package/dist/client-v2/components/ParanoidField.d.ts +10 -0
- package/dist/client-v2/components/PathField.d.ts +18 -0
- package/dist/client-v2/components/RenameModeField.d.ts +10 -0
- package/dist/client-v2/components/TitleField.d.ts +10 -0
- package/dist/client-v2/components/index.d.ts +17 -0
- package/dist/client-v2/index.d.ts +17 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/client-v2/locale.d.ts +10 -0
- package/dist/{client → client-v2}/models/DisplayPreviewFieldModel.d.ts +1 -1
- package/dist/{client → client-v2}/models/UploadActionModel.d.ts +4 -3
- package/dist/{client → client-v2}/models/UploadFieldModel.d.ts +1 -1
- package/dist/client-v2/models/index.d.ts +11 -0
- package/dist/{client → client-v2}/models/uploadFieldUtils.d.ts +8 -0
- package/dist/client-v2/pages/FileStoragePage.d.ts +10 -0
- package/dist/client-v2/plugin.d.ts +89 -0
- package/dist/client-v2/previewer/filePreviewTypes.d.ts +46 -0
- package/dist/client-v2/storage-forms/AliOssStorageForm.d.ts +10 -0
- package/dist/client-v2/storage-forms/LocalStorageForm.d.ts +10 -0
- package/dist/client-v2/storage-forms/S3StorageForm.d.ts +10 -0
- package/dist/client-v2/storage-forms/TxCosStorageForm.d.ts +10 -0
- package/dist/common/collections/attachments.d.ts +1 -0
- package/dist/common/collections/attachments.js +1 -0
- package/dist/common/collections/storages.d.ts +1 -0
- package/dist/common/collections/storages.js +1 -0
- package/dist/externalVersion.js +12 -12
- package/dist/locale/de-DE.json +3 -0
- package/dist/locale/en-US.json +4 -0
- package/dist/locale/es-ES.json +3 -0
- package/dist/locale/fr-FR.json +3 -0
- package/dist/locale/hu-HU.json +4 -1
- package/dist/locale/id-ID.json +4 -1
- package/dist/locale/it-IT.json +3 -0
- package/dist/locale/ja-JP.json +3 -0
- package/dist/locale/ko-KR.json +3 -0
- package/dist/locale/nl-NL.json +3 -0
- package/dist/locale/pt-BR.json +3 -0
- package/dist/locale/ru-RU.json +3 -0
- package/dist/locale/tr-TR.json +3 -0
- package/dist/locale/uk-UA.json +3 -0
- package/dist/locale/vi-VN.json +4 -1
- package/dist/locale/zh-CN.json +4 -0
- package/dist/locale/zh-TW.json +3 -0
- package/dist/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +190 -190
- package/dist/node_modules/@aws-sdk/client-s3/package.json +1 -1
- package/dist/node_modules/@aws-sdk/lib-storage/dist-cjs/index.js +195 -195
- package/dist/node_modules/@aws-sdk/lib-storage/node_modules/buffer/index.d.ts +186 -0
- package/dist/node_modules/@aws-sdk/lib-storage/node_modules/buffer/index.js +1794 -0
- package/dist/node_modules/@aws-sdk/lib-storage/node_modules/buffer/package.json +82 -0
- package/dist/node_modules/@aws-sdk/lib-storage/package.json +1 -1
- package/dist/node_modules/ali-oss/lib/client.js +14 -14
- package/dist/node_modules/ali-oss/package.json +1 -1
- package/dist/node_modules/cos-nodejs-sdk-v5/.github/workflows/auto-changelog.yml +55 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/.prettierrc +10 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/.travis.yml +16 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/LICENSE +21 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/demo/crc64.js +9 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/demo/demo-sts-scope.js +75 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/demo/demo-sts.js +65 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/demo/demo.js +4542 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/demo/util.js +135 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/index.d.ts +2610 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/index.js +220 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/package.json +1 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/advance.js +1659 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/async.js +59 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/base.js +4404 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/cos.js +137 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/event.js +34 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/select-stream.js +181 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/session.js +126 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/task.js +255 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/sdk/util.js +776 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/test/csp.js +1302 -0
- package/dist/node_modules/cos-nodejs-sdk-v5/test/test.js +6119 -0
- package/dist/node_modules/mime-match/index.js +1 -1
- package/dist/node_modules/mime-match/package.json +1 -1
- package/dist/node_modules/mime-types/index.js +3 -3
- package/dist/node_modules/mime-types/package.json +1 -1
- package/dist/node_modules/mkdirp/index.js +1 -1
- package/dist/node_modules/mkdirp/package.json +1 -1
- package/dist/node_modules/url-join/lib/url-join.js +1 -1
- package/dist/node_modules/url-join/package.json +1 -1
- package/dist/server/actions/attachments.js +2 -2
- package/dist/server/actions/index.js +8 -1
- package/dist/server/actions/storage-validation.d.ts +10 -0
- package/dist/server/actions/storage-validation.js +73 -0
- package/dist/server/commands/repair-filenames.d.ts +55 -0
- package/dist/server/commands/repair-filenames.js +283 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/server.d.ts +5 -2
- package/dist/server/server.js +19 -7
- package/dist/server/storages/ali-oss.d.ts +3 -1
- package/dist/server/storages/ali-oss.js +23 -2
- package/dist/server/storages/index.d.ts +7 -1
- package/dist/server/storages/index.js +8 -1
- package/dist/server/storages/local.d.ts +6 -0
- package/dist/server/storages/local.js +76 -11
- package/dist/server/storages/s3.d.ts +2 -0
- package/dist/server/storages/s3.js +26 -0
- package/dist/server/storages/tx-cos.d.ts +18 -1
- package/dist/server/storages/tx-cos.js +138 -10
- package/dist/server/utils.d.ts +3 -0
- package/dist/server/utils.js +72 -4
- package/package.json +8 -3
- package/dist/node_modules/multer-cos/LICENSE +0 -24
- package/dist/node_modules/multer-cos/demo/index.js +0 -39
- package/dist/node_modules/multer-cos/demo/myMulter.js +0 -88
- package/dist/node_modules/multer-cos/index.js +0 -220
- package/dist/node_modules/multer-cos/package.json +0 -1
|
@@ -90,11 +90,16 @@ class AliYunOssStorage {
|
|
|
90
90
|
});
|
|
91
91
|
}).catch(cb);
|
|
92
92
|
}
|
|
93
|
-
_removeFile(req, file, cb) {
|
|
93
|
+
async _removeFile(req, file, cb) {
|
|
94
94
|
if (!this.client) {
|
|
95
95
|
return cb(ERROR_NO_CLIENT);
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
try {
|
|
98
|
+
const result = await this.client.delete(file.filename);
|
|
99
|
+
cb(null, result);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
cb(error);
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
class ali_oss_default extends import__.StorageType {
|
|
@@ -118,6 +123,22 @@ class ali_oss_default extends import__.StorageType {
|
|
|
118
123
|
filename: (0, import_utils.cloudFilenameGetter)(this.storage)
|
|
119
124
|
});
|
|
120
125
|
}
|
|
126
|
+
async exists(record) {
|
|
127
|
+
const { client } = this.make();
|
|
128
|
+
try {
|
|
129
|
+
await client.head((0, import_utils.getFileKey)(record));
|
|
130
|
+
return true;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (["NoSuchKey", "NotFoundError"].includes(error.name)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async copy(source, target) {
|
|
139
|
+
const { client } = this.make();
|
|
140
|
+
await client.copy((0, import_utils.getFileKey)(target), (0, import_utils.getFileKey)(source));
|
|
141
|
+
}
|
|
121
142
|
async delete(records) {
|
|
122
143
|
const { client } = this.make();
|
|
123
144
|
const { deleted } = await client.deleteMulti(records.map(import_utils.getFileKey));
|
|
@@ -24,6 +24,7 @@ export interface StorageModel {
|
|
|
24
24
|
settings?: Record<string, any>;
|
|
25
25
|
}
|
|
26
26
|
export interface AttachmentModel {
|
|
27
|
+
id?: number;
|
|
27
28
|
title: string;
|
|
28
29
|
filename: string;
|
|
29
30
|
mimetype?: string;
|
|
@@ -38,6 +39,8 @@ export declare abstract class StorageType {
|
|
|
38
39
|
constructor(storage: StorageModel);
|
|
39
40
|
abstract make(): StorageEngine;
|
|
40
41
|
abstract delete(records: AttachmentModel[]): [number, AttachmentModel[]] | Promise<[number, AttachmentModel[]]>;
|
|
42
|
+
exists(record: AttachmentModel): Promise<boolean>;
|
|
43
|
+
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
41
44
|
getFileKey(record: AttachmentModel): any;
|
|
42
45
|
getFileData(file: any, meta?: {}): {
|
|
43
46
|
title: string;
|
|
@@ -50,7 +53,7 @@ export declare abstract class StorageType {
|
|
|
50
53
|
storageId: number;
|
|
51
54
|
};
|
|
52
55
|
getFileURL(file: AttachmentModel, preview?: boolean): string | Promise<string>;
|
|
53
|
-
getFileStream(file: AttachmentModel): Promise<{
|
|
56
|
+
getFileStream(file: AttachmentModel, options?: GetFileStreamOptions): Promise<{
|
|
54
57
|
stream: Readable;
|
|
55
58
|
contentType?: string;
|
|
56
59
|
}>;
|
|
@@ -58,3 +61,6 @@ export declare abstract class StorageType {
|
|
|
58
61
|
export type StorageClassType = {
|
|
59
62
|
new (storage: StorageModel): StorageType;
|
|
60
63
|
} & typeof StorageType;
|
|
64
|
+
export type GetFileStreamOptions = {
|
|
65
|
+
requestOptions?: any;
|
|
66
|
+
};
|
|
@@ -52,6 +52,12 @@ class StorageType {
|
|
|
52
52
|
return {};
|
|
53
53
|
}
|
|
54
54
|
static filenameKey;
|
|
55
|
+
async exists(record) {
|
|
56
|
+
throw new Error(`Storage type "${this.storage.type}" does not support object existence checks`);
|
|
57
|
+
}
|
|
58
|
+
async copy(source, target) {
|
|
59
|
+
throw new Error(`Storage type "${this.storage.type}" does not support object copy`);
|
|
60
|
+
}
|
|
55
61
|
getFileKey(record) {
|
|
56
62
|
return (0, import_utils2.getFileKey)(record);
|
|
57
63
|
}
|
|
@@ -88,12 +94,13 @@ class StorageType {
|
|
|
88
94
|
].filter(Boolean);
|
|
89
95
|
return (0, import_url_join.default)(keys);
|
|
90
96
|
}
|
|
91
|
-
async getFileStream(file) {
|
|
97
|
+
async getFileStream(file, options) {
|
|
92
98
|
var _a;
|
|
93
99
|
try {
|
|
94
100
|
const fileURL = await this.getFileURL(file);
|
|
95
101
|
const requestOptions = {
|
|
96
102
|
...(_a = this.storage.settings) == null ? void 0 : _a.requestOptions,
|
|
103
|
+
...(options == null ? void 0 : options.requestOptions) ?? {},
|
|
97
104
|
responseType: "stream",
|
|
98
105
|
validateStatus: (status) => status === 200,
|
|
99
106
|
timeout: 3e4
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
import multer from 'multer';
|
|
11
11
|
import type { Readable } from 'stream';
|
|
12
12
|
import { AttachmentModel, StorageType } from '.';
|
|
13
|
+
export declare function normalizeLocalStoragePath(storagePath?: unknown): string;
|
|
13
14
|
export declare function getDocumentRoot(storage: any): string;
|
|
14
15
|
export declare function resolveSafePath(documentRoot: string, filePath?: string, filename?: string): string;
|
|
16
|
+
export declare function validateLocalStorageConfig(storage: Pick<StorageType['storage'], 'type' | 'options' | 'path'>, { validateDocumentRoot }?: {
|
|
17
|
+
validateDocumentRoot?: boolean;
|
|
18
|
+
}): void;
|
|
15
19
|
export default class extends StorageType {
|
|
16
20
|
static defaults(): {
|
|
17
21
|
title: string;
|
|
@@ -27,6 +31,8 @@ export default class extends StorageType {
|
|
|
27
31
|
};
|
|
28
32
|
};
|
|
29
33
|
make(): multer.StorageEngine;
|
|
34
|
+
exists(record: AttachmentModel): Promise<boolean>;
|
|
35
|
+
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
30
36
|
delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
|
|
31
37
|
getFileURL(file: AttachmentModel, preview?: boolean): Promise<any>;
|
|
32
38
|
getFileStream(file: AttachmentModel): Promise<{
|
|
@@ -38,7 +38,9 @@ var local_exports = {};
|
|
|
38
38
|
__export(local_exports, {
|
|
39
39
|
default: () => local_default,
|
|
40
40
|
getDocumentRoot: () => getDocumentRoot,
|
|
41
|
-
|
|
41
|
+
normalizeLocalStoragePath: () => normalizeLocalStoragePath,
|
|
42
|
+
resolveSafePath: () => resolveSafePath,
|
|
43
|
+
validateLocalStorageConfig: () => validateLocalStorageConfig
|
|
42
44
|
});
|
|
43
45
|
module.exports = __toCommonJS(local_exports);
|
|
44
46
|
var import_utils = require("@nocobase/utils");
|
|
@@ -51,21 +53,64 @@ var import__ = require(".");
|
|
|
51
53
|
var import_constants = require("../../constants");
|
|
52
54
|
var import_utils2 = require("../utils");
|
|
53
55
|
const DEFAULT_BASE_URL = "/storage/uploads";
|
|
56
|
+
function pathError(message) {
|
|
57
|
+
const error = new Error(message);
|
|
58
|
+
error.code = "PATH_TRAVERSAL";
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
function isInside(base, target) {
|
|
62
|
+
const relative = import_path.default.relative(base, target);
|
|
63
|
+
return !relative.startsWith("..") && !import_path.default.isAbsolute(relative);
|
|
64
|
+
}
|
|
65
|
+
function resolveDocumentRoot(documentRoot) {
|
|
66
|
+
if (typeof documentRoot !== "string" || !documentRoot || documentRoot.includes("\0")) {
|
|
67
|
+
throw pathError("Invalid local storage document root");
|
|
68
|
+
}
|
|
69
|
+
return (0, import_utils2.normalizeDocumentRoot)(documentRoot);
|
|
70
|
+
}
|
|
71
|
+
function allowedRoots() {
|
|
72
|
+
var _a;
|
|
73
|
+
const roots = [(0, import_utils.storagePathJoin)()];
|
|
74
|
+
const extra = [process.env.LOCAL_STORAGE_DEST, ...((_a = process.env.LOCAL_STORAGE_ALLOWED_ROOTS) == null ? void 0 : _a.split(",")) ?? []];
|
|
75
|
+
for (const item of extra) {
|
|
76
|
+
if (item == null ? void 0 : item.trim()) {
|
|
77
|
+
roots.push(resolveDocumentRoot(item.trim()));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return roots;
|
|
81
|
+
}
|
|
82
|
+
function normalizeLocalStoragePath(storagePath) {
|
|
83
|
+
if (storagePath == null || storagePath === "") {
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
if (typeof storagePath !== "string" || storagePath.includes("\0")) {
|
|
87
|
+
throw pathError("Invalid local storage path");
|
|
88
|
+
}
|
|
89
|
+
return storagePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
90
|
+
}
|
|
54
91
|
function getDocumentRoot(storage) {
|
|
55
|
-
|
|
56
|
-
|
|
92
|
+
var _a;
|
|
93
|
+
const raw = ((_a = storage == null ? void 0 : storage.options) == null ? void 0 : _a.documentRoot) ?? process.env.LOCAL_STORAGE_DEST ?? (0, import_utils.storagePathJoin)("uploads");
|
|
94
|
+
return resolveDocumentRoot(raw);
|
|
57
95
|
}
|
|
58
96
|
function resolveSafePath(documentRoot, filePath, filename) {
|
|
59
|
-
const root =
|
|
97
|
+
const root = resolveDocumentRoot(documentRoot);
|
|
60
98
|
const target = import_path.default.resolve(root, filePath || "", filename || "");
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const error = new Error("Access denied");
|
|
64
|
-
error.code = "PATH_TRAVERSAL";
|
|
65
|
-
throw error;
|
|
99
|
+
if (!isInside(root, target)) {
|
|
100
|
+
throw pathError("Access denied");
|
|
66
101
|
}
|
|
67
102
|
return target;
|
|
68
103
|
}
|
|
104
|
+
function validateLocalStorageConfig(storage, { validateDocumentRoot = false } = {}) {
|
|
105
|
+
if (storage.type !== import_constants.STORAGE_TYPE_LOCAL) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const root = getDocumentRoot(storage);
|
|
109
|
+
if (validateDocumentRoot && !allowedRoots().some((allowed) => isInside(allowed, root))) {
|
|
110
|
+
throw pathError("Invalid local storage document root");
|
|
111
|
+
}
|
|
112
|
+
resolveSafePath(root, normalizeLocalStoragePath(storage.path));
|
|
113
|
+
}
|
|
69
114
|
class local_default extends import__.StorageType {
|
|
70
115
|
static defaults() {
|
|
71
116
|
return {
|
|
@@ -85,13 +130,31 @@ class local_default extends import__.StorageType {
|
|
|
85
130
|
make() {
|
|
86
131
|
return import_multer.default.diskStorage({
|
|
87
132
|
destination: (req, file, cb) => {
|
|
88
|
-
const destPath =
|
|
133
|
+
const destPath = resolveSafePath(getDocumentRoot(this.storage), normalizeLocalStoragePath(this.storage.path));
|
|
89
134
|
const mkdirp = require("mkdirp");
|
|
90
135
|
mkdirp(destPath, (err) => cb(err, destPath));
|
|
91
136
|
},
|
|
92
137
|
filename: (0, import_utils2.diskFilenameGetter)(this.storage)
|
|
93
138
|
});
|
|
94
139
|
}
|
|
140
|
+
async exists(record) {
|
|
141
|
+
try {
|
|
142
|
+
await import_promises.default.stat(resolveSafePath(getDocumentRoot(this.storage), record.path, record.filename));
|
|
143
|
+
return true;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error.code === "ENOENT") {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async copy(source, target) {
|
|
152
|
+
const documentRoot = getDocumentRoot(this.storage);
|
|
153
|
+
const sourcePath = resolveSafePath(documentRoot, source.path, source.filename);
|
|
154
|
+
const targetPath = resolveSafePath(documentRoot, target.path, target.filename);
|
|
155
|
+
await import_promises.default.mkdir(import_path.default.dirname(targetPath), { recursive: true });
|
|
156
|
+
await import_promises.default.copyFile(sourcePath, targetPath);
|
|
157
|
+
}
|
|
95
158
|
async delete(records) {
|
|
96
159
|
const documentRoot = getDocumentRoot(this.storage);
|
|
97
160
|
let count = 0;
|
|
@@ -140,5 +203,7 @@ class local_default extends import__.StorageType {
|
|
|
140
203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
141
204
|
0 && (module.exports = {
|
|
142
205
|
getDocumentRoot,
|
|
143
|
-
|
|
206
|
+
normalizeLocalStoragePath,
|
|
207
|
+
resolveSafePath,
|
|
208
|
+
validateLocalStorageConfig
|
|
144
209
|
});
|
|
@@ -32,5 +32,7 @@ export default class extends StorageType {
|
|
|
32
32
|
deleteS3Objects(bucketName: string, objects: string[]): Promise<{
|
|
33
33
|
Deleted: any[];
|
|
34
34
|
}>;
|
|
35
|
+
exists(record: AttachmentModel): Promise<boolean>;
|
|
36
|
+
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
35
37
|
delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
|
|
36
38
|
}
|
|
@@ -173,6 +173,32 @@ class s3_default extends import__.StorageType {
|
|
|
173
173
|
Deleted
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
|
+
async exists(record) {
|
|
177
|
+
try {
|
|
178
|
+
await this.client.send(
|
|
179
|
+
new import_client_s3.HeadObjectCommand({
|
|
180
|
+
Bucket: this.storage.options.bucket,
|
|
181
|
+
Key: this.getFileKey(record)
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
return true;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (["NotFound", "NoSuchKey", "NoSuchBucket"].includes(error.name)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async copy(source, target) {
|
|
193
|
+
const sourceKey = this.getFileKey(source);
|
|
194
|
+
await this.client.send(
|
|
195
|
+
new import_client_s3.CopyObjectCommand({
|
|
196
|
+
Bucket: this.storage.options.bucket,
|
|
197
|
+
Key: this.getFileKey(target),
|
|
198
|
+
CopySource: `${this.storage.options.bucket}/${sourceKey.split("/").map((segment) => encodeURIComponent(segment)).join("/")}`
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
}
|
|
176
202
|
async delete(records) {
|
|
177
203
|
const { Deleted } = await this.deleteS3Objects(
|
|
178
204
|
this.storage.options.bucket,
|
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { AttachmentModel, StorageType } from '.';
|
|
10
|
+
declare class TxCosStorage {
|
|
11
|
+
cos: any;
|
|
12
|
+
getFilename: Function;
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
options: Record<string, any>;
|
|
15
|
+
constructor({ config, baseUrl, filename }: {
|
|
16
|
+
config: any;
|
|
17
|
+
baseUrl: any;
|
|
18
|
+
filename: any;
|
|
19
|
+
});
|
|
20
|
+
getUploadedFileURL(key: string, location?: string): any;
|
|
21
|
+
_handleFile(req: any, file: any, cb: any): any;
|
|
22
|
+
_removeFile(req: any, file: any, cb: any): any;
|
|
23
|
+
}
|
|
10
24
|
export default class extends StorageType {
|
|
11
25
|
static defaults(): {
|
|
12
26
|
title: string;
|
|
@@ -21,6 +35,9 @@ export default class extends StorageType {
|
|
|
21
35
|
};
|
|
22
36
|
};
|
|
23
37
|
static filenameKey: string;
|
|
24
|
-
make():
|
|
38
|
+
make(): TxCosStorage;
|
|
39
|
+
exists(record: AttachmentModel): Promise<boolean>;
|
|
40
|
+
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
25
41
|
delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
|
|
26
42
|
}
|
|
43
|
+
export {};
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
var __create = Object.create;
|
|
10
11
|
var __defProp = Object.defineProperty;
|
|
11
12
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
13
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
15
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
16
|
var __export = (target, all) => {
|
|
15
17
|
for (var name in all)
|
|
@@ -23,6 +25,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
23
25
|
}
|
|
24
26
|
return to;
|
|
25
27
|
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
26
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
37
|
var tx_cos_exports = {};
|
|
28
38
|
__export(tx_cos_exports, {
|
|
@@ -30,9 +40,103 @@ __export(tx_cos_exports, {
|
|
|
30
40
|
});
|
|
31
41
|
module.exports = __toCommonJS(tx_cos_exports);
|
|
32
42
|
var import_util = require("util");
|
|
43
|
+
var import_stream = require("stream");
|
|
44
|
+
var import_url_join = __toESM(require("url-join"));
|
|
33
45
|
var import__ = require(".");
|
|
34
46
|
var import_constants = require("../../constants");
|
|
35
|
-
var
|
|
47
|
+
var import_utils = require("../utils");
|
|
48
|
+
const ERROR_NO_CLIENT = new Error("cos client undefined");
|
|
49
|
+
class CountingStream extends import_stream.Transform {
|
|
50
|
+
size = 0;
|
|
51
|
+
_transform(chunk, encoding, callback) {
|
|
52
|
+
this.size += chunk.length;
|
|
53
|
+
callback(null, chunk);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class TxCosStorage {
|
|
57
|
+
cos;
|
|
58
|
+
getFilename;
|
|
59
|
+
baseUrl;
|
|
60
|
+
options;
|
|
61
|
+
constructor({ config, baseUrl, filename }) {
|
|
62
|
+
const COS = require("cos-nodejs-sdk-v5");
|
|
63
|
+
this.cos = new COS({
|
|
64
|
+
SecretId: config.SecretId,
|
|
65
|
+
SecretKey: config.SecretKey,
|
|
66
|
+
SecurityToken: config.SecurityToken,
|
|
67
|
+
XCosSecurityToken: config.XCosSecurityToken,
|
|
68
|
+
FileParallelLimit: config.FileParallelLimit,
|
|
69
|
+
ChunkParallelLimit: config.ChunkParallelLimit,
|
|
70
|
+
ChunkSize: config.ChunkSize,
|
|
71
|
+
Domain: config.Domain,
|
|
72
|
+
Protocol: config.Protocol,
|
|
73
|
+
Timeout: config.Timeout,
|
|
74
|
+
KeepAlive: config.KeepAlive,
|
|
75
|
+
ForcePathStyle: config.ForcePathStyle,
|
|
76
|
+
CompatibilityMode: config.CompatibilityMode,
|
|
77
|
+
UseAccelerate: config.UseAccelerate
|
|
78
|
+
});
|
|
79
|
+
this.getFilename = filename;
|
|
80
|
+
this.baseUrl = baseUrl;
|
|
81
|
+
this.options = config;
|
|
82
|
+
}
|
|
83
|
+
getUploadedFileURL(key, location) {
|
|
84
|
+
if (this.baseUrl) {
|
|
85
|
+
return (0, import_url_join.default)(this.baseUrl, key);
|
|
86
|
+
}
|
|
87
|
+
if (!location) {
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
if (/^https?:\/\//.test(location)) {
|
|
91
|
+
return location;
|
|
92
|
+
}
|
|
93
|
+
return `https://${location}`;
|
|
94
|
+
}
|
|
95
|
+
_handleFile(req, file, cb) {
|
|
96
|
+
if (!this.cos) {
|
|
97
|
+
return cb(ERROR_NO_CLIENT);
|
|
98
|
+
}
|
|
99
|
+
const getFilename = (0, import_util.promisify)(this.getFilename);
|
|
100
|
+
Promise.resolve().then(async () => {
|
|
101
|
+
const key = await getFilename(req, file);
|
|
102
|
+
const counter = new CountingStream();
|
|
103
|
+
const body = file.stream.pipe(counter);
|
|
104
|
+
const params = {
|
|
105
|
+
Bucket: this.options.Bucket,
|
|
106
|
+
Region: this.options.Region,
|
|
107
|
+
Key: key,
|
|
108
|
+
Body: body
|
|
109
|
+
};
|
|
110
|
+
if (file.mimetype === "text/plain") {
|
|
111
|
+
params.ContentType = "text/plain; charset=utf-8";
|
|
112
|
+
} else if (file.mimetype) {
|
|
113
|
+
params.ContentType = file.mimetype;
|
|
114
|
+
}
|
|
115
|
+
const result = await (0, import_util.promisify)(this.cos.putObject).call(this.cos, params);
|
|
116
|
+
cb(null, {
|
|
117
|
+
key,
|
|
118
|
+
size: counter.size,
|
|
119
|
+
url: this.getUploadedFileURL(key, result == null ? void 0 : result.Location)
|
|
120
|
+
});
|
|
121
|
+
}).catch(cb);
|
|
122
|
+
}
|
|
123
|
+
_removeFile(req, file, cb) {
|
|
124
|
+
if (!this.cos) {
|
|
125
|
+
return cb(ERROR_NO_CLIENT);
|
|
126
|
+
}
|
|
127
|
+
if (!file.key) {
|
|
128
|
+
return cb(null);
|
|
129
|
+
}
|
|
130
|
+
this.cos.deleteObject(
|
|
131
|
+
{
|
|
132
|
+
Bucket: this.options.Bucket,
|
|
133
|
+
Region: this.options.Region,
|
|
134
|
+
Key: file.key
|
|
135
|
+
},
|
|
136
|
+
cb
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
36
140
|
class tx_cos_default extends import__.StorageType {
|
|
37
141
|
static defaults() {
|
|
38
142
|
return {
|
|
@@ -50,13 +154,37 @@ class tx_cos_default extends import__.StorageType {
|
|
|
50
154
|
}
|
|
51
155
|
static filenameKey = "url";
|
|
52
156
|
make() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
157
|
+
return new TxCosStorage({
|
|
158
|
+
config: this.storage.options,
|
|
159
|
+
baseUrl: this.storage.baseUrl,
|
|
160
|
+
filename: (0, import_utils.cloudFilenameGetter)(this.storage)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async exists(record) {
|
|
164
|
+
const { cos } = this.make();
|
|
165
|
+
try {
|
|
166
|
+
await (0, import_util.promisify)(cos.headObject).call(cos, {
|
|
167
|
+
Region: this.storage.options.Region,
|
|
168
|
+
Bucket: this.storage.options.Bucket,
|
|
169
|
+
Key: (0, import_utils.getFileKey)(record)
|
|
170
|
+
});
|
|
171
|
+
return true;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (["NoSuchKey", "NotFound"].includes(error.name)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async copy(source, target) {
|
|
180
|
+
const { cos } = this.make();
|
|
181
|
+
const sourceKey = (0, import_utils.getFileKey)(source);
|
|
182
|
+
const copySource = `${this.storage.options.Bucket}.cos.${this.storage.options.Region}.myqcloud.com/${sourceKey.split("/").map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
183
|
+
await (0, import_util.promisify)(cos.putObjectCopy).call(cos, {
|
|
184
|
+
Region: this.storage.options.Region,
|
|
185
|
+
Bucket: this.storage.options.Bucket,
|
|
186
|
+
Key: (0, import_utils.getFileKey)(target),
|
|
187
|
+
CopySource: copySource
|
|
60
188
|
});
|
|
61
189
|
}
|
|
62
190
|
async delete(records) {
|
|
@@ -64,8 +192,8 @@ class tx_cos_default extends import__.StorageType {
|
|
|
64
192
|
const { Deleted } = await (0, import_util.promisify)(cos.deleteMultipleObject).call(cos, {
|
|
65
193
|
Region: this.storage.options.Region,
|
|
66
194
|
Bucket: this.storage.options.Bucket,
|
|
67
|
-
Objects: records.map((record) => ({ Key: (0,
|
|
195
|
+
Objects: records.map((record) => ({ Key: (0, import_utils.getFileKey)(record) }))
|
|
68
196
|
});
|
|
69
|
-
return [Deleted.length, records.filter((record) => !Deleted.find((item) => item.Key === (0,
|
|
197
|
+
return [Deleted.length, records.filter((record) => !Deleted.find((item) => item.Key === (0, import_utils.getFileKey)(record)))];
|
|
70
198
|
}
|
|
71
199
|
}
|
package/dist/server/utils.d.ts
CHANGED
|
@@ -10,5 +10,8 @@ export declare function getFilename(req: any, file: any, cb: any): void;
|
|
|
10
10
|
export declare const cloudFilenameGetter: (storage: any) => (req: any, file: any, cb: any) => void;
|
|
11
11
|
export declare const diskFilenameGetter: (storage: any) => (req: any, file: any, cb: any) => void;
|
|
12
12
|
export declare function getFileKey(record: any): any;
|
|
13
|
+
export declare function normalizeStorageSubPath(subPath?: unknown): string;
|
|
14
|
+
export declare function resolveStoragePath(storagePath?: unknown, subPath?: unknown): string;
|
|
13
15
|
export declare function ensureUrlEncoded(value: any): any;
|
|
14
16
|
export declare function encodeURL(url: any): any;
|
|
17
|
+
export declare function normalizeDocumentRoot(documentRoot?: string): string;
|
package/dist/server/utils.js
CHANGED
|
@@ -41,13 +41,23 @@ __export(utils_exports, {
|
|
|
41
41
|
encodeURL: () => encodeURL,
|
|
42
42
|
ensureUrlEncoded: () => ensureUrlEncoded,
|
|
43
43
|
getFileKey: () => getFileKey,
|
|
44
|
-
getFilename: () => getFilename
|
|
44
|
+
getFilename: () => getFilename,
|
|
45
|
+
normalizeDocumentRoot: () => normalizeDocumentRoot,
|
|
46
|
+
normalizeStorageSubPath: () => normalizeStorageSubPath,
|
|
47
|
+
resolveStoragePath: () => resolveStoragePath
|
|
45
48
|
});
|
|
46
49
|
module.exports = __toCommonJS(utils_exports);
|
|
47
50
|
var import_utils = require("@nocobase/utils");
|
|
48
51
|
var import_crypto = __toESM(require("crypto"));
|
|
49
52
|
var import_path = __toESM(require("path"));
|
|
50
53
|
var import_url_join = __toESM(require("url-join"));
|
|
54
|
+
const INVALID_FILENAME_CHARS = /* @__PURE__ */ new Set(["<", ">", "?", "*", "|", ":", '"', "\\", "/"]);
|
|
55
|
+
function sanitizeFilename(value) {
|
|
56
|
+
return Array.from(value).map((char) => {
|
|
57
|
+
const code = char.charCodeAt(0);
|
|
58
|
+
return code < 32 || code === 127 || INVALID_FILENAME_CHARS.has(char) ? "-" : char;
|
|
59
|
+
}).join("");
|
|
60
|
+
}
|
|
51
61
|
function normalizeOriginalname(file) {
|
|
52
62
|
const originalname = file == null ? void 0 : file.originalname;
|
|
53
63
|
if (!originalname) {
|
|
@@ -56,6 +66,9 @@ function normalizeOriginalname(file) {
|
|
|
56
66
|
if (Buffer.isBuffer(originalname)) {
|
|
57
67
|
return originalname.toString("utf8");
|
|
58
68
|
}
|
|
69
|
+
if (Array.from(originalname).some((char) => char.charCodeAt(0) > 255)) {
|
|
70
|
+
return originalname;
|
|
71
|
+
}
|
|
59
72
|
const decoded = Buffer.from(originalname, "binary").toString("utf8");
|
|
60
73
|
if (decoded.includes("\uFFFD")) {
|
|
61
74
|
return originalname;
|
|
@@ -64,13 +77,13 @@ function normalizeOriginalname(file) {
|
|
|
64
77
|
}
|
|
65
78
|
function getFilename(req, file, cb) {
|
|
66
79
|
const originalname = normalizeOriginalname(file);
|
|
67
|
-
const baseName = import_path.default.basename(originalname
|
|
80
|
+
const baseName = import_path.default.basename(sanitizeFilename(originalname), import_path.default.extname(originalname));
|
|
68
81
|
cb(null, `${baseName}-${(0, import_utils.uid)(6)}${import_path.default.extname(originalname)}`);
|
|
69
82
|
}
|
|
70
83
|
function getOriginalFilename(file) {
|
|
71
84
|
const originalname = normalizeOriginalname(file);
|
|
72
85
|
const extname = import_path.default.extname(originalname);
|
|
73
|
-
const baseName = import_path.default.basename(originalname
|
|
86
|
+
const baseName = import_path.default.basename(sanitizeFilename(originalname), extname);
|
|
74
87
|
return `${baseName}${extname}`;
|
|
75
88
|
}
|
|
76
89
|
const cloudFilenameGetter = (storage) => (req, file, cb) => {
|
|
@@ -118,6 +131,41 @@ const diskFilenameGetter = (storage) => (req, file, cb) => {
|
|
|
118
131
|
function getFileKey(record) {
|
|
119
132
|
return (0, import_url_join.default)(record.path || "", record.filename).replace(/^\//, "");
|
|
120
133
|
}
|
|
134
|
+
function pathError(message) {
|
|
135
|
+
const error = new Error(message);
|
|
136
|
+
error.code = "PATH_TRAVERSAL";
|
|
137
|
+
return error;
|
|
138
|
+
}
|
|
139
|
+
function normalizeStoragePathForJoin(value, message, { allowLeadingSlash = false } = {}) {
|
|
140
|
+
if (value == null || value === "") {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
if (typeof value !== "string" || value.includes("\0")) {
|
|
144
|
+
throw pathError(message);
|
|
145
|
+
}
|
|
146
|
+
const normalized = value.replace(/\\/g, "/");
|
|
147
|
+
if (!allowLeadingSlash && normalized.startsWith("/")) {
|
|
148
|
+
throw pathError(message);
|
|
149
|
+
}
|
|
150
|
+
const segments = normalized.replace(/^\/+|\/+$/g, "").split("/").filter((segment) => segment && segment !== ".");
|
|
151
|
+
if (segments.some((segment) => segment === "..")) {
|
|
152
|
+
throw pathError("Access denied");
|
|
153
|
+
}
|
|
154
|
+
return segments.join("/");
|
|
155
|
+
}
|
|
156
|
+
function normalizeStorageSubPath(subPath) {
|
|
157
|
+
return normalizeStoragePathForJoin(subPath, "Invalid storage sub path");
|
|
158
|
+
}
|
|
159
|
+
function resolveStoragePath(storagePath, subPath) {
|
|
160
|
+
const normalizedSubPath = normalizeStorageSubPath(subPath);
|
|
161
|
+
if (!normalizedSubPath) {
|
|
162
|
+
return typeof storagePath === "string" ? storagePath : "";
|
|
163
|
+
}
|
|
164
|
+
const normalizedStoragePath = normalizeStoragePathForJoin(storagePath, "Invalid storage path", {
|
|
165
|
+
allowLeadingSlash: true
|
|
166
|
+
});
|
|
167
|
+
return normalizedStoragePath ? `${normalizedStoragePath}/${normalizedSubPath}` : normalizedSubPath;
|
|
168
|
+
}
|
|
121
169
|
function ensureUrlEncoded(value) {
|
|
122
170
|
try {
|
|
123
171
|
if (decodeURIComponent(value) !== value) {
|
|
@@ -140,6 +188,23 @@ function encodeURL(url) {
|
|
|
140
188
|
return url;
|
|
141
189
|
}
|
|
142
190
|
}
|
|
191
|
+
function isStorageRelativeDocumentRoot(documentRoot) {
|
|
192
|
+
return documentRoot === "storage" || documentRoot.startsWith("storage/") || documentRoot === "./storage" || documentRoot.startsWith("./storage/");
|
|
193
|
+
}
|
|
194
|
+
function normalizeDocumentRoot(documentRoot) {
|
|
195
|
+
if (!documentRoot) {
|
|
196
|
+
return (0, import_utils.storagePathJoin)("uploads");
|
|
197
|
+
}
|
|
198
|
+
if (import_path.default.isAbsolute(documentRoot)) {
|
|
199
|
+
return documentRoot;
|
|
200
|
+
}
|
|
201
|
+
const normalizedDocumentRoot = documentRoot.replace(/\\/g, "/");
|
|
202
|
+
if (isStorageRelativeDocumentRoot(normalizedDocumentRoot)) {
|
|
203
|
+
const relativePath = normalizedDocumentRoot.replace(/^\.?\/?storage(?:\/|$)/, "").replace(/^[/\\]+/, "");
|
|
204
|
+
return relativePath ? (0, import_utils.storagePathJoin)(relativePath) : (0, import_utils.storagePathJoin)();
|
|
205
|
+
}
|
|
206
|
+
return import_path.default.resolve(process.cwd(), documentRoot);
|
|
207
|
+
}
|
|
143
208
|
// Annotate the CommonJS export names for ESM import in node:
|
|
144
209
|
0 && (module.exports = {
|
|
145
210
|
cloudFilenameGetter,
|
|
@@ -147,5 +212,8 @@ function encodeURL(url) {
|
|
|
147
212
|
encodeURL,
|
|
148
213
|
ensureUrlEncoded,
|
|
149
214
|
getFileKey,
|
|
150
|
-
getFilename
|
|
215
|
+
getFilename,
|
|
216
|
+
normalizeDocumentRoot,
|
|
217
|
+
normalizeStorageSubPath,
|
|
218
|
+
resolveStoragePath
|
|
151
219
|
});
|