cloudcmd 19.20.1 → 19.20.3

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/ChangeLog CHANGED
@@ -1,3 +1,13 @@
1
+ 2026.08.26, v19.20.3
2
+
3
+ feature:
4
+ - 12cb4aca cloudcmd: @cloudcmd/fileop v9.0.8 (#472)
5
+
6
+ 2026.08.25, v19.20.2
7
+
8
+ fix:
9
+ - b9bdc9ed path traversal (#474)
10
+
1
11
  2026.08.23, v19.20.1
2
12
 
3
13
  feature:
package/HELP.md CHANGED
@@ -1,4 +1,4 @@
1
- # Cloud Commander v19.20.1
1
+ # Cloud Commander v19.20.3
2
2
 
3
3
  ### [Main][MainURL] [Blog][BlogURL] [Demo][DemoURL] [Deploy](#deploy)
4
4
 
@@ -1122,6 +1122,8 @@ There are a lot of ways to be involved in `Cloud Commander` development:
1122
1122
 
1123
1123
  ## Version history
1124
1124
 
1125
+ - *2026.08.26*, **[v19.20.3](//github.com/coderaiser/cloudcmd/releases/tag/v19.20.3)**
1126
+ - *2026.08.25*, **[v19.20.2](//github.com/coderaiser/cloudcmd/releases/tag/v19.20.2)**
1125
1127
  - *2026.08.23*, **[v19.20.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.20.1)**
1126
1128
  - *2026.08.06*, **[v19.20.0](//github.com/coderaiser/cloudcmd/releases/tag/v19.20.0)**
1127
1129
  - *2026.06.15*, **[v19.19.1](//github.com/coderaiser/cloudcmd/releases/tag/v19.19.1)**
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Cloud Commander v19.20.1 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
1
+ # Cloud Commander v19.20.3 [![Build Status][BuildStatusIMGURL]][BuildStatusURL] [![Codacy][CodacyIMG]][CodacyURL] [![Gitter][GitterIMGURL]][GitterURL] [![Deploy on InstaPods][DeployInstaPodsIMG]][DeployInstaPodsURL]
2
2
 
3
3
  ### [Main][MainURL] [Blog][BlogURL] [Support][SupportURL] [Demo][DemoURL]
4
4
 
package/dist/sw.js CHANGED
@@ -302,7 +302,7 @@ const respondWith = currify((f, e) => {
302
302
  e.respondWith(f(e));
303
303
  });
304
304
  const getPathName = url => new URL(url).pathname;
305
- const date = "Sun Aug 23 2026 10:54:37 GMT+0300 (Eastern European Summer Time)";
305
+ const date = "Wed Aug 26 2026 11:59:14 GMT+0300 (Eastern European Summer Time)";
306
306
  const NAME = `cloudcmd: ${date}`;
307
307
  const createRequest = a => new Request(a, {
308
308
  credentials: 'same-origin'
@@ -32,7 +32,7 @@ eval("{\n\nconst identityLoginURL = 'static.olark.com/jsclient/loader.js';\n\nif
32
32
  \************************/
33
33
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
34
34
  "use strict";
35
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
35
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
36
36
 
37
37
  },
38
38
  "./css/main.css"
@@ -41,7 +41,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
41
41
  \**********************/
42
42
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
43
43
  "use strict";
44
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
44
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
45
45
 
46
46
  },
47
47
  "./css/terminal.css"
@@ -50,7 +50,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
50
50
  \**************************/
51
51
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
52
52
  "use strict";
53
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
53
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
54
54
 
55
55
  },
56
56
  "./css/user-menu.css"
@@ -59,7 +59,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
59
59
  \***************************/
60
60
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
61
61
  "use strict";
62
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
62
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
63
63
 
64
64
  },
65
65
  "./css/view.css"
@@ -68,7 +68,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
68
68
  \**********************/
69
69
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
70
70
  "use strict";
71
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
71
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
72
72
 
73
73
  },
74
74
  "./node_modules/@cloudcmd/modal/css/modal.css"
@@ -77,7 +77,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
77
77
  \****************************************************/
78
78
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
79
79
  "use strict";
80
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
80
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
81
81
 
82
82
  },
83
83
  "./node_modules/smalltalk/css/smalltalk.css"
@@ -86,7 +86,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
86
86
  \**************************************************/
87
87
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
88
88
  "use strict";
89
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
89
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
90
90
 
91
91
  },
92
92
  "./node_modules/supermenu/css/supermenu.css"
@@ -95,7 +95,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-
95
95
  \**************************************************/
96
96
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
97
97
  "use strict";
98
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/terminal.css\n}");
98
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
99
99
 
100
100
  },
