@emeryld/rrroutes-server 2.4.9 → 2.5.0

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/dist/index.js CHANGED
@@ -8,6 +8,140 @@ import {
8
8
  lowProfileParse
9
9
  } from "@emeryld/rrroutes-contract";
10
10
  import multer from "multer";
11
+
12
+ // src/routesV3.server.sanitize.ts
13
+ var defaultTargets = ["params", "query", "body"];
14
+ var defaultBlockedKeys = ["__proto__", "prototype", "constructor"];
15
+ var defaultMaxDepth = 20;
16
+ var nullBytePattern = /\u0000/g;
17
+ var isPlainObject = (value) => {
18
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
19
+ const proto = Object.getPrototypeOf(value);
20
+ return proto === Object.prototype || proto === null;
21
+ };
22
+ var normalizeOptions = (options) => {
23
+ return {
24
+ targets: new Set(options.targets ?? defaultTargets),
25
+ trimStrings: options.trimStrings ?? false,
26
+ stripNullBytes: options.stripNullBytes ?? true,
27
+ stripPrototypePollutionKeys: options.stripPrototypePollutionKeys ?? true,
28
+ blockedKeys: new Set(options.blockedKeys ?? defaultBlockedKeys),
29
+ maxDepth: options.maxDepth ?? defaultMaxDepth,
30
+ customSanitizer: options.customSanitizer
31
+ };
32
+ };
33
+ var applyCustomSanitizer = (value, options, context) => {
34
+ if (!options.customSanitizer) return value;
35
+ return options.customSanitizer(value, context);
36
+ };
37
+ var sanitizeString = (value, options) => {
38
+ let next = value;
39
+ if (options.stripNullBytes && next.includes("\0")) {
40
+ next = next.replace(nullBytePattern, "");
41
+ }
42
+ if (options.trimStrings) {
43
+ next = next.trim();
44
+ }
45
+ return next;
46
+ };
47
+ var sanitizeValue = (value, options, depth, seen, req, target, path) => {
48
+ const context = {
49
+ req,
50
+ target,
51
+ path,
52
+ depth
53
+ };
54
+ if (depth > options.maxDepth) {
55
+ return applyCustomSanitizer(value, options, context);
56
+ }
57
+ if (typeof value === "string") {
58
+ const sanitized = sanitizeString(value, options);
59
+ return applyCustomSanitizer(sanitized, options, context);
60
+ }
61
+ if (value && typeof value === "object" && seen.has(value)) {
62
+ return applyCustomSanitizer(value, options, context);
63
+ }
64
+ if (Array.isArray(value)) {
65
+ seen.add(value);
66
+ const next = value.map(
67
+ (entry, index) => sanitizeValue(entry, options, depth + 1, seen, req, target, [
68
+ ...path,
69
+ index
70
+ ])
71
+ );
72
+ seen.delete(value);
73
+ return applyCustomSanitizer(next, options, context);
74
+ }
75
+ if (!isPlainObject(value)) {
76
+ return applyCustomSanitizer(value, options, context);
77
+ }
78
+ seen.add(value);
79
+ const source = value;
80
+ const objectTarget = Object.getPrototypeOf(source) === null ? /* @__PURE__ */ Object.create(null) : {};
81
+ for (const [key, entry] of Object.entries(source)) {
82
+ if (options.stripPrototypePollutionKeys && options.blockedKeys.has(key)) {
83
+ continue;
84
+ }
85
+ ;
86
+ objectTarget[key] = sanitizeValue(
87
+ entry,
88
+ options,
89
+ depth + 1,
90
+ seen,
91
+ req,
92
+ context.target,
93
+ [...path, key]
94
+ );
95
+ }
96
+ seen.delete(value);
97
+ return applyCustomSanitizer(objectTarget, options, context);
98
+ };
99
+ var createRequestSanitizationMiddleware = (options = {}) => {
100
+ const normalized = normalizeOptions(options);
101
+ return (req, _res, next) => {
102
+ try {
103
+ if (normalized.targets.has("params") && req.params) {
104
+ req.params = sanitizeValue(
105
+ req.params,
106
+ normalized,
107
+ 0,
108
+ /* @__PURE__ */ new WeakSet(),
109
+ req,
110
+ "params",
111
+ []
112
+ );
113
+ }
114
+ if (normalized.targets.has("query") && req.query) {
115
+ req.query = sanitizeValue(
116
+ req.query,
117
+ normalized,
118
+ 0,
119
+ /* @__PURE__ */ new WeakSet(),
120
+ req,
121
+ "query",
122
+ []
123
+ );
124
+ }
125
+ if (normalized.targets.has("body") && req.body !== void 0) {
126
+ req.body = sanitizeValue(
127
+ req.body,
128
+ normalized,
129
+ 0,
130
+ /* @__PURE__ */ new WeakSet(),
131
+ req,
132
+ "body",
133
+ []
134
+ );
135
+ }
136
+ next();
137
+ } catch (err) {
138
+ next(err);
139
+ }
140
+ };
141
+ };
142
+ var requestSanitizationMiddleware = createRequestSanitizationMiddleware();
143
+
144
+ // src/routesV3.server.ts
11
145
  var serverDebugEventTypes = [
12
146
  "register",
13
147
  "request",
@@ -41,12 +175,12 @@ function createServerDebugEmitter(option) {
41
175
  }
42
176
  return disabled;
43
177
  }
