@nocobase/plugin-file-manager 2.0.57 → 2.0.59

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.
Files changed (46) hide show
  1. package/dist/client/867ada653cd02a3e.mjs +6 -0
  2. package/dist/client/index.js +1 -1
  3. package/dist/client/previewer/filePreviewTypes.d.ts +3 -0
  4. package/dist/externalVersion.js +8 -8
  5. package/dist/locale/de-DE.json +3 -0
  6. package/dist/locale/en-US.json +3 -0
  7. package/dist/locale/es-ES.json +3 -0
  8. package/dist/locale/fr-FR.json +3 -0
  9. package/dist/locale/hu-HU.json +4 -1
  10. package/dist/locale/id-ID.json +4 -1
  11. package/dist/locale/it-IT.json +3 -0
  12. package/dist/locale/ja-JP.json +3 -0
  13. package/dist/locale/ko-KR.json +3 -0
  14. package/dist/locale/nl-NL.json +3 -0
  15. package/dist/locale/pt-BR.json +3 -0
  16. package/dist/locale/ru-RU.json +3 -0
  17. package/dist/locale/tr-TR.json +3 -0
  18. package/dist/locale/uk-UA.json +3 -0
  19. package/dist/locale/vi-VN.json +4 -1
  20. package/dist/locale/zh-CN.json +3 -0
  21. package/dist/locale/zh-TW.json +3 -0
  22. package/dist/node_modules/@aws-sdk/client-s3/package.json +1 -1
  23. package/dist/node_modules/@aws-sdk/lib-storage/package.json +1 -1
  24. package/dist/node_modules/ali-oss/package.json +1 -1
  25. package/dist/node_modules/cos-nodejs-sdk-v5/package.json +1 -1
  26. package/dist/node_modules/mime-match/package.json +1 -1
  27. package/dist/node_modules/mime-types/package.json +1 -1
  28. package/dist/node_modules/mkdirp/package.json +1 -1
  29. package/dist/node_modules/url-join/package.json +1 -1
  30. package/dist/server/commands/repair-filenames.d.ts +55 -0
  31. package/dist/server/commands/repair-filenames.js +283 -0
  32. package/dist/server/server.d.ts +1 -0
  33. package/dist/server/server.js +4 -0
  34. package/dist/server/storages/ali-oss.d.ts +3 -1
  35. package/dist/server/storages/ali-oss.js +23 -2
  36. package/dist/server/storages/index.d.ts +3 -0
  37. package/dist/server/storages/index.js +6 -0
  38. package/dist/server/storages/local.d.ts +2 -0
  39. package/dist/server/storages/local.js +18 -0
  40. package/dist/server/storages/s3.d.ts +2 -0
  41. package/dist/server/storages/s3.js +26 -0
  42. package/dist/server/storages/tx-cos.d.ts +2 -0
  43. package/dist/server/storages/tx-cos.js +27 -0
  44. package/dist/server/utils.js +12 -2
  45. package/package.json +5 -3
  46. package/dist/client/301.4bc6687c4a9cf8e0.js +0 -15