101
101
  "./node_modules/apart/lib/apart.js"
@@ -6,7 +6,7 @@ var __webpack_modules__ = ({
6
6
  !*** ./css/columns/name-size-date-time.css ***!
7
7
  \*********************************************/
8
8
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/columns/name-size-date-time.css\n}");
9
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/help.css\n}");
10
10
 
11
11
  },
12
12
 
@@ -6,7 +6,7 @@ var __webpack_modules__ = ({
6
6
  !*** ./css/columns/name-size-date.css ***!
7
7
  \****************************************/
8
8
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/columns/name-size-date.css\n}");
9
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/columns/name-size.css\n}");
10
10
 
11
11
  },
12
12
 
package/dist-dev/sw.js CHANGED
@@ -23,7 +23,7 @@ eval("{// shim for using process in browser\nvar process = module.exports = {};\
23
23
  \*************************/
24
24
  (__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
25
25
  "use strict";
26
- eval("{__webpack_require__.r(__webpack_exports__);\n/* import */ var node_process__rspack_import_0 = __webpack_require__(/*! node:process */ \"./node_modules/process/browser.js\");\n/* import */ var try_to_catch__rspack_import_1 = __webpack_require__(/*! try-to-catch */ \"./node_modules/try-to-catch/lib/try-to-catch.js\");\n/* import */ var currify__rspack_import_2 = __webpack_require__(/*! currify */ \"./node_modules/currify/lib/currify.js\");\n\n\n\nconst isDev = \"development\" === 'development';\nconst isGet = a => a.method === 'GET';\nconst isBasic = a => a.type === 'basic';\nconst wait = currify__rspack_import_2((f, e) => e.waitUntil(f()));\nconst respondWith = currify__rspack_import_2((f, e) => {\n const {\n request\n } = e;\n const {\n url\n } = request;\n const pathname = getPathName(url);\n if (url.endsWith('/') || /\\^\\/fs/.test(pathname)) return;\n if (!isGet(request)) return;\n if (!isBasic(request)) return;\n if (pathname.startsWith('/api')) return;\n if (/^socket.io/.test(pathname)) return;\n e.respondWith(f(e));\n});\nconst getPathName = url => new URL(url).pathname;\nconst date = \"Sun Aug 23 2026 10:54:38 GMT+0300 (Eastern European Summer Time)\";\nconst NAME = `cloudcmd: ${date}`;\nconst createRequest = a => new Request(a, {\n credentials: 'same-origin'\n});\nconst getRequest = (a, request) => {\n if (a !== '/') return request;\n return createRequest('/');\n};\nglobalThis.addEventListener('install', wait(onInstall));\nglobalThis.addEventListener('fetch', respondWith(onFetch));\nglobalThis.addEventListener('activate', wait(onActivate));\nasync function onActivate() {\n console.info(`cloudcmd: sw: activate: ${NAME}`);\n await globalThis.clients.claim();\n const keys = await caches.keys();\n const deleteCache = caches.delete.bind(caches);\n await Promise.all(keys.map(deleteCache));\n}\nasync function onInstall() {\n console.info(`cloudcmd: sw: install: ${NAME}`);\n await globalThis.skipWaiting();\n}\nasync function onFetch(event) {\n const {\n request\n } = event;\n const {\n url\n } = request;\n const pathname = getPathName(url);\n const newRequest = getRequest(pathname, event.request);\n const cache = await caches.open(NAME);\n const response = await cache.match(request);\n if (!isDev && response) return response;\n const [e, resp] = await (0,try_to_catch__rspack_import_1.tryToCatch)(fetch, newRequest, {\n credentials: 'same-origin'\n });\n if (e) return new Response(e.message);\n await addToCache(request, resp.clone());\n return resp;\n}\nasync function addToCache(request, response) {\n const cache = await caches.open(NAME);\n return cache.put(request, response);\n}\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/client/sw/sw.js\n}");
26
+ eval("{__webpack_require__.r(__webpack_exports__);\n/* import */ var node_process__rspack_import_0 = __webpack_require__(/*! node:process */ \"./node_modules/process/browser.js\");\n/* import */ var try_to_catch__rspack_import_1 = __webpack_require__(/*! try-to-catch */ \"./node_modules/try-to-catch/lib/try-to-catch.js\");\n/* import */ var currify__rspack_import_2 = __webpack_require__(/*! currify */ \"./node_modules/currify/lib/currify.js\");\n\n\n\nconst isDev = \"development\" === 'development';\nconst isGet = a => a.method === 'GET';\nconst isBasic = a => a.type === 'basic';\nconst wait = currify__rspack_import_2((f, e) => e.waitUntil(f()));\nconst respondWith = currify__rspack_import_2((f, e) => {\n const {\n request\n } = e;\n const {\n url\n } = request;\n const pathname = getPathName(url);\n if (url.endsWith('/') || /\\^\\/fs/.test(pathname)) return;\n if (!isGet(request)) return;\n if (!isBasic(request)) return;\n if (pathname.startsWith('/api')) return;\n if (/^socket.io/.test(pathname)) return;\n e.respondWith(f(e));\n});\nconst getPathName = url => new URL(url).pathname;\nconst date = \"Wed Aug 26 2026 11:59:14 GMT+0300 (Eastern European Summer Time)\";\nconst NAME = `cloudcmd: ${date}`;\nconst createRequest = a => new Request(a, {\n credentials: 'same-origin'\n});\nconst getRequest = (a, request) => {\n if (a !== '/') return request;\n return createRequest('/');\n};\nglobalThis.addEventListener('install', wait(onInstall));\nglobalThis.addEventListener('fetch', respondWith(onFetch));\nglobalThis.addEventListener('activate', wait(onActivate));\nasync function onActivate() {\n console.info(`cloudcmd: sw: activate: ${NAME}`);\n await globalThis.clients.claim();\n const keys = await caches.keys();\n const deleteCache = caches.delete.bind(caches);\n await Promise.all(keys.map(deleteCache));\n}\nasync function onInstall() {\n console.info(`cloudcmd: sw: install: ${NAME}`);\n await globalThis.skipWaiting();\n}\nasync function onFetch(event) {\n const {\n request\n } = event;\n const {\n url\n } = request;\n const pathname = getPathName(url);\n const newRequest = getRequest(pathname, event.request);\n const cache = await caches.open(NAME);\n const response = await cache.match(request);\n if (!isDev && response) return response;\n const [e, resp] = await (0,try_to_catch__rspack_import_1.tryToCatch)(fetch, newRequest, {\n credentials: 'same-origin'\n });\n if (e) return new Response(e.message);\n await addToCache(request, resp.clone());\n return resp;\n}\nasync function addToCache(request, response) {\n const cache = await caches.open(NAME);\n return cache.put(request, response);\n}\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/client/sw/sw.js\n}");
27
27
 
28
28
  },
29
29
  "./node_modules/try-to-catch/lib/try-to-catch.js"
@@ -6,7 +6,7 @@ var __webpack_modules__ = ({
6
6
  !*** ./css/user-menu.css ***!
7
7
  \***************************/
8
8
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/user-menu.css\n}");
9
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
10
10
 
11
11
  },
12
12
 
package/dist-dev/view.js CHANGED
@@ -6,7 +6,7 @@ var __webpack_modules__ = ({
6
6
  !*** ./css/view.css ***!
7
7
  \**********************/
8
8
  (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
9
- eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/view.css\n}");
9
+ eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by css-extract-rspack-plugin\n\n\n//# sourceURL=file://cloudcmd//Users/coderaiser/cloudcmd/css/themes/light.css\n}");
10
10
 
11
11
  },
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudcmd",
3
- "version": "19.20.1",
3
+ "version": "19.20.3",
4
4
  "type": "module",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "File manager for the web with console and editor",
@@ -86,7 +86,7 @@
86
86
  "subdomain": "cloudcmd",
87
87
  "dependencies": {
88
88
  "@cloudcmd/dropbox": "^5.0.1",
89
- "@cloudcmd/fileop": "^9.0.7",
89
+ "@cloudcmd/fileop": "^9.0.8",
90
90
  "@cloudcmd/move-files": "^9.0.0",
91
91
  "@cloudcmd/read-files-sync": "^2.0.0",
92
92
  "@putout/cli-validate-args": "^2.0.0",
@@ -32,9 +32,9 @@ function parseName(query, name, rootDir) {
32
32
  const shortName = name.replace('/markdown', '');
33
33
 
34
34
  if (query === 'relative')
35
- return DIR_ROOT + shortName;
35
+ return root.resolve(shortName, DIR_ROOT);
36
36
 
37
- return root(shortName, rootDir);
37
+ return root.resolve(shortName, rootDir);
38
38
  }
39
39
 
40
40
  async function onGET(request, name, root) {
@@ -11,6 +11,7 @@ import json from 'jonny';
11
11
  import * as ponse from 'ponse';
12
12
  import {copymitter} from 'copymitter';
13
13
  import {moveFiles as _moveFiles} from '@cloudcmd/move-files';
14
+ import {tryCatch} from 'try-catch';
14
15
  import * as CloudFunc from '#common/cloudfunc';
15
16
  import root from '../root.js';
16
17
  import markdown from '../markdown/index.js';
@@ -216,8 +217,15 @@ function onPUT({name, fs, moveFiles, config, body}, callback) {
216
217
  const msg = getMoveMsg(names);
217
218
  const fn = swap(callback, msg);
218
219
 
219
- const fromRooted = root(from, rootDir);
220
- const toRooted = root(to, rootDir);
220
+ const [errorFrom, fromRooted] = tryCatch(root.resolve, from, rootDir);
221
+
222
+ if (errorFrom)
223
+ return callback(errorFrom);
224
+
225
+ const [errorTo, toRooted] = tryCatch(root.resolve, to, rootDir);
226
+
227
+ if (errorTo)
228
+ return callback(errorTo);
221
229
 
222
230
  return moveFiles(fromRooted, toRooted, names)
223
231
  .on('error', fn)
@@ -227,21 +235,32 @@ function onPUT({name, fs, moveFiles, config, body}, callback) {
227
235
  case 'rename':
228
236
  return rename(rootDir, files.from, files.to, fs, callback);
229
237
 
230
- case 'copy':
238
+ case 'copy': {
231
239
  if (!files.from || !files.names || !files.to)
232
240
  return callback(body);
233
241
 
234
242
  if (isRootAll(rootDir, [files.to, files.from]))
235
243
  return callback(getWin32RootMsg());
236
244
 
237
- files.from = root(files.from, rootDir);
238
- files.to = root(files.to, rootDir);
245
+ const [errorFrom, resolvedFrom] = tryCatch(root.resolve, files.from, rootDir);
246
+
247
+ if (errorFrom)
248
+ return callback(errorFrom);
249
+
250
+ const [errorTo, resolvedTo] = tryCatch(root.resolve, files.to, rootDir);
251
+
252
+ if (errorTo)
253
+ return callback(errorTo);
254
+
255
+ files.from = resolvedFrom;
256
+ files.to = resolvedTo;
239
257
 
240
258
  copy(files.from, files.to, files.names, (error) => {
241
259
  const msg = formatMsg('copy', files.names);
242
260
  callback(error, msg);
243
261
  });
244
262
  break;
263
+ }
245
264
 
246
265
  case 'pack':
247
266
  if (!files.from)
@@ -274,8 +293,15 @@ function rename(rootDir, from, to, fs, callback) {
274
293
  const msg = getRenameMsg(from, to);
275
294
  const fn = swap(callback, msg);
276
295
 
277
- const fromRooted = root(from, rootDir);
278
- const toRooted = root(to, rootDir);
296
+ const [errorFrom, fromRooted] = tryCatch(root.resolve, from, rootDir);
297
+
298
+ if (errorFrom)
299
+ return callback(errorFrom);
300
+
301
+ const [errorTo, toRooted] = tryCatch(root.resolve, to, rootDir);
302
+
303
+ if (errorTo)
304
+ return callback(errorTo);
279
305
 
280
306
  return fs.rename(fromRooted, toRooted, fn);
281
307
  }
@@ -286,8 +312,18 @@ function pack(from, to, names, config, fn) {
286
312
  const rootDir = config('root');
287
313
  const packer = config('packer');
288
314
 
289
- from = root(from, rootDir);
290
- to = root(to, rootDir);
315
+ const [errorFrom, resolvedFrom] = tryCatch(root.resolve, from, rootDir);
316
+
317
+ if (errorFrom)
318
+ return fn(errorFrom);
319
+
320
+ const [errorTo, resolvedTo] = tryCatch(root.resolve, to, rootDir);
321
+
322
+ if (errorTo)
323
+ return fn(errorTo);
324
+
325
+ from = resolvedFrom;
326
+ to = resolvedTo;
291
327
 
292
328
  if (!names) {
293
329
  names = [
@@ -305,12 +341,23 @@ export const _pack = pack;
305
341
  function extract(from, to, config, fn) {
306
342
  const rootDir = config('root');
307
343
 
308
- from = root(from, rootDir);
344
+ const [errorFrom, resolvedFrom] = tryCatch(root.resolve, from, rootDir);
309
345
 
310
- if (to)
311
- to = root(to, rootDir);
312
- else
346
+ if (errorFrom)
347
+ return fn(errorFrom);
348
+
349
+ from = resolvedFrom;
350
+
351
+ if (to) {
352
+ const [errorTo, resolvedTo] = tryCatch(root.resolve, to, rootDir);
353
+
354
+ if (errorTo)
355
+ return fn(errorTo);
356
+
357
+ to = resolvedTo;
358
+ } else {
313
359
  to = from.replace(/\.tar\.gz$/, '');
360
+ }
314
361
 
315
362
  operation('extract', config('packer'), from, to, fn);
316
363
  }
package/server/root.js CHANGED
@@ -1,5 +1,20 @@
1
+ import path from 'node:path';
1
2
  import mellow from 'mellow';
2
3
 
3
- export default (dir, root, {webToWin = mellow.webToWin} = {}) => {
4
+ const rootFn = (dir, root, {webToWin = mellow.webToWin} = {}) => {
4
5
  return webToWin(dir, root || '/');
5
6
  };
7
+
8
+ rootFn.resolve = (dir, rootDir) => {
9
+ const normalizedRoot = path.normalize(rootDir);
10
+ const resolved = rootFn(dir, normalizedRoot);
11
+
12
+ const sep = normalizedRoot.endsWith(path.sep) ? '' : path.sep;
13
+
14
+ if (resolved !== normalizedRoot && !resolved.startsWith(normalizedRoot + sep))
15
+ throw Error(`Path ${resolved} beyond root ${normalizedRoot}!`);
16
+
17
+ return resolved;
18
+ };
19
+
20
+ export default rootFn;