@nocobase/plugin-file-manager 2.1.0-alpha.45 → 2.1.0-alpha.47

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.
@@ -0,0 +1,73 @@
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 storage_validation_exports = {};
28
+ __export(storage_validation_exports, {
29
+ validateStorageMiddleware: () => validateStorageMiddleware
30
+ });
31
+ module.exports = __toCommonJS(storage_validation_exports);
32
+ var import_local = require("../storages/local");
33
+ async function validateStorageMiddleware(ctx, next) {
34
+ const { actionName, params } = ctx.action;
35
+ const values = params.values || {};
36
+ let storage = values;
37
+ const hasSubmittedDocumentRoot = Object.prototype.hasOwnProperty.call(values.options || {}, "documentRoot");
38
+ if (actionName === "update" && params.filterByTk) {
39
+ const repository = ctx.db.getRepository("storages");
40
+ let existing = await repository.findById(params.filterByTk);
41
+ if (!existing) {
42
+ existing = await repository.findOne({
43
+ filter: {
44
+ name: params.filterByTk
45
+ }
46
+ });
47
+ }
48
+ if (existing) {
49
+ const existingValues = existing.toJSON();
50
+ storage = {
51
+ ...existingValues,
52
+ ...values,
53
+ options: {
54
+ ...existingValues.options || {},
55
+ ...values.options || {}
56
+ }
57
+ };
58
+ }
59
+ }
60
+ try {
61
+ (0, import_local.validateLocalStorageConfig)(storage, { validateDocumentRoot: hasSubmittedDocumentRoot });
62
+ } catch (error) {
63
+ if (error.code === "PATH_TRAVERSAL") {
64
+ return ctx.throw(400, error.message);
65
+ }
66
+ throw error;
67
+ }
68
+ await next();
69
+ }
70
+ // Annotate the CommonJS export names for ESM import in node:
71
+ 0 && (module.exports = {
72
+ validateStorageMiddleware
73
+ });
@@ -216,6 +216,9 @@ class PluginFileManagerServer extends import_server.Plugin {
216
216
  this.storageTypes.register(import_constants.STORAGE_TYPE_S3, import_s3.default);
217
217
  this.storageTypes.register(import_constants.STORAGE_TYPE_TX_COS, import_tx_cos.default);
218
218
  const Storage = this.db.getModel("storages");
219
+ Storage.beforeSave((m) => {
220
+ (0, import_local.validateLocalStorageConfig)(m.toJSON());
221
+ });
219
222
  Storage.afterSave(async (m, { transaction }) => {
220
223
  await this.loadStorages({ transaction });
221
224
  this.sendSyncMessage({ type: "reloadStorages" }, { transaction });
@@ -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;
@@ -38,7 +38,9 @@ var local_exports = {};
38
38
  __export(local_exports, {
39
39
  default: () => local_default,
40
40
  getDocumentRoot: () => getDocumentRoot,
41
- resolveSafePath: () => resolveSafePath
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,25 +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
92
  var _a;
56
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");
57
- if (import_path.default.isAbsolute(raw)) {
58
- return raw;
59
- }
60
- return (0, import_utils2.normalizeDocumentRoot)(raw);
94
+ return resolveDocumentRoot(raw);
61
95
  }
62
96
  function resolveSafePath(documentRoot, filePath, filename) {
63
- const root = (0, import_utils2.normalizeDocumentRoot)(documentRoot);
97
+ const root = resolveDocumentRoot(documentRoot);
64
98
  const target = import_path.default.resolve(root, filePath || "", filename || "");
65
- const relative = import_path.default.relative(root, target);
66
- if (relative.startsWith("..") || import_path.default.isAbsolute(relative)) {
67
- const error = new Error("Access denied");
68
- error.code = "PATH_TRAVERSAL";
69
- throw error;
99
+ if (!isInside(root, target)) {
100
+ throw pathError("Access denied");
70
101
  }
71
102
  return target;
72
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
+ }
73
114
  class local_default extends import__.StorageType {
74
115
  static defaults() {
75
116
  return {
@@ -89,7 +130,7 @@ class local_default extends import__.StorageType {
89
130
  make() {
90
131
  return import_multer.default.diskStorage({
91
132
  destination: (req, file, cb) => {
92
- const destPath = import_path.default.join(getDocumentRoot(this.storage), this.storage.path || "");
133
+ const destPath = resolveSafePath(getDocumentRoot(this.storage), normalizeLocalStoragePath(this.storage.path));
93
134
  const mkdirp = require("mkdirp");
94
135
  mkdirp(destPath, (err) => cb(err, destPath));
95
136
  },
@@ -162,5 +203,7 @@ class local_default extends import__.StorageType {
162
203
  // Annotate the CommonJS export names for ESM import in node:
163
204
  0 && (module.exports = {
164
205
  getDocumentRoot,
165
- resolveSafePath
206
+ normalizeLocalStoragePath,
207
+ resolveSafePath,
208
+ validateLocalStorageConfig
166
209
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-file-manager",
3
- "version": "2.1.0-alpha.45",
3
+ "version": "2.1.0-alpha.47",
4
4
  "displayName": "File manager",
5
5
  "displayName.ru-RU": "Менеджер файлов",
6
6
  "displayName.zh-CN": "文件管理器",
@@ -60,5 +60,5 @@
60
60
  "Collections",
61
61
  "Collection fields"
62
62
  ],
63
- "gitHead": "e9e24987e12d0ad10a5db8815b1e1b7b447f1938"
63
+ "gitHead": "66196b57f9043ea0ac6ebdafbc732bfb98af1396"
64
64
  }