@sasjs/api 0.6.0 → 0.6.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.6.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.6.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;
@@ -111,6 +111,7 @@ var SASjsApi = (() => {
111
111
  status: "success",
112
112
  message: "Files deployed successfully to @sasjs/server."
113
113
  };
114
+ var MAX_DEPTH = 32;
114
115
  var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
115
116
  var base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, "");
116
117
  var base64Length = (value) => Math.floor(base64Body(value).length * 3 / 4);
@@ -145,7 +146,9 @@ var SASjsApi = (() => {
145
146
  };
146
147
  var memberName = (member) => member.type === "service" ? `${member.name}.sas` : member.name;
147
148
  var memberBytes = (member) => member.type === "file" ? base64Length(member.code) : utf8Length(member.code);
148
- var describeSize = (bytes2) => bytes2 < 1024 * 1024 ? `${bytes2} bytes` : `${Math.round(bytes2 / (1024 * 1024) * 10) / 10} MB`;
149
+ var MEGABYTE = 1024 * 1024;
150
+ var describeSize = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.ceil(bytes2 / MEGABYTE * 10) / 10} MB`;
151
+ var describeLimit = (bytes2) => bytes2 < MEGABYTE ? `${bytes2} bytes` : `${Math.round(bytes2 / MEGABYTE * 10) / 10} MB`;
149
152
  var emptyWalk = () => ({
150
153
  entries: 0,
151
154
  totalBytes: 0,
@@ -153,7 +156,7 @@ var SASjsApi = (() => {
153
156
  });
154
157
  var isSafeSegment = (name) => name !== "" && name !== "." && name !== ".." && !/[/\\]/.test(name) && // a Windows drive or a NUL byte is never a segment either
155
158
  !/[\0]/.test(name);
156
- var walk = (members, parentPath, acc) => {
159
+ var walk = (members, parentPath, acc, depth = 1) => {
157
160
  for (const member of members) {
158
161
  const name = memberName(member);
159
162
  const memberPath = parentPath ? `${parentPath}/${name}` : name;
@@ -165,7 +168,11 @@ var SASjsApi = (() => {
165
168
  };
166
169
  acc.entries++;
167
170
  if (member.type === "folder") {
168
- walk(member.members, memberPath, acc);
171
+ if (depth + 1 > MAX_DEPTH) {
172
+ if (!acc.tooDeep) acc.tooDeep = depth + 1;
173
+ continue;
174
+ }
175
+ walk(member.members, memberPath, acc, depth + 1);
169
176
  continue;
170
177
  }
171
178
  const bytes2 = memberBytes(member);
@@ -186,6 +193,12 @@ var SASjsApi = (() => {
186
193
  const measured = emptyWalk();
187
194
  walk(members, "", measured);
188
195
  if (measured.nameRefusal) return measured.nameRefusal;
196
+ if (measured.tooDeep)
197
+ return {
198
+ code: 413,
199
+ status: "failure",
200
+ message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${MAX_DEPTH}).`
201
+ };
189
202
  if (measured.entries > limits.entries)
190
203
  return {
191
204
  code: 413,
@@ -196,13 +209,13 @@ var SASjsApi = (() => {
196
209
  return {
197
210
  code: 413,
198
211
  status: "failure",
199
- message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeSize(limits.totalBytes)}).`
212
+ message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
200
213
  };
201
214
  if (measured.largest.bytes > limits.fileBytes)
202
215
  return {
203
216
  code: 413,
204
217
  status: "failure",
205
- message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeSize(limits.fileBytes)} per file).`
218
+ message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
206
219
  };
207
220
  return null;
208
221
  };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deployTree = exports.isDeployTree = exports.checkDeployTree = exports.measureDeployTree = exports.decodeBase64 = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = void 0;
3
+ exports.deployTree = exports.isDeployTree = exports.checkDeployTree = exports.measureDeployTree = exports.decodeBase64 = exports.MAX_DEPTH = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = void 0;
4
4
  const types_js_1 = require("../types.js");
5
5
  const paths_js_1 = require("./paths.js");
