@stonyx/rest-server 0.2.1-beta.93 → 0.2.1-beta.95
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/README.md +0 -373
- package/config/environment.js +0 -192
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -15
- package/dist/main.js.map +1 -1
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +1 -99
- package/dist/request.js.map +1 -1
- package/package.json +2 -2
- package/dist/route-matching.d.ts +0 -257
- package/dist/route-matching.d.ts.map +0 -1
- package/dist/route-matching.js +0 -323
- package/dist/route-matching.js.map +0 -1
package/dist/route-matching.js
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import config from 'stonyx/config';
|
|
2
|
-
/**
|
|
3
|
-
* Applies this module's route-matching settings to an express app.
|
|
4
|
-
*
|
|
5
|
-
* Called from BOTH express construction sites (abofs/stonyx-rest-server#47):
|
|
6
|
-
* `RestServer`'s constructor closes the mount segment (`/PUBLIC/...`), and
|
|
7
|
-
* `Request`'s constructor closes sub-paths (`/public/SUCCESS`). Neither alone
|
|
8
|
-
* is sufficient -- settings are inherited on mount, but `mountRoute()` calls
|
|
9
|
-
* `registerCalls()` before `api.use()`, so each child router is already built
|
|
10
|
-
* by the time the parent's setting could reach it.
|
|
11
|
-
*
|
|
12
|
-
* Both callers invoke this from a constructor, and must keep doing so: express
|
|
13
|
-
* materializes a router lazily on first route registration, and a setting
|
|
14
|
-
* applied afterwards is silently ineffective -- no throw, no warning.
|
|
15
|
-
*
|
|
16
|
-
* The guard is `!== false`, not a plain truthy check, and that polarity is
|
|
17
|
-
* load-bearing. `trustProxy` and `enableHealthCheck` default to the falsy
|
|
18
|
-
* direction, so a missing key fails safe for them. This flag defaults to the
|
|
19
|
-
* truthy direction, so `if (config.restServer?.caseSensitiveRoutes)` would
|
|
20
|
-
* silently fail OPEN for a consumer whose shipped config predates the key.
|
|
21
|
-
*
|
|
22
|
-
* It lives here, in one place, rather than being written out at each call
|
|
23
|
-
* site, so that a single test can anchor it. The invariant is duplicated the
|
|
24
|
-
* moment the expression is: `test/unit/request-test.ts` AC6 reaches this
|
|
25
|
-
* function through `Request`, which means the same assertion now also covers
|
|
26
|
-
* the `RestServer` half. Two copies of the predicate left the parent's copy
|
|
27
|
-
* free to drift -- inverting it, or dropping the condition entirely, kept the
|
|
28
|
-
* suite green.
|
|
29
|
-
*
|
|
30
|
-
* Note `express({ caseSensitive: true })` does NOT work: express 5's
|
|
31
|
-
* `createApplication()` takes zero arguments and forwards nothing. The app
|
|
32
|
-
* setting is the only mechanism. The same is true of `strict`.
|
|
33
|
-
*
|
|
34
|
-
* ---
|
|
35
|
-
*
|
|
36
|
-
* `strict routing` (abofs/stonyx-rest-server#50) closes the same class of
|
|
37
|
-
* authorization bypass for a TRAILING SLASH: `GET /private/failure` was denied
|
|
38
|
-
* by a consumer's auth hook while `GET /private/failure/` reached the guarded
|
|
39
|
-
* handler, because the hook compares `req.path` against `/failure`.
|
|
40
|
-
*
|
|
41
|
-
* It is a SEPARATE key from `caseSensitiveRoutes`, not a rename and not a
|
|
42
|
-
* reuse. Coupling them would force any consumer who legitimately needs
|
|
43
|
-
* trailing-slash tolerance -- a load balancer probing `/health/`, a
|
|
44
|
-
* slash-normalizing proxy -- to re-open #47's case bypass to get it. Case
|
|
45
|
-
* insensitivity is almost never intentional; slash tolerance frequently is.
|
|
46
|
-
*
|
|
47
|
-
* The two settings do NOT share the #47 split above, and this is the one thing
|
|
48
|
-
* not to carry across from that fix. For `strict routing`:
|
|
49
|
-
*
|
|
50
|
-
* - The CHILD site (Request's constructor) closes the entire security
|
|
51
|
-
* defect on its own. The parent site does nothing for it.
|
|
52
|
-
* - The PARENT site (RestServer's constructor) closes exactly one thing in
|
|
53
|
-
* this repo: `/health/`, the only route registered directly on the parent
|
|
54
|
-
* app. It has no security role here; do not describe it as having one.
|
|
55
|
-
*
|
|
56
|
-
* The cause is concrete: `Router.prototype.use` hardcodes `strict: false` (and
|
|
57
|
-
* `end: false`), so mount segments are structurally strict-immune. That is the
|
|
58
|
-
* OPPOSITE of `sensitive`, which `use()` DOES forward, and which is why #47's
|
|
59
|
-
* parent site closed `/PUBLIC/...`. The version-pinned file-and-line citation
|
|
60
|
-
* for that upstream behaviour is deliberately kept in ONE place --
|
|
61
|
-
* `docs/project-structure.md`, section "Strict routing (#50)" -- so a router
|
|
62
|
-
* upgrade invalidates one line rather than five. Both sites still get both
|
|
63
|
-
* settings (they share this function), but the justification differs and the
|
|
64
|
-
* tests are built on the measured split, not on the analogy.
|
|
65
|
-
*
|
|
66
|
-
* Consequence worth stating so nobody expects this SETTING to cover it: no
|
|
67
|
-
* express setting rejects the mount-segment trailing slash (`/public/`). For
|
|
68
|
-
* both `/public` and `/public/` the mounted sub-app receives `req.path === '/'`,
|
|
69
|
-
* so a `req.path` auth hook sees no difference and there is nothing for the
|
|
70
|
-
* setting to reject. That statement is still true, and it is still the reason
|
|
71
|
-
* `applyRouteMatching()` is not the fix site for that edge.
|
|
72
|
-
*
|
|
73
|
-
* The edge itself is CLOSED, by `shouldRejectTarget()` below rather than by a
|
|
74
|
-
* setting (abofs/stonyx-rest-server#54). `req.originalUrl` does differ between
|
|
75
|
-
* the two spellings, and a hook authorizing on it was bypassed by one
|
|
76
|
-
* character -- measured: `GET /admin` -> 401, `GET /admin/` -> 200 with the
|
|
77
|
-
* guarded handler running unauthenticated. `GET /public/` now returns 404, and
|
|
78
|
-
* the integration AC asserts exactly that.
|
|
79
|
-
*
|
|
80
|
-
* All four guards in this file are `!== false` for the same reason: these flags
|
|
81
|
-
* default to the
|
|
82
|
-
* truthy direction, so a truthy check fails OPEN for a consumer whose shipped
|
|
83
|
-
* config predates the key. All are also asserted at the unit tier for BOTH
|
|
84
|
-
* failure shapes -- key present-and-`undefined` and key absent as an own
|
|
85
|
-
* property -- in `test/unit/request-test.ts` (#47's AC6, #50's AC3, #54's AC2,
|
|
86
|
-
* #56's AC6). The integration tier cannot see any of them: with the shipped
|
|
87
|
-
* default `true`, a fail-open guard leaves every integration assertion green.
|
|
88
|
-
*/
|
|
89
|
-
export default function applyRouteMatching(api) {
|
|
90
|
-
if (config.restServer?.caseSensitiveRoutes !== false)
|
|
91
|
-
api.set('case sensitive routing', true);
|
|
92
|
-
if (config.restServer?.strictRoutes !== false)
|
|
93
|
-
api.set('strict routing', true);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Decides whether a request must be rejected because its RAW request target is
|
|
97
|
-
* not the canonical path express matched (abofs/stonyx-rest-server#54).
|
|
98
|
-
*
|
|
99
|
-
* Closes two live authorization bypasses against a consumer hook that
|
|
100
|
-
* authorizes on `req.originalUrl` -- the field express does NOT normalize:
|
|
101
|
-
*
|
|
102
|
-
* 1. mount-root trailing slash GET /admin/ -> was 200
|
|
103
|
-
* 2. absolute-form request target GET http://host/admin -> was 200
|
|
104
|
-
* (RFC 9112 3.2.2; hits EVERY route, not just the mount root)
|
|
105
|
-
*
|
|
106
|
-
* Both were measured reaching the guarded handler unauthenticated while
|
|
107
|
-
* `GET /admin` was denied 401.
|
|
108
|
-
*
|
|
109
|
-
* COMPARE THE RAW TARGET; DO NOT PARSE IT. Any implementation reaching for
|
|
110
|
-
* `new URL(req.originalUrl, base).pathname` to "get the path" re-opens vector 2
|
|
111
|
-
* BY CONSTRUCTION: parsing normalizes the exact string the consumer's hook is
|
|
112
|
-
* exposed to, so the check would compare a laundered value while the hook still
|
|
113
|
-
* sees the raw one. Measured: the narrow `endsWith('/')` form closes 1 and
|
|
114
|
-
* leaves 2 at 200.
|
|
115
|
-
*
|
|
116
|
-
* `req.baseUrl + req.path` is likewise NOT usable as the left-hand side. It is
|
|
117
|
-
* `/admin/` for BOTH spellings of vector 1 -- `originalUrl` is the only field
|
|
118
|
-
* that differs, which is precisely why the bypass exists. A check built on
|
|
119
|
-
* `baseUrl + path` cannot see its own defect.
|
|
120
|
-
*
|
|
121
|
-
* TIMING CONTRACT -- DELIBERATELY DIFFERENT FROM ITS TWO SIBLINGS. This lives
|
|
122
|
-
* beside `applyRouteMatching()` for the same "one place anchors it" reason, but
|
|
123
|
-
* deliberately OUTSIDE it: that function's contract is *apply express settings
|
|
124
|
-
* to an app*, it is called from two constructors, and this is neither a setting
|
|
125
|
-
* nor constructor-timed. `caseSensitiveRoutes`/`strictRoutes` are read once in a
|
|
126
|
-
* constructor and are silently ineffective if applied late; this flag is read
|
|
127
|
-
* PER REQUEST, inside the handler closure. There is no lazy-materialisation
|
|
128
|
-
* hazard here, so do not carry that constraint across.
|
|
129
|
-
*
|
|
130
|
-
* The caller must reject with `next('router')`, NOT `res.sendStatus(404)`, and
|
|
131
|
-
* must run this BEFORE `this.auth` as well as outside `if (this.auth)` -- those
|
|
132
|
-
* are two separate properties with two separate assertions (AC1.11 and AC1.6);
|
|
133
|
-
* see src/request.ts.
|
|
134
|
-
*
|
|
135
|
-
* Guard polarity is `!== false`, matching its three siblings, for the same measured
|
|
136
|
-
* reason: the secure value is the TRUTHY one, so a plain truthy check fails
|
|
137
|
-
* OPEN for any consumer whose shipped `restServer` block predates the key --
|
|
138
|
-
* the state every existing consumer is in, and reachable in practice because
|
|
139
|
-
* the stonyx loader only merges a module's `config/environment.js` for modules
|
|
140
|
-
* in devDependencies. `trustProxy` deliberately differs (`=== 'true'`): its safe
|
|
141
|
-
* default is FALSY, so a truthy check already fails closed for it. The rule is
|
|
142
|
-
* "the guard must fail toward the safe value", not "all guards look alike" --
|
|
143
|
-
* preserve the asymmetry.
|
|
144
|
-
*
|
|
145
|
-
* The integration tier cannot see a fail-open guard here: with the shipped
|
|
146
|
-
* default `true`, `=== true` leaves every integration assertion green. Only
|
|
147
|
-
* `test/unit/request-test.ts` AC2 can, and it probes BOTH failure shapes --
|
|
148
|
-
* key present-and-`undefined` and key absent as an own property.
|
|
149
|
-
*/
|
|
150
|
-
export function shouldRejectTarget(req) {
|
|
151
|
-
// Written as `!== false` rather than `=== false` on purpose: the polarity is
|
|
152
|
-
// the load-bearing part and it should read identically to the two guards in
|
|
153
|
-
// applyRouteMatching() above.
|
|
154
|
-
const enforced = config.restServer?.canonicalRoutes !== false;
|
|
155
|
-
if (!enforced)
|
|
156
|
-
return false;
|
|
157
|
-
// Raw, unparsed. Only the query string is removed, by string split.
|
|
158
|
-
const target = req.originalUrl.split('?')[0];
|
|
159
|
-
// At a mount root express reports `req.path === '/'` while the canonical
|
|
160
|
-
// target is the bare mount segment, so the two are not simply concatenated.
|
|
161
|
-
//
|
|
162
|
-
// `&& req.baseUrl` is load-bearing and is NOT a redundant truthiness guard.
|
|
163
|
-
// A route class named `index` mounts at '/' (src/main.ts `mountRoute()`),
|
|
164
|
-
// and that is the one mount shape where `req.baseUrl` is ''. Without the
|
|
165
|
-
// conjunct, `GET /` compares the raw target '/' against a canonical of '' and
|
|
166
|
-
// the APPLICATION ROOT is rejected. Measured before it had a guard: shipped
|
|
167
|
-
// `GET /` -> 200, conjunct dropped -> 404, suite 34 pass / 0 fail BOTH ways.
|
|
168
|
-
// Killed now by AC1.12, against `test/sample/requests/index.ts`.
|
|
169
|
-
const canonical = req.path === '/' && req.baseUrl ? req.baseUrl : req.baseUrl + req.path;
|
|
170
|
-
return target !== canonical;
|
|
171
|
-
}
|
|
172
|
-
// RFC 3986 §2.3 UNRESERVED = ALPHA / DIGIT / "-" / "." / "_" / "~".
|
|
173
|
-
//
|
|
174
|
-
// These are the characters a URI generator MUST NOT percent-encode and that a
|
|
175
|
-
// normaliser MUST decode (§6.2.2.2), so an encoded one carries no information
|
|
176
|
-
// a client is ever required to send. Everything else -- every RESERVED
|
|
177
|
-
// character and every non-ASCII octet -- stays encodable, which is the whole
|
|
178
|
-
// reason this is an allowlist of octets rather than a ban on triplets. See
|
|
179
|
-
// `shouldRejectEncoding()` below.
|
|
180
|
-
const UNRESERVED_OCTET = /^[A-Za-z0-9\-._~]$/;
|
|
181
|
-
// A percent-triplet: `%` followed by exactly two hex digits, either case.
|
|
182
|
-
//
|
|
183
|
-
// A `%` can never be part of ANOTHER triplet's hex digits, because `%` is not a
|
|
184
|
-
// hex digit -- so scanning left to right without skipping cannot produce an
|
|
185
|
-
// overlapping false match. `%2561` therefore yields exactly one candidate
|
|
186
|
-
// (`%25`), which is the property AC4 pins.
|
|
187
|
-
//
|
|
188
|
-
// Malformed and over-long escapes (`%zz`, `%`, `%6`, `%c1%a1`, `%e0%81%a1`) are
|
|
189
|
-
// deliberately NOT this function's business: `router@2.2.0`'s `decodeParam`
|
|
190
|
-
// (lib/layer.js:225) answers 400 for them before any handler or hook runs.
|
|
191
|
-
// Verified here rather than imported -- measured 400 both before and after this
|
|
192
|
-
// change. None of those octets is unreserved, and the first three are not valid
|
|
193
|
-
// triplets at all, so the rule does not touch them either way.
|
|
194
|
-
const PERCENT_TRIPLET = /%([0-9A-Fa-f]{2})/g;
|
|
195
|
-
/**
|
|
196
|
-
* Decides whether a request must be rejected because its RAW request target
|
|
197
|
-
* spells an unreserved character as a percent-triplet
|
|
198
|
-
* (abofs/stonyx-rest-server#56).
|
|
199
|
-
*
|
|
200
|
-
* Closes a live authorization bypass on any route class carrying a `:param`
|
|
201
|
-
* segment. Express decodes `req.params` and NOTHING else -- `req.path` and
|
|
202
|
-
* `req.originalUrl` both stay percent-encoded -- so a consumer hook comparing
|
|
203
|
-
* either of those raw fields was walked past by re-spelling the id:
|
|
204
|
-
*
|
|
205
|
-
* GET /enc/secret -> 401 (hook fires)
|
|
206
|
-
* GET /enc/%73ecret -> 200 guarded handler, unauthenticated, id "secret"
|
|
207
|
-
*
|
|
208
|
-
* Both hook shapes are affected and neither is safer than the other; there is
|
|
209
|
-
* no spelling that defeats one and not the same-id comparison in the other.
|
|
210
|
-
* A third shape is worse still: a LITERAL guarded route co-registered with a
|
|
211
|
-
* sibling `/:id` (this repo's own `test/sample/requests/private.ts`) has the
|
|
212
|
-
* encoded spelling miss the literal layer and be ABSORBED by the param route,
|
|
213
|
-
* so the guard is walked past without the guarded handler ever running --
|
|
214
|
-
* measured `GET /private/failure` -> 505 vs `GET /private/%66ailure` -> 200.
|
|
215
|
-
*
|
|
216
|
-
* THE RULE IS AN UNRESERVED-OCTET SCAN, NOT A DECODE-AND-COMPARE. Two wrong
|
|
217
|
-
* implementations were built and measured, and each breaks a legitimate
|
|
218
|
-
* request:
|
|
219
|
-
*
|
|
220
|
-
* 1. `decodeURIComponent(target) !== target` -- rejects `/enc/sec%2fret`
|
|
221
|
-
* (404), which names the DISTINCT id `sec/ret`. The router SPLITS then
|
|
222
|
-
* DECODES; a whole-target decode decodes then splits, and the two
|
|
223
|
-
* disagree about `%2f` by construction. Killed by AC3.
|
|
224
|
-
* 2. decode until stable -- rejects `/enc/%2573ecret` (404), which names the
|
|
225
|
-
* legitimate id `%73ecret`. Express decodes EXACTLY ONCE, so `%2561` is
|
|
226
|
-
* not a bypass and a loop invents a false deny. Killed by AC4.
|
|
227
|
-
*
|
|
228
|
-
* WHY THIS IS NOT PART OF `shouldRejectTarget()` (#54), and why extending that
|
|
229
|
-
* comparison cannot work: for `GET /enc/%73ecret`, `originalUrl` is
|
|
230
|
-
* `/enc/%73ecret`, `baseUrl` is `/enc` and `path` is `/%73ecret`, so
|
|
231
|
-
* `target === canonical` -- both sides carry the SAME encoded string. The
|
|
232
|
-
* comparison is structurally blind to this axis and no change to it can see it.
|
|
233
|
-
*
|
|
234
|
-
* WHY IT IS A FOURTH KEY AND NOT A REUSE OF `canonicalRoutes`. Measured with
|
|
235
|
-
* the rule implemented correctly but gated on #54's key:
|
|
236
|
-
* `REST_CANONICAL_ROUTES=false` returns `GET /enc/%73ecret` to 200. That flag
|
|
237
|
-
* is exactly what a consumer behind an absolute-form-emitting forward proxy
|
|
238
|
-
* must set, so folding the two would hand precisely those consumers the
|
|
239
|
-
* encoding bypass as the price of staying up. Same argument the block above
|
|
240
|
-
* makes for why #50 is not a rename of #47. Pinned by
|
|
241
|
-
* `test/unit/request-test.ts` AC5, which also asserts -- in that same state --
|
|
242
|
-
* that #54's own vector IS re-opened, so an implementation that simply ignores
|
|
243
|
-
* `canonicalRoutes` cannot pass it vacuously.
|
|
244
|
-
*
|
|
245
|
-
* TIMING CONTRACT: identical to `shouldRejectTarget()` and NOT to the two
|
|
246
|
-
* settings above. Read per request, inside the handler closure in
|
|
247
|
-
* `Request.registerCalls()`; there is no lazy-materialisation hazard, so do not
|
|
248
|
-
* move it into `applyRouteMatching()`. The caller must reject with
|
|
249
|
-
* `next('router')`, and must run this BEFORE `this.auth` as well as outside
|
|
250
|
-
* `if (this.auth)` -- see src/request.ts.
|
|
251
|
-
*
|
|
252
|
-
* Guard polarity is `!== false`, matching all three siblings, for the same
|
|
253
|
-
* measured reason: the secure value is the TRUTHY one, so `=== true` fails OPEN
|
|
254
|
-
* for any consumer whose shipped `restServer` block predates the key. The
|
|
255
|
-
* integration tier CANNOT see that mutation -- with the shipped default `true`
|
|
256
|
-
* every integration assertion stays green -- so it is `test/unit/request-test.ts`
|
|
257
|
-
* AC6 that kills it, probing the key present-and-`undefined` and absent as an
|
|
258
|
-
* own property separately.
|
|
259
|
-
*
|
|
260
|
-
* WHAT THIS DOES NOT CLOSE, stated here rather than left implied. It cannot
|
|
261
|
-
* give each decoded id exactly one accepted spelling, because everything the
|
|
262
|
-
* allowlist above does NOT cover must remain encodable: `/enc/a+b` and
|
|
263
|
-
* `/enc/a%2Bb` both name the id `a+b`, and `/enc/sec%2fret` and
|
|
264
|
-
* `/enc/sec%2Fret` both name `sec/ret`.
|
|
265
|
-
*
|
|
266
|
-
* THE RESIDUAL IS WIDER THAN "RESERVED CHARACTERS" AND MUST NOT BE WRITTEN
|
|
267
|
-
* DOWN THAT WAY. An octet outside `[A-Za-z0-9-._~]` keeps more than one
|
|
268
|
-
* accepted spelling when its hex carries a letter digit (upper- and lower-case
|
|
269
|
-
* hex) or when a client may also send it literally. That is every reserved
|
|
270
|
-
* character, every non-ASCII byte AND every control octet whose hex carries a
|
|
271
|
-
* letter digit. Measured through a real listener against this predicate, on a
|
|
272
|
-
* deny list holding NO reserved character at all:
|
|
273
|
-
*
|
|
274
|
-
* GET /i18n/caf%C3%A9 -> 401 GET /i18n/caf%c3%a9 -> 200, id "café"
|
|
275
|
-
* GET /i18n/%E5%8C%97%E4%BA%AC -> 401 GET /i18n/%e5%8c%97%e4%ba%ac -> 200, id "北京"
|
|
276
|
-
* GET /i18n/a%0Db -> 401 GET /i18n/a%0db -> 200, id "a\rb"
|
|
277
|
-
*
|
|
278
|
-
* A consumer whose ids are i18n text reads "any id containing a reserved
|
|
279
|
-
* character" as not applying to them. It does. The three docs that carried the
|
|
280
|
-
* narrow wording (`README.md`, `docs/project-structure.md`,
|
|
281
|
-
* `docs/agents/security-reviewer.md`) were widened to this scope rather than
|
|
282
|
-
* this comment being narrowed to theirs.
|
|
283
|
-
*
|
|
284
|
-
* NOT the whole complement of the unreserved set, and this qualifier is load-
|
|
285
|
-
* bearing rather than pedantry -- the sentence above is stated as measured, so
|
|
286
|
-
* it must not over-warn either. Measured counterexamples: `%21` and `%40` are
|
|
287
|
-
* reserved and carry no letter hex digit, so they alias literal-versus-encoded
|
|
288
|
-
* rather than by hex case; `%00` and `%09` have exactly one accepted spelling
|
|
289
|
-
* and do not alias at all; `%90` is a 400 (invalid UTF-8), not an alias.
|
|
290
|
-
*
|
|
291
|
-
* So a hook comparing a raw path string REMAINS UNSOUND for any id carrying an
|
|
292
|
-
* octet outside `[A-Za-z0-9-._~]` that keeps more than one accepted spelling,
|
|
293
|
-
* and `req.params` -- which express decodes, and which is populated before
|
|
294
|
-
* `auth()` runs -- is the sound idiom. That
|
|
295
|
-
* residual is the consumer's comparison to own; the module cannot close it
|
|
296
|
-
* without 404ing encodings clients are required to emit.
|
|
297
|
-
*
|
|
298
|
-
* SCOPE LIMIT, separate from the residual above: this predicate is called from
|
|
299
|
-
* `Request.registerCalls()`, so it covers the routes mounted from request
|
|
300
|
-
* classes and nothing else. A route registered directly on the public
|
|
301
|
-
* `RestServer.instance.api` gets none of it -- measured,
|
|
302
|
-
* `GET /direct/%73ecret` -> 200 with `id "secret"` while `GET /enc/%73ecret`
|
|
303
|
-
* -> 404. Same registration-site limit `canonicalRoutes` (#54) has.
|
|
304
|
-
*/
|
|
305
|
-
export function shouldRejectEncoding(req) {
|
|
306
|
-
// `!== false`, not `=== false` and not a truthy check: the polarity is the
|
|
307
|
-
// load-bearing part and it should read identically to the three guards above.
|
|
308
|
-
const enforced = config.restServer?.canonicalEncoding !== false;
|
|
309
|
-
if (!enforced)
|
|
310
|
-
return false;
|
|
311
|
-
// Raw, unparsed, and only the query string removed -- by string split, for
|
|
312
|
-
// the same reason #54 gives: parsing would launder the exact string the
|
|
313
|
-
// consumer's hook is exposed to. The query is stripped because a query string
|
|
314
|
-
// is a legitimately variable part of a request target and may carry any
|
|
315
|
-
// encoding at all; `?name=%61` is a normal request and must not 404.
|
|
316
|
-
const target = req.originalUrl.split('?')[0];
|
|
317
|
-
for (const [, hex] of target.matchAll(PERCENT_TRIPLET)) {
|
|
318
|
-
if (UNRESERVED_OCTET.test(String.fromCharCode(parseInt(hex, 16))))
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
//# sourceMappingURL=route-matching.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"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"}
|