@zintrust/core 2.5.7 → 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"}
|
|
@@ -156,6 +156,29 @@ const parseJsonBody = (text, contentType, res) => {
|
|
|
156
156
|
return null;
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* Attempt to recover a JSON string that was stored as raw text or bytes
|
|
161
|
+
* due to a missing or incorrect Content-Type header.
|
|
162
|
+
*
|
|
163
|
+
* Only plain objects `{...}` are considered for recovery — arrays, strings,
|
|
164
|
+
* and primitives are left unchanged. This minimizes false positives for
|
|
165
|
+
* legitimate text or binary payloads that happen to start with `{`.
|
|
166
|
+
*/
|
|
167
|
+
const tryRecoverTextJsonBody = (body) => {
|
|
168
|
+
const trimmed = body.trim();
|
|
169
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(trimmed);
|
|
172
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Not valid JSON — leave original unchanged
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return body;
|
|
181
|
+
};
|
|
159
182
|
const setRequestBody = (req, rawResult, contentType) => {
|
|
160
183
|
const isUrlEncoded = contentType.includes('application/x-www-form-urlencoded');
|
|
161
184
|
const isText = contentType.startsWith('text/') || contentType.includes('application/xml');
|
|
@@ -164,9 +187,26 @@ const setRequestBody = (req, rawResult, contentType) => {
|
|
|
164
187
|
req.setBody(parseUrlEncodedBody(text));
|
|
165
188
|
}
|
|
166
189
|
else if (isText) {
|
|
167
|
-
|
|
190
|
+
// Recovery: JSON body sent with text Content-Type (e.g. text/plain)
|
|
191
|
+
// is stored as a raw string. Detect a JSON object and parse it.
|
|
192
|
+
req.setBody(tryRecoverTextJsonBody(text));
|
|
168
193
|
}
|
|
169
194
|
else if (contentType !== '') {
|
|
195
|
+
// Recovery: JSON body sent with an unknown Content-Type is stored as
|
|
196
|
+
// raw bytes. Try to decode as text and check for JSON.
|
|
197
|
+
const decoded = text.trim();
|
|
198
|
+
if (decoded.startsWith('{') && decoded.endsWith('}')) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(decoded);
|
|
201
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
202
|
+
req.setBody(parsed);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Not valid JSON — fall through to bytes
|
|
208
|
+
}
|
|
209
|
+
}
|
|
170
210
|
req.setBody(rawResult.bytes);
|
|
171
211
|
}
|
|
172
212
|
};
|
|
@@ -198,39 +238,79 @@ const reuseExistingRawBody = (req, res, contentType, existingBytes, existingText
|
|
|
198
238
|
}
|
|
199
239
|
return applyParsedRequestBody(req, res, rawResult, contentType);
|
|
200
240
|
};
|
|
201
|
-
|
|
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) => {
|
|
202
266
|
const out = {};
|
|
203
267
|
const params = new URLSearchParams(text);
|
|
204
268
|
for (const [key, value] of params.entries()) {
|
|
205
269
|
const existing = out[key];
|
|
206
270
|
if (existing === undefined) {
|
|
207
271
|
out[key] = value;
|
|
208
|
-
continue;
|
|
209
272
|
}
|
|
210
|
-
if (Array.isArray(existing)) {
|
|
273
|
+
else if (Array.isArray(existing)) {
|
|
211
274
|
existing.push(value);
|
|
212
|
-
continue;
|
|
213
275
|
}
|
|
214
|
-
|
|
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;
|
|
215
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);
|
|
216
307
|
// Recovery: if a JSON payload is sent with the wrong Content-Type header
|
|
217
308
|
// (e.g. application/x-www-form-urlencoded instead of application/json),
|
|
218
309
|
// URLSearchParams will treat the entire JSON string as a single key with
|
|
219
310
|
// an empty value. Detect this degenerate case and fall back to JSON.parse.
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
if (maybeJson.startsWith('{') && maybeJson.endsWith('}')) {
|
|
224
|
-
try {
|
|
225
|
-
const parsed = JSON.parse(maybeJson);
|
|
226
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
227
|
-
return parsed;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
// Not valid JSON — leave original result unchanged
|
|
232
|
-
}
|
|
233
|
-
}
|
|
311
|
+
const recovered = tryRecoverDegenerateJson(out);
|
|
312
|
+
if (recovered !== null) {
|
|
313
|
+
return recovered;
|
|
234
314
|
}
|
|
235
315
|
return out;
|
|
236
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';
|