@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 +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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
261
|
-
if (
|
|
262
|
-
|
|
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;
|
|
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
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
59
|
-
//
|
|
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.
|
|
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-
|
|
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-
|
|
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';
|