@zintrust/core 2.5.8 → 2.5.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "2.5.8",
3
+ "version": "2.5.9",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -1 +1 @@
1
- {"version":3,"file":"BodyParsingMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/BodyParsingMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAwV9D;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,UA4CnC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"BodyParsingMiddleware.d.ts","sourceRoot":"","sources":["../../../../src/http/middleware/BodyParsingMiddleware.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAkY9D;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,UA4CnC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -238,39 +238,79 @@ const reuseExistingRawBody = (req, res, contentType, existingBytes, existingText
238
238
  }
239
239
  return applyParsedRequestBody(req, res, rawResult, contentType);
240
240
  };
241
- const parseUrlEncodedBody = (text) => {
241
+ /**
242
+ * Attempt to parse a string as a JSON object.
243
+ * Returns the parsed object on success, or null if the string is not
244
+ * a valid JSON object literal (plain object `{...}`, not array or primitive).
245
+ */
246
+ const tryParseJsonObject = (text) => {
247
+ const trimmed = text.trim();
248
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
249
+ return null;
250
+ try {
251
+ const parsed = JSON.parse(trimmed);
252
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
253
+ return parsed;
254
+ }
255
+ }
256
+ catch {
257
+ // Not valid JSON
258
+ }
259
+ return null;
260
+ };
261
+ /**
262
+ * Parse URL-encoded form data into a key-value record.
263
+ * Multiple values for the same key are collected into an array.
264
+ */
265
+ const parseUrlEncodedParams = (text) => {
242
266
  const out = {};
243
267
  const params = new URLSearchParams(text);
244
268
  for (const [key, value] of params.entries()) {
245
269
  const existing = out[key];
246
270
  if (existing === undefined) {
247
271
  out[key] = value;
248
- continue;
249
272
  }
250
- if (Array.isArray(existing)) {
273
+ else if (Array.isArray(existing)) {
251
274
  existing.push(value);
252
- continue;
253
275
  }
254
- out[key] = [existing, value];
276
+ else {
277
+ out[key] = [existing, value];
278
+ }
279
+ }
280
+ return out;
281
+ };
282
+ /**
283
+ * Detect and recover from a degenerate case where a JSON payload was sent
284
+ * with a url-encoded Content-Type header. URLSearchParams treats the entire
285
+ * JSON string as a single key with an empty value, so we try parsing that key.
286
+ */
287
+ const tryRecoverDegenerateJson = (out) => {
288
+ const keys = Object.keys(out);
289
+ if (keys.length !== 1 || out[keys[0]] !== '')
290
+ return null;
291
+ const maybeJson = keys[0].trim();
292
+ const parsed = tryParseJsonObject(maybeJson);
293
+ if (parsed !== null) {
294
+ return parsed;
255
295
  }
296
+ return null;
297
+ };
298
+ const parseUrlEncodedBody = (text) => {
299
+ // JSON-first recovery: parse raw text before URLSearchParams touches it.
300
+ // URLSearchParams splits on the first '=' (corrupting base64-padded values)
301
+ // and converts '+' to spaces, making encrypted/base64 fields unrecoverable.
302
+ const jsonResult = tryParseJsonObject(text);
303
+ if (jsonResult !== null) {
304
+ return jsonResult;
305
+ }
306
+ const out = parseUrlEncodedParams(text);
256
307
  // Recovery: if a JSON payload is sent with the wrong Content-Type header
257
308
  // (e.g. application/x-www-form-urlencoded instead of application/json),
258
309
  // URLSearchParams will treat the entire JSON string as a single key with
259
310
  // an empty value. Detect this degenerate case and fall back to JSON.parse.
260
- const keys = Object.keys(out);
261
- if (keys.length === 1 && out[keys[0]] === '') {
262
- const maybeJson = keys[0].trim();
263
- if (maybeJson.startsWith('{') && maybeJson.endsWith('}')) {
264
- try {
265
- const parsed = JSON.parse(maybeJson);
266
- if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
267
- return parsed;
268
- }
269
- }
270
- catch {
271
- // Not valid JSON — leave original result unchanged
272
- }
273
- }
311
+ const recovered = tryRecoverDegenerateJson(out);
312
+ if (recovered !== null) {
313
+ return recovered;
274
314
  }
275
315
  return out;
276
316
  };
@@ -1 +1 @@
1
- {"version":3,"file":"BodyParsers.d.ts","sourceRoot":"","sources":["../../../../src/http/parsers/BodyParsers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,UAAU,gBAAgB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAAC;CAChD;AAiLD,eAAO,MAAM,WAAW;;;;IAKtB;;OAEG;cACO,WAAW,EAAE;IAIvB;;OAEG;4BACqB,MAAM,GAAG,WAAW,GAAG,SAAS;IAIxD;;OAEG;uBACgB,MAAM,QAAQ,MAAM,GAAG,MAAM,GAAG,gBAAgB;EAQnE,CAAC;AAEH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"BodyParsers.d.ts","sourceRoot":"","sources":["../../../../src/http/parsers/BodyParsers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,UAAU,gBAAgB;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,OAAO,EAAE,CAAC;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAAC;CAChD;AAoND,eAAO,MAAM,WAAW;;;;IAKtB;;OAEG;cACO,WAAW,EAAE;IAIvB;;OAEG;4BACqB,MAAM,GAAG,WAAW,GAAG,SAAS;IAIxD;;OAEG;uBACgB,MAAM,QAAQ,MAAM,GAAG,MAAM,GAAG,gBAAgB;EAQnE,CAAC;AAEH,eAAe,WAAW,CAAC"}
@@ -27,6 +27,48 @@ const tryRecoverJsonBody = (result) => {
27
27
  }
28
28
  return undefined;
29
29
  };
30
+ /**
31
+ * Try to parse raw text as JSON before URLSearchParams touches it.
32
+ * This handles cases where JSON is sent with application/x-www-form-urlencoded Content-Type,
33
+ * which would be corrupted by URLSearchParams (splits on '=', converts '+' to spaces).
34
+ */
35
+ const tryParseJsonRaw = (text) => {
36
+ const trimmed = text.trim();
37
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
38
+ return undefined;
39
+ try {
40
+ const parsed = JSON.parse(trimmed);
41
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
42
+ return parsed;
43
+ }
44
+ }
45
+ catch {
46
+ // Not valid JSON
47
+ }
48
+ return undefined;
49
+ };
50
+ /**
51
+ * Parse URL-encoded string into key-value pairs, handling duplicate keys as arrays.
52
+ */
53
+ const parseUrlEncodedParams = (text) => {
54
+ const params = new URLSearchParams(text);
55
+ const result = {};
56
+ for (const [key, value] of params.entries()) {
57
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
58
+ const existing = result[key];
59
+ if (Array.isArray(existing)) {
60
+ existing.push(value);
61
+ }
62
+ else {
63
+ result[key] = [existing, value];
64
+ }
65
+ }
66
+ else {
67
+ result[key] = value;
68
+ }
69
+ }
70
+ return result;
71
+ };
30
72
  /**
31
73
  * URL-encoded form data parser
32
74
  * Content-Type: application/x-www-form-urlencoded
@@ -38,25 +80,14 @@ const FormDataParser = {
38
80
  const text = typeof body === 'string' ? body : body.toString('utf-8');
39
81
  if (!text.trim())
40
82
  return { ok: true, data: {} };
41
- const params = new URLSearchParams(text);
42
- const result = {};
43
- for (const [key, value] of params.entries()) {
44
- if (Object.prototype.hasOwnProperty.call(result, key)) {
45
- // Handle multiple values with same key
46
- const existing = result[key];
47
- if (Array.isArray(existing)) {
48
- existing.push(value);
49
- }
50
- else {
51
- result[key] = [existing, value];
52
- }
53
- }
54
- else {
55
- result[key] = value;
56
- }
83
+ // JSON-first recovery: parse raw text before URLSearchParams touches it.
84
+ const rawJsonResult = tryParseJsonRaw(text);
85
+ if (rawJsonResult !== undefined) {
86
+ return { ok: true, data: rawJsonResult };
57
87
  }
58
- // Recovery: if a JSON payload is sent with the wrong Content-Type header,
59
- // URLSearchParams will treat the entire JSON string as a single key with
88
+ const result = parseUrlEncodedParams(text);
89
+ // Recovery: if a JSON payload was sent with wrong Content-Type,
90
+ // URLSearchParams treats the whole JSON string as a single key with
60
91
  // an empty value. Detect this degenerate case and fall back to JSON.parse.
61
92
  const recovered = tryRecoverJsonBody(result);
62
93
  if (recovered !== undefined) {
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v2.5.8
2
+ * @zintrust/core v2.5.9
3
3
  *
4
4
  * ZinTrust Framework - Production-Grade TypeScript Backend
5
5
  * Built for performance, type safety, and exceptional developer experience
6
6
  *
7
7
  * Build Information:
8
- * Built: 2026-06-12T18:44:49.274Z
8
+ * Built: 2026-06-13T09:17:52.095Z
9
9
  * Node: >=20.0.0
10
10
  * License: MIT
11
11
  *
@@ -21,7 +21,7 @@
21
21
  * Available at runtime for debugging and health checks
22
22
  */
23
23
  export const ZINTRUST_VERSION = '0.1.41';
24
- export const ZINTRUST_BUILD_DATE = '2026-06-12T18:44:49.232Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-06-13T09:17:51.999Z'; // Replaced during build
25
25
  export { Application } from './boot/Application.js';
26
26
  export { AwsSigV4 } from './common/index.js';
27
27
  export { SignedRequest } from './security/SignedRequest.js';