@@ -0,0 +1,283 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var repair_filenames_exports = {};
28
+ __export(repair_filenames_exports, {
29
+ getRepairedAttachmentValues: () => getRepairedAttachmentValues,
30
+ registerRepairFilenamesCommand: () => registerRepairFilenamesCommand,
31
+ repairAttachmentFilenames: () => repairAttachmentFilenames,
32
+ replaceInvisibleChars: () => replaceInvisibleChars
33
+ });
34
+ module.exports = __toCommonJS(repair_filenames_exports);
35
+ var import_sequelize = require("sequelize");
36
+ var import_utils = require("../utils");
37
+ function containsInvisibleChars(value) {
38
+ return Array.from(value || "").some((char) => {
39
+ const code = char.charCodeAt(0);
40
+ return code <= 31 || code >= 127 && code <= 159;
41
+ });
42
+ }
43
+ function replaceInvisibleChars(value) {
44
+ return Array.from(value || "").map((char) => {
45
+ const code = char.charCodeAt(0);
46
+ return code <= 31 || code >= 127 && code <= 159 ? "-" : char;
47
+ }).join("");
48
+ }
49
+ function getRepairedAttachmentValues(record) {
50
+ return {
51
+ path: replaceInvisibleChars(record.path),
52
+ filename: replaceInvisibleChars(record.filename)
53
+ };
54
+ }
55
+ function updateUrlValue(url, oldKey, newKey) {
56
+ if (!url) {
57
+ return url;
58
+ }
59
+ const encodedOldKey = oldKey.split("/").map((segment) => encodeURIComponent(segment)).join("/");
60
+ const encodedNewKey = newKey.split("/").map((segment) => encodeURIComponent(segment)).join("/");
61
+ return url.split(oldKey).join(newKey).split(encodedOldKey).join(encodedNewKey);
62
+ }
63
+ function formatItem(item) {
64
+ return {
65
+ collectionName: item.collectionName,
66
+ id: item.id,
67
+ storageId: item.storageId,
68
+ storageType: item.storageType,
69
+ oldKey: item.oldKey,
70
+ newKey: item.newKey,
71
+ sourceExists: item.sourceExists,
72
+ targetExists: item.targetExists,
73
+ status: item.status,
74
+ reason: item.reason
75
+ };
76
+ }
77
+ async function getFileCollectionNames(app) {
78
+ var _a;
79
+ const repository = app.db.getRepository("collections") || ((_a = app.db.getCollection("collections")) == null ? void 0 : _a.repository);
80
+ if (repository) {
81
+ const collections = await repository.find({
82
+ filter: {
83
+ "options.template": "file"
84
+ }
85
+ });
86
+ return Array.from(/* @__PURE__ */ new Set(["attachments", ...collections.map((collection) => collection.get("name"))]));
87
+ }
88
+ return Array.from(
89
+ /* @__PURE__ */ new Set([
90
+ "attachments",
91
+ ...Array.from(app.db.collections.values()).filter((collection) => collection.options.template === "file").map((collection) => collection.name)
92
+ ])
93
+ );
94
+ }
95
+ async function loadFileCollections(app) {
96
+ var _a, _b;
97
+ const collection = app.db.getCollection("collections");
98
+ if (!collection) {
99
+ return;
100
+ }
101
+ const repository = collection.repository;
102
+ await ((_a = repository.setApp) == null ? void 0 : _a.call(repository, app));
103
+ await ((_b = repository.load) == null ? void 0 : _b.call(repository));
104
+ }
105
+ async function repairCollectionAttachmentFilenames({
106
+ app,
107
+ fileManager,
108
+ collectionName,
109
+ result,
110
+ apply,
111
+ batchSize,
112
+ limit
113
+ }) {
114
+ const collection = app.db.getCollection(collectionName);
115
+ if (!collection) {
116
+ return;
117
+ }
118
+ const Model = collection.model;
119
+ let lastId = null;
120
+ while (result.scanned < limit) {
121
+ const rows = await Model.findAll({
122
+ attributes: ["id", "path", "filename", "storageId", "url"],
123
+ where: lastId ? { id: { [import_sequelize.Op.gt]: lastId } } : void 0,
124
+ order: [["id", "ASC"]],
125
+ limit: Math.min(batchSize, limit - result.scanned)
126
+ });
127
+ if (!rows.length) {
128
+ break;
129
+ }
130
+ for (const row of rows) {
131
+ result.scanned += 1;
132
+ lastId = row.get("id");
133
+ const record = row.toJSON();
134
+ if (!containsInvisibleChars(record.path) && !containsInvisibleChars(record.filename)) {
135
+ continue;
136
+ }
137
+ const storage = fileManager.storagesCache.get(record.storageId);
138
+ const { path: newPath, filename: newFilename } = getRepairedAttachmentValues(record);
139
+ const oldKey = (0, import_utils.getFileKey)(record);
140
+ const newRecord = { ...record, path: newPath, filename: newFilename };
141
+ const newKey = (0, import_utils.getFileKey)(newRecord);
142
+ const item = {
143
+ collectionName,
144
+ id: lastId,
145
+ storageId: record.storageId,
146
+ storageType: (storage == null ? void 0 : storage.type) || "",
147
+ oldPath: record.path || "",
148
+ oldFilename: record.filename,
149
+ newPath,
150
+ newFilename,
151
+ oldKey,
152
+ newKey,
153
+ status: "pending"
154
+ };
155
+ result.candidates.push(item);
156
+ if (!storage) {
157
+ item.status = "skipped";
158
+ item.reason = "storage_not_found";
159
+ result.skipped.push(item);
160
+ continue;
161
+ }
162
+ if (oldKey === newKey) {
163
+ item.status = "skipped";
164
+ item.reason = "unchanged";
165
+ result.skipped.push(item);
166
+ continue;
167
+ }
168
+ try {
169
+ const StorageClass = fileManager.storageTypes.get(storage.type);
170
+ if (!StorageClass) {
171
+ item.status = "skipped";
172
+ item.reason = "storage_type_not_found";
173
+ result.skipped.push(item);
174
+ continue;
175
+ }
176
+ const storageInstance = new StorageClass(storage);
177
+ item.sourceExists = await storageInstance.exists(record);
178
+ if (!item.sourceExists) {
179
+ item.status = "skipped";
180
+ item.reason = "source_not_found";
181
+ result.skipped.push(item);
182
+ continue;
183
+ }
184
+ item.targetExists = await storageInstance.exists(newRecord);
185
+ if (item.targetExists) {
186
+ item.status = "skipped";
187
+ item.reason = "target_exists";
188
+ result.skipped.push(item);
189
+ continue;
190
+ }
191
+ if (apply) {
192
+ await storageInstance.copy(record, newRecord);
193
+ await row.update({
194
+ path: newPath,
195
+ filename: newFilename,
196
+ url: updateUrlValue(record.url, oldKey, newKey)
197
+ });
198
+ try {
199
+ const [deleted] = await storageInstance.delete([record]);
200
+ if (!deleted) {
201
+ item.reason = "delete_old_failed";
202
+ }
203
+ } catch (error) {
204
+ item.reason = `delete_old_failed: ${error.message}`;
205
+ }
206
+ item.status = "repaired";
207
+ result.repaired.push(item);
208
+ }
209
+ } catch (error) {
210
+ item.status = "failed";
211
+ item.reason = error.message;
212
+ result.failed.push(item);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ async function repairAttachmentFilenames(app, fileManager, options = {}) {
218
+ const apply = !!options.apply;
219
+ const batchSize = options.batchSize || 500;
220
+ const limit = options.limit || Number.POSITIVE_INFINITY;
221
+ const result = {
222
+ dryRun: !apply,
223
+ scanned: 0,
224
+ candidates: [],
225
+ repaired: [],
226
+ skipped: [],
227
+ failed: []
228
+ };
229
+ if (!fileManager.storagesCache.size) {
230
+ await fileManager.loadStorages();
231
+ }
232
+ await loadFileCollections(app);
233
+ for (const collectionName of await getFileCollectionNames(app)) {
234
+ if (result.scanned >= limit) {
235
+ break;
236
+ }
237
+ await repairCollectionAttachmentFilenames({
238
+ app,
239
+ fileManager,
240
+ collectionName,
241
+ result,
242
+ apply,
243
+ batchSize,
244
+ limit
245
+ });
246
+ }
247
+ return result;
248
+ }
249
+ function registerRepairFilenamesCommand(app) {
250
+ const command = app.findCommand("file-manager") || app.command("file-manager");
251
+ command.command("repair-filenames").preload().option("--apply", "rename objects and update attachment records").option("--batch-size [batchSize]", "batch size for scanning attachments").option("--limit [limit]", "maximum number of attachment records to scan").action(async (options) => {
252
+ const fileManager = app.pm.get("file-manager");
253
+ const result = await repairAttachmentFilenames(app, fileManager, {
254
+ apply: !!options.apply,
255
+ batchSize: options.batchSize ? Number(options.batchSize) : void 0,
256
+ limit: options.limit ? Number(options.limit) : void 0
257
+ });
258
+ console.log(
259
+ JSON.stringify(
260
+ {
261
+ dryRun: result.dryRun,
262
+ scanned: result.scanned,
263
+ candidates: result.candidates.length,
264
+ repaired: result.repaired.length,
265
+ skipped: result.skipped.length,
266
+ failed: result.failed.length
267
+ },
268
+ null,
269
+ 2
270
+ )
271
+ );
272
+ if (result.candidates.length) {
273
+ console.table(result.candidates.map(formatItem));
274
+ }
275
+ });
276
+ }
277
+ // Annotate the CommonJS export names for ESM import in node:
278
+ 0 && (module.exports = {
279
+ getRepairedAttachmentValues,
280
+ registerRepairFilenamesCommand,
281
+ repairAttachmentFilenames,
282
+ replaceInvisibleChars
283
+ });
@@ -27,6 +27,7 @@ export type UploadFileOptions = {
27
27
  export declare class PluginFileManagerServer extends Plugin {
28
28
  storageTypes: Registry<StorageClassType>;
29
29
  storagesCache: Map<string | number, StorageModel>;
30
+ static staticImport(): Promise<void>;
30
31
  afterDestroy: (record: Model, options: any) => Promise<void>;
31
32
  registerStorageType(type: string, Type: StorageClassType): void;
32
33
  createFileRecord(options: FileRecordOptions): Promise<any>;
@@ -54,6 +54,7 @@ var import_local = __toESM(require("./storages/local"));
54
54
  var import_s3 = __toESM(require("./storages/s3"));
55
55
  var import_tx_cos = __toESM(require("./storages/tx-cos"));
56
56
  var import_utils2 = require("./utils");
57
+ var import_repair_filenames = require("./commands/repair-filenames");
57
58
  const DEFAULT_STORAGE_TYPE = import_constants.STORAGE_TYPE_LOCAL;
58
59
  class FileDeleteError extends Error {
59
60
  data;
@@ -66,6 +67,9 @@ class FileDeleteError extends Error {
66
67
  class PluginFileManagerServer extends import_server.Plugin {
67
68
  storageTypes = new import_utils.Registry();
68
69
  storagesCache = /* @__PURE__ */ new Map();
70
+ static async staticImport() {
71
+ import_server.Application.addCommand(import_repair_filenames.registerRepairFilenamesCommand);
72
+ }
69
73
  afterDestroy = async (record, options) => {
70
74
  var _a;
71
75
  const { collection } = record.constructor;
@@ -18,7 +18,7 @@ declare class AliYunOssStorage {
18
18
  filename?: typeof getRandomFilename;
19
19
  });
20
20
  _handleFile(req: any, file: any, cb: any): any;
21
- _removeFile(req: any, file: any, cb: any): any;
21
+ _removeFile(req: any, file: any, cb: any): Promise<any>;
22
22
  }
23
23
  export default class extends StorageType {
24
24
  static defaults(): {
@@ -34,6 +34,8 @@ export default class extends StorageType {
34
34
  };
35
35
  };
36
36
  make(): AliYunOssStorage;
37
+ exists(record: AttachmentModel): Promise<boolean>;
38
+ copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
37
39
  delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
38
40
  }
39
41
  export {};
@@ -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
- this.client.delete(file.filename).then((result) => cb(null, result)).catch(cb);
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;
@@ -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
  }
@@ -27,6 +27,8 @@ export default class extends StorageType {
27
27
  };
28
28
  };
29
29
  make(): multer.StorageEngine;
30
+ exists(record: AttachmentModel): Promise<boolean>;
31
+ copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
30
32
  delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
31
33
  getFileURL(file: AttachmentModel, preview?: boolean): Promise<any>;
32
34
  getFileStream(file: AttachmentModel): Promise<{
@@ -92,6 +92,24 @@ class local_default extends import__.StorageType {
92
92
  filename: (0, import_utils2.diskFilenameGetter)(this.storage)
93
93
  });
94
94
  }
95
+ async exists(record) {
96
+ try {
97
+ await import_promises.default.stat(resolveSafePath(getDocumentRoot(this.storage), record.path, record.filename));
98
+ return true;
99
+ } catch (error) {
100
+ if (error.code === "ENOENT") {
101
+ return false;
102
+ }
103
+ throw error;
104
+ }
105
+ }
106
+ async copy(source, target) {
107
+ const documentRoot = getDocumentRoot(this.storage);
108
+ const sourcePath = resolveSafePath(documentRoot, source.path, source.filename);
109
+ const targetPath = resolveSafePath(documentRoot, target.path, target.filename);
110
+ await import_promises.default.mkdir(import_path.default.dirname(targetPath), { recursive: true });
111
+ await import_promises.default.copyFile(sourcePath, targetPath);
112
+ }
95
113
  async delete(records) {
96
114
  const documentRoot = getDocumentRoot(this.storage);
97
115
  let count = 0;
@@ -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,
@@ -36,6 +36,8 @@ export default class extends StorageType {
36
36
  };
37
37
  static filenameKey: string;
38
38
  make(): TxCosStorage;
39
+ exists(record: AttachmentModel): Promise<boolean>;
40
+ copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
39
41
  delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
40
42
  }
41
43
  export {};
@@ -160,6 +160,33 @@ class tx_cos_default extends import__.StorageType {
160
160
  filename: (0, import_utils.cloudFilenameGetter)(this.storage)
161
161
  });
162
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
188
+ });
189
+ }
163
190
  async delete(records) {
164
191
  const { cos } = this.make();
165
192
  const { Deleted } = await (0, import_util.promisify)(cos.deleteMultipleObject).call(cos, {
@@ -48,6 +48,13 @@ var import_utils = require("@nocobase/utils");
48
48
  var import_crypto = __toESM(require("crypto"));
49
49
  var import_path = __toESM(require("path"));
50
50
  var import_url_join = __toESM(require("url-join"));
51
+ const INVALID_FILENAME_CHARS = /* @__PURE__ */ new Set(["<", ">", "?", "*", "|", ":", '"', "\\", "/"]);
52
+ function sanitizeFilename(value) {
53
+ return Array.from(value).map((char) => {
54
+ const code = char.charCodeAt(0);
55
+ return code < 32 || code === 127 || INVALID_FILENAME_CHARS.has(char) ? "-" : char;
56
+ }).join("");
57
+ }
51
58
  function normalizeOriginalname(file) {
52
59
  const originalname = file == null ? void 0 : file.originalname;
53
60
  if (!originalname) {
@@ -56,6 +63,9 @@ function normalizeOriginalname(file) {
56
63
  if (Buffer.isBuffer(originalname)) {
57
64
  return originalname.toString("utf8");
58
65
  }
66
+ if (Array.from(originalname).some((char) => char.charCodeAt(0) > 255)) {
67
+ return originalname;
68
+ }
59
69
  const decoded = Buffer.from(originalname, "binary").toString("utf8");
60
70
  if (decoded.includes("\uFFFD")) {
61
71
  return originalname;
@@ -64,13 +74,13 @@ function normalizeOriginalname(file) {
64
74
  }
65
75
  function getFilename(req, file, cb) {
66
76
  const originalname = normalizeOriginalname(file);
67
- const baseName = import_path.default.basename(originalname.replace(/[<>?*|:"\\/]/g, "-"), import_path.default.extname(originalname));
77
+ const baseName = import_path.default.basename(sanitizeFilename(originalname), import_path.default.extname(originalname));
68
78
  cb(null, `${baseName}-${(0, import_utils.uid)(6)}${import_path.default.extname(originalname)}`);
69
79
  }
70
80
  function getOriginalFilename(file) {
71
81
  const originalname = normalizeOriginalname(file);
72
82
  const extname = import_path.default.extname(originalname);
73
- const baseName = import_path.default.basename(originalname.replace(/[<>?*|:"\\/]/g, "-"), extname);
83
+ const baseName = import_path.default.basename(sanitizeFilename(originalname), extname);
74
84
  return `${baseName}${extname}`;
75
85
  }
76
86
  const cloudFilenameGetter = (storage) => (req, file, cb) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-file-manager",
3
- "version": "2.0.57",
3
+ "version": "2.0.59",
4
4
  "displayName": "File manager",
5
5
  "displayName.ru-RU": "Менеджер файлов",
6
6
  "displayName.zh-CN": "文件管理器",
@@ -27,7 +27,6 @@
27
27
  "antd": "5.x",
28
28
  "axios": "^1.7.0",
29
29
  "cos-nodejs-sdk-v5": "^2.11.14",
30
- "file-type": "^21.0.0",
31
30
  "koa-static": "^5.0.0",
32
31
  "mime-match": "^1.0.2",
33
32
  "mime-types": "^3.0.1",
@@ -41,6 +40,9 @@
41
40
  "supertest": "^6.1.6",
42
41
  "url-join": "4.0.1"
43
42
  },
43
+ "dependencies": {
44
+ "file-type": "^21.0.0"
45
+ },
44
46
  "peerDependencies": {
45
47
  "@nocobase/actions": "2.x",
46
48
  "@nocobase/client": "2.x",
@@ -53,5 +55,5 @@
53
55
  "Collections",
54
56
  "Collection fields"
55
57
  ],
56
- "gitHead": "629bf05e63bca0cbd60fffb87057d45bda5d9222"
58
+ "gitHead": "8b3a0cfadf6cb2da4a7e9213ef0d8d092d885c4a"
57
59
  }