6
6
  exports.DEPLOY_LIMITS = {
@@ -36,6 +36,16 @@ exports.DEPLOY_SUCCESS = {
36
36
  status: 'success',
37
37
  message: 'Files deployed successfully to @sasjs/server.'
38
38
  };
39
+ /**
40
+ * How deep a tree may nest.
41
+ *
42
+ * A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
43
+ * levels is generous, and the number matters because the walk recurses once per
44
+ * level - without a ceiling, a tree nested deeply enough exhausts the stack and
45
+ * throws a `RangeError`, which is neither the `400` nor the `413` this route
46
+ * documents, on input the cap exists to bound.
47
+ */
48
+ exports.MAX_DEPTH = 32;
39
49
  const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
40
50
  /** A base64 body without its padding, which is what the arithmetic needs. */
41
51
  const base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, '');
@@ -82,14 +92,28 @@ const utf8Length = (value) => {
82
92
  const memberName = (member) => member.type === 'service' ? `${member.name}.sas` : member.name;
83
93
  /** The bytes a member writes. */
84
94
  const memberBytes = (member) => member.type === 'file' ? base64Length(member.code) : utf8Length(member.code);
95
+ const MEGABYTE = 1024 * 1024;
85
96
  /**
86
- * Sizes as the messages state them: one decimal in binary megabytes, and bytes
87
- * below a megabyte - a limit small enough to be measured in bytes should not
88
- * read as "0 MB", which is what a stricter host would otherwise report.
97
+ * A MEASURED size, rounded UP to one decimal in binary megabytes.
98
+ *
99
+ * Up, not to nearest: a tree one byte over a 100 MB cap must not read as "100 MB
100
+ * (maximum 100 MB)", which is a refusal that states no overage at all. Rounding
101
+ * up is the direction that never understates what is being refused.
102
+ *
103
+ * Bytes below a megabyte are printed as bytes, because a limit small enough to
104
+ * be measured in bytes should not read as "0 MB" - which is what a host passing
105
+ * a tighter set would otherwise report.
89
106
  */
90
- const describeSize = (bytes) => bytes < 1024 * 1024
107
+ const describeSize = (bytes) => bytes < MEGABYTE
91
108
  ? `${bytes} bytes`
92
- : `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MB`;
109
+ : `${Math.ceil((bytes / MEGABYTE) * 10) / 10} MB`;
110
+ /**
111
+ * A LIMIT, printed as it is stated: `DEPLOY_LIMITS` holds exact megabytes, so
112
+ * rounding cannot make it disagree with itself.
113
+ */
114
+ const describeLimit = (bytes) => bytes < MEGABYTE
115
+ ? `${bytes} bytes`
116
+ : `${Math.round((bytes / MEGABYTE) * 10) / 10} MB`;
93
117
  /** A walk's starting point. */
94
118
  const emptyWalk = () => ({
95
119
  entries: 0,
@@ -113,8 +137,12 @@ const isSafeSegment = (name) => name !== '' &&
113
137
  * a caller that only wants the numbers gets the size of the whole tree, and the
114
138
  * validator decides whether the name matters. Returning early would report a
115
139
  * truncated tree as if it were the real one.
140
+ *
141
+ * The one thing that stops the walk is depth: past `MAX_DEPTH` it does not
142
+ * descend, because descending is the work that would exhaust the stack. The
143
+ * level it reached is recorded so the refusal can state it.
116
144
  */
117
- const walk = (members, parentPath, acc) => {
145
+ const walk = (members, parentPath, acc, depth = 1) => {
118
146
  for (const member of members) {
119
147
  const name = memberName(member);
120
148
  const memberPath = parentPath ? `${parentPath}/${name}` : name;
@@ -126,7 +154,12 @@ const walk = (members, parentPath, acc) => {
126
154
  };
127
155
  acc.entries++;
128
156
  if (member.type === 'folder') {
129
- walk(member.members, memberPath, acc);
157
+ if (depth + 1 > exports.MAX_DEPTH) {
158
+ if (!acc.tooDeep)
159
+ acc.tooDeep = depth + 1;
160
+ continue;
161
+ }
162
+ walk(member.members, memberPath, acc, depth + 1);
130
163
  continue;
131
164
  }
132
165
  const bytes = memberBytes(member);
@@ -166,6 +199,12 @@ const checkDeployTree = (members, limits = exports.DEPLOY_LIMITS) => {
166
199
  walk(members, '', measured);
167
200
  if (measured.nameRefusal)
168
201
  return measured.nameRefusal;
202
+ if (measured.tooDeep)
203
+ return {
204
+ code: 413,
205
+ status: 'failure',
206
+ message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${exports.MAX_DEPTH}).`
207
+ };
169
208
  if (measured.entries > limits.entries)
170
209
  return {
171
210
  code: 413,
@@ -176,13 +215,13 @@ const checkDeployTree = (members, limits = exports.DEPLOY_LIMITS) => {
176
215
  return {
177
216
  code: 413,
178
217
  status: 'failure',
179
- message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeSize(limits.totalBytes)}).`
218
+ message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
180
219
  };
181
220
  if (measured.largest.bytes > limits.fileBytes)
182
221
  return {
183
222
  code: 413,
184
223
  status: 'failure',
185
- message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeSize(limits.fileBytes)} per file).`
224
+ message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
186
225
  };
187
226
  return null;
188
227
  };
@@ -99,6 +99,16 @@ export interface DeployMeasurements {
99
99
  bytes: number;
100
100
  };
101
101
  }
102
+ /**
103
+ * How deep a tree may nest.
104
+ *
105
+ * A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
106
+ * levels is generous, and the number matters because the walk recurses once per
107
+ * level - without a ceiling, a tree nested deeply enough exhausts the stack and
108
+ * throws a `RangeError`, which is neither the `400` nor the `413` this route
109
+ * documents, on input the cap exists to bound.
110
+ */
111
+ export declare const MAX_DEPTH = 32;
102
112
  /** The bytes a base64 body decodes to, for a store that writes bytes. */
103
113
  export declare const decodeBase64: (value: string) => Uint8Array;
104
114
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/drive/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAI7C,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,YAAY,GACtB,kBAAkB,GAAG,mBAAmB,GAAG,gBAAgB,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,EAAE,YAI3B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAoB,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAA;AAEV,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;IACf,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AAiBD,yEAAyE;AACzE,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,UAmB5C,CAAA;AA8FD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,YAAY,EAAE,KACtB,kBAUF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EAAE,EACvB,SAAQ,YAA4B,KACnC,aAAa,GAAG,IA6BlB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAMtD,CAAA;AAkBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,UAAU,EACjB,QAAQ,OAAO,EACf,MAAM,OAAO,EACb,SAAQ,YAA4B,KACnC,OAAO,CAAC,IAAI,CAiCd,CAAA"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/drive/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAI7C,sDAAsD;AACtD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,YAAY,GACtB,kBAAkB,GAAG,mBAAmB,GAAG,gBAAgB,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,EAAE,YAI3B,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,QAAoB,CAAA;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAA;AAEV,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;IACf,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AASD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,KAAK,CAAA;AAY3B,yEAAyE;AACzE,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,UAmB5C,CAAA;AA8HD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,YAAY,EAAE,KACtB,kBAUF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EAAE,EACvB,SAAQ,YAA4B,KACnC,aAAa,GAAG,IAoClB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,UAMtD,CAAA;AAkBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,UAAU,EACjB,QAAQ,OAAO,EACf,MAAM,OAAO,EACb,SAAQ,YAA4B,KACnC,OAAO,CAAC,IAAI,CAiCd,CAAA"}
@@ -33,6 +33,16 @@ export const DEPLOY_SUCCESS = {
33
33
  status: 'success',
34
34
  message: 'Files deployed successfully to @sasjs/server.'
35
35
  };
36
+ /**
37
+ * How deep a tree may nest.
38
+ *
39
+ * A deployment is an app: `services/`, `web/`, a folder per service. A few dozen
40
+ * levels is generous, and the number matters because the walk recurses once per
41
+ * level - without a ceiling, a tree nested deeply enough exhausts the stack and
42
+ * throws a `RangeError`, which is neither the `400` nor the `413` this route
43
+ * documents, on input the cap exists to bound.
44
+ */
45
+ export const MAX_DEPTH = 32;
36
46
  const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
37
47
  /** A base64 body without its padding, which is what the arithmetic needs. */
38
48
  const base64Body = (value) => value.replace(/[^A-Za-z0-9+/]/g, '');
@@ -78,14 +88,28 @@ const utf8Length = (value) => {
78
88
  const memberName = (member) => member.type === 'service' ? `${member.name}.sas` : member.name;
79
89
  /** The bytes a member writes. */
80
90
  const memberBytes = (member) => member.type === 'file' ? base64Length(member.code) : utf8Length(member.code);
91
+ const MEGABYTE = 1024 * 1024;
81
92
  /**
82
- * Sizes as the messages state them: one decimal in binary megabytes, and bytes
83
- * below a megabyte - a limit small enough to be measured in bytes should not
84
- * read as "0 MB", which is what a stricter host would otherwise report.
93
+ * A MEASURED size, rounded UP to one decimal in binary megabytes.
94
+ *
95
+ * Up, not to nearest: a tree one byte over a 100 MB cap must not read as "100 MB
96
+ * (maximum 100 MB)", which is a refusal that states no overage at all. Rounding
97
+ * up is the direction that never understates what is being refused.
98
+ *
99
+ * Bytes below a megabyte are printed as bytes, because a limit small enough to
100
+ * be measured in bytes should not read as "0 MB" - which is what a host passing
101
+ * a tighter set would otherwise report.
85
102
  */
86
- const describeSize = (bytes) => bytes < 1024 * 1024
103
+ const describeSize = (bytes) => bytes < MEGABYTE
87
104
  ? `${bytes} bytes`
88
- : `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MB`;
105
+ : `${Math.ceil((bytes / MEGABYTE) * 10) / 10} MB`;
106
+ /**
107
+ * A LIMIT, printed as it is stated: `DEPLOY_LIMITS` holds exact megabytes, so
108
+ * rounding cannot make it disagree with itself.
109
+ */
110
+ const describeLimit = (bytes) => bytes < MEGABYTE
111
+ ? `${bytes} bytes`
112
+ : `${Math.round((bytes / MEGABYTE) * 10) / 10} MB`;
89
113
  /** A walk's starting point. */
90
114
  const emptyWalk = () => ({
91
115
  entries: 0,
@@ -109,8 +133,12 @@ const isSafeSegment = (name) => name !== '' &&
109
133
  * a caller that only wants the numbers gets the size of the whole tree, and the
110
134
  * validator decides whether the name matters. Returning early would report a
111
135
  * truncated tree as if it were the real one.
136
+ *
137
+ * The one thing that stops the walk is depth: past `MAX_DEPTH` it does not
138
+ * descend, because descending is the work that would exhaust the stack. The
139
+ * level it reached is recorded so the refusal can state it.
112
140
  */
113
- const walk = (members, parentPath, acc) => {
141
+ const walk = (members, parentPath, acc, depth = 1) => {
114
142
  for (const member of members) {
115
143
  const name = memberName(member);
116
144
  const memberPath = parentPath ? `${parentPath}/${name}` : name;
@@ -122,7 +150,12 @@ const walk = (members, parentPath, acc) => {
122
150
  };
123
151
  acc.entries++;
124
152
  if (member.type === 'folder') {
125
- walk(member.members, memberPath, acc);
153
+ if (depth + 1 > MAX_DEPTH) {
154
+ if (!acc.tooDeep)
155
+ acc.tooDeep = depth + 1;
156
+ continue;
157
+ }
158
+ walk(member.members, memberPath, acc, depth + 1);
126
159
  continue;
127
160
  }
128
161
  const bytes = memberBytes(member);
@@ -161,6 +194,12 @@ export const checkDeployTree = (members, limits = DEPLOY_LIMITS) => {
161
194
  walk(members, '', measured);
162
195
  if (measured.nameRefusal)
163
196
  return measured.nameRefusal;
197
+ if (measured.tooDeep)
198
+ return {
199
+ code: 413,
200
+ status: 'failure',
201
+ message: `The file tree is nested too deeply: ${measured.tooDeep} levels (maximum ${MAX_DEPTH}).`
202
+ };
164
203
  if (measured.entries > limits.entries)
165
204
  return {
166
205
  code: 413,
@@ -171,13 +210,13 @@ export const checkDeployTree = (members, limits = DEPLOY_LIMITS) => {
171
210
  return {
172
211
  code: 413,
173
212
  status: 'failure',
174
- message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeSize(limits.totalBytes)}).`
213
+ message: `The file tree exceeds the deploy limit: ${describeSize(measured.totalBytes)} (maximum ${describeLimit(limits.totalBytes)}).`
175
214
  };
176
215
  if (measured.largest.bytes > limits.fileBytes)
177
216
  return {
178
217
  code: 413,
179
218
  status: 'failure',
180
- message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeSize(limits.fileBytes)} per file).`
219
+ message: `A file in the tree exceeds the deploy limit: ${measured.largest.path} is ${describeSize(measured.largest.bytes)} (maximum ${describeLimit(limits.fileBytes)} per file).`
181
220
  };
182
221
  return null;
183
222
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/api",
3
- "version": "0.6.0",
3
+ "version": "0.6.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",