44
- var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
178
+ var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
45
179
  var decodeJsonLikeQueryValue = (value) => {
46
180
  if (Array.isArray(value)) {
47
181
  return value.map((entry) => decodeJsonLikeQueryValue(entry));
48
182
  }
49
- if (isPlainObject(value)) {
183
+ if (isPlainObject2(value)) {
50
184
  const next = {};
51
185
  for (const [key, child] of Object.entries(value)) {
52
186
  next[key] = decodeJsonLikeQueryValue(child);
@@ -70,7 +204,7 @@ var REQUEST_PAYLOAD_SYMBOL = /* @__PURE__ */ Symbol.for(
70
204
  "typedLeaves.requestPayload"
71
205
  );
72
206
  function isMulterFile(value) {
73
- if (!isPlainObject(value)) return false;
207
+ if (!isPlainObject2(value)) return false;
74
208
  const candidate = value;
75
209
  return typeof candidate.fieldname === "string";
76
210
  }
@@ -86,7 +220,7 @@ function collectMulterFiles(req) {
86
220
  files.push(value);
87
221
  return;
88
222
  }
89
- if (isPlainObject(value)) {
223
+ if (isPlainObject2(value)) {
90
224
  Object.values(value).forEach(pushValue);
91
225
  }
92
226
  };
@@ -212,9 +346,12 @@ function createRRRoute(router, config) {
212
346
  if (!isVerbose || !details) return event;
213
347
  return { ...event, ...details };
214
348
  };
215
- const globalBeforeMws = [...config.globalMiddleware ?? []].map(
349
+ const middlewareConfig = config.middleware ?? {};
350
+ const postCtxMws = [...middlewareConfig.postCtx ?? []].map(
216
351
  (mw) => adaptCtxMw(mw)
217
352
  );
353
+ const preCtxMws = [...middlewareConfig.preCtx ?? []];
354
+ const sanitizerMw = middlewareConfig.sanitizer === void 0 ? void 0 : typeof middlewareConfig.sanitizer === "function" ? middlewareConfig.sanitizer : createRequestSanitizationMiddleware(middlewareConfig.sanitizer);
218
355
  const registered = getRegisteredRouteStore(router);
219
356
  const getMulterOptions = (fields) => {
220
357
  if (!fields || fields.length === 0) return void 0;
@@ -376,9 +513,11 @@ function createRRRoute(router, config) {
376
513
  }
377
514
  };
378
515
  const before = [
516
+ ...sanitizerMw ? [sanitizerMw] : [],
517
+ ...preCtxMws,
379
518
  resolvePayloadMw,
380
519
  ctxMw,
381
- ...globalBeforeMws,
520
+ ...postCtxMws,
382
521
  ...routeSpecific
383
522
  ];
384
523
  const wrapped = async (req, res, next) => {
@@ -1393,9 +1532,11 @@ export {
1393
1532
  keyOf2 as contractKeyOf,
1394
1533
  createConnectionLoggingMiddleware,
1395
1534
  createRRRoute,
1535
+ createRequestSanitizationMiddleware,
1396
1536
  createSocketConnections,
1397
1537
  defineControllers,
1398
1538
  getCtx,
1539
+ requestSanitizationMiddleware,
1399
1540
  warnMissingControllers
1400
1541
  };
1401
1542
  //# sourceMappingURL=index.js.map