@sasjs/api 0.2.0 → 0.2.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/build/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.2.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.2.1 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
2
2
  "use strict";
3
3
  var SASjsApi = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -203,6 +203,8 @@ var SASjsApi = (() => {
203
203
  409,
204
204
  stat.directory ? "Folder with new name already exists." : "File with new name already exists."
205
205
  );
206
+ if (!stat.directory && hasBlockedExtension(to))
207
+ throw new ApiError(400, "Invalid file extension");
206
208
  await store.move(from, to);
207
209
  return { status: "success" };
208
210
  }
@@ -2398,6 +2400,26 @@ var SASjsApi = (() => {
2398
2400
  request: { body: { oldPath: "/data/nope.csv", newPath: "/data/x.csv" } },
2399
2401
  expect: { status: 404, error: "No file/folder found for provided path." }
2400
2402
  },
2403
+ {
2404
+ id: "rename-file-to-blocked-extension",
2405
+ route: "POST /SASjsApi/drive/rename",
2406
+ description: "refuses a rename that would produce a name the file routes refuse",
2407
+ seed: { "/data/report.csv": "a,b" },
2408
+ request: {
2409
+ body: { oldPath: "/data/report.csv", newPath: "/data/report.sh" }
2410
+ },
2411
+ expect: { status: 400, error: "Invalid file extension" }
2412
+ },
2413
+ {
2414
+ id: "rename-folder-to-dotted-name",
2415
+ route: "POST /SASjsApi/drive/rename",
2416
+ description: "renames a folder whose new name carries a blocked suffix",
2417
+ seed: { "/data/reports/a.csv": "a" },
2418
+ request: {
2419
+ body: { oldPath: "/data/reports", newPath: "/data/reports.sh" }
2420
+ },
2421
+ expect: { status: 200, body: { status: "success" } }
2422
+ },
2401
2423
  {
2402
2424
  id: "rename-folder-into-itself",
2403
2425
  route: "POST /SASjsApi/drive/rename",
@@ -238,6 +238,26 @@ exports.DRIVE_CORPUS = [
238
238
  request: { body: { oldPath: '/data/nope.csv', newPath: '/data/x.csv' } },
239
239
  expect: { status: 404, error: 'No file/folder found for provided path.' }
240
240
  },
241
+ {
242
+ id: 'rename-file-to-blocked-extension',
243
+ route: 'POST /SASjsApi/drive/rename',
244
+ description: 'refuses a rename that would produce a name the file routes refuse',
245
+ seed: { '/data/report.csv': 'a,b' },
246
+ request: {
247
+ body: { oldPath: '/data/report.csv', newPath: '/data/report.sh' }
248
+ },
249
+ expect: { status: 400, error: 'Invalid file extension' }
250
+ },
251
+ {
252
+ id: 'rename-folder-to-dotted-name',
253
+ route: 'POST /SASjsApi/drive/rename',
254
+ description: 'renames a folder whose new name carries a blocked suffix',
255
+ seed: { '/data/reports/a.csv': 'a' },
256
+ request: {
257
+ body: { oldPath: '/data/reports', newPath: '/data/reports.sh' }
258
+ },
259
+ expect: { status: 200, body: { status: 'success' } }
260
+ },
241
261
  {
242
262
  id: 'rename-folder-into-itself',
243
263
  route: 'POST /SASjsApi/drive/rename',
@@ -124,6 +124,14 @@ const createDriveService = (store) => {
124
124
  throw new types_js_1.ApiError(409, stat.directory
125
125
  ? 'Folder with new name already exists.'
126
126
  : 'File with new name already exists.');
127
+ // the extension rule is the drive's file-path rule, so a rename may not
128
+ // be the way around it: without this a file could be renamed into a name
129
+ // that GET, PATCH and DELETE then refuse, leaving an entry the API that
130
+ // created it can no longer read or remove. A folder is not subject to it
131
+ // (nothing puts a folder through the file-path schema), so only a file
132
+ // move is checked.
133
+ if (!stat.directory && (0, paths_js_1.hasBlockedExtension)(to))
134
+ throw new types_js_1.ApiError(400, 'Invalid file extension');
127
135
  await store.move(from, to);
128
136
  return { status: 'success' };
129
137
  }
@@ -1 +1 @@
1
- {"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAC1C,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9B,yDAAyD;QACzD,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAWD,eAAO,MAAM,YAAY,EAAE,UAAU,EAyRpC,CAAA"}
1
+ {"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAC1C,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9B,yDAAyD;QACzD,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAWD,eAAO,MAAM,YAAY,EAAE,UAAU,EA8SpC,CAAA"}
@@ -235,6 +235,26 @@ export const DRIVE_CORPUS = [
235
235
  request: { body: { oldPath: '/data/nope.csv', newPath: '/data/x.csv' } },
236
236
  expect: { status: 404, error: 'No file/folder found for provided path.' }
237
237
  },
238
+ {
239
+ id: 'rename-file-to-blocked-extension',
240
+ route: 'POST /SASjsApi/drive/rename',
241
+ description: 'refuses a rename that would produce a name the file routes refuse',
242
+ seed: { '/data/report.csv': 'a,b' },
243
+ request: {
244
+ body: { oldPath: '/data/report.csv', newPath: '/data/report.sh' }
245
+ },
246
+ expect: { status: 400, error: 'Invalid file extension' }
247
+ },
248
+ {
249
+ id: 'rename-folder-to-dotted-name',
250
+ route: 'POST /SASjsApi/drive/rename',
251
+ description: 'renames a folder whose new name carries a blocked suffix',
252
+ seed: { '/data/reports/a.csv': 'a' },
253
+ request: {
254
+ body: { oldPath: '/data/reports', newPath: '/data/reports.sh' }
255
+ },
256
+ expect: { status: 200, body: { status: 'success' } }
257
+ },
238
258
  {
239
259
  id: 'rename-folder-into-itself',
240
260
  route: 'POST /SASjsApi/drive/rename',
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/drive/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAUvD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9D,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CAC9C;AAqBD,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KAAG,YA4ItD,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/drive/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAWvD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9D,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CAC9C;AAqBD,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KAAG,YAoJtD,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { ApiError } from '../types.js';
2
- import { basename, checkFilePath, dirname, isRoot, normaliseDrivePath } from './paths.js';
2
+ import { basename, checkFilePath, dirname, hasBlockedExtension, isRoot, normaliseDrivePath } from './paths.js';
3
3
  /** A path a file operation may act on, or the refusal the operation answers. */
4
4
  const filePath = (input, outside) => {
5
5
  const checked = checkFilePath(input);
@@ -121,6 +121,14 @@ export const createDriveService = (store) => {
121
121
  throw new ApiError(409, stat.directory
122
122
  ? 'Folder with new name already exists.'
123
123
  : 'File with new name already exists.');
124
+ // the extension rule is the drive's file-path rule, so a rename may not
125
+ // be the way around it: without this a file could be renamed into a name
126
+ // that GET, PATCH and DELETE then refuse, leaving an entry the API that
127
+ // created it can no longer read or remove. A folder is not subject to it
128
+ // (nothing puts a folder through the file-path schema), so only a file
129
+ // move is checked.
130
+ if (!stat.directory && hasBlockedExtension(to))
131
+ throw new ApiError(400, 'Invalid file extension');
124
132
  await store.move(from, to);
125
133
  return { status: 'success' };
126
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/api",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "The SASjs Server API, described once - a generated route table, the services behind it, and the conformance corpus both hosts run",
5
5
  "license": "MIT",
6
6
  "author": "SASjs",
@@ -43,15 +43,21 @@
43
43
  "@semantic-release/github": "12.0.10",
44
44
  "@semantic-release/npm": "13.2.0",
45
45
  "@semantic-release/release-notes-generator": "14.1.1",
46
- "@types/jest": "29.5.14",
46
+ "@types/jest": "30.0.0",
47
47
  "@types/node": "22.20.5",
48
+ "deepmerge": "file:./vendor/deepmerge",
48
49
  "esbuild": "0.28.2",
49
- "jest": "29.7.0",
50
+ "jest": "30.5.2",
50
51
  "prettier": "3.9.10",
51
52
  "rimraf": "6.1.4",
52
53
  "semantic-release": "25.0.9",
53
54
  "ts-jest": "29.4.15",
54
55
  "typescript": "5.9.3",
55
56
  "yaml": "2.9.1"
57
+ },
58
+ "overrides": {
59
+ "deepmerge": "$deepmerge",
60
+ "glob": "14.0.3",
61
+ "minimatch": "11.0.4"
56
62
  }
57
63
  }