You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Access to the full parsed request [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL).
`event.url.pathname` is percent-decoded **once**, while `event.req.url` keeps the original encoding exactly as it arrived on the wire. H3 decodes eagerly so that route matching and any pathname-based middleware always compare the same normalized value — otherwise a request to `/%61dmin` would slip past an `/admin` guard and still reach the `/admin` route.
115
+
116
+
Decoding is a single [`decodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI) pass, and the result is re-serialized by the URL parser. This means only unreserved escapes visibly decode:
|`/a/%2e%2e/b`|`/b`| Decoded `..` collapses during re-parse |
123
+
|`/x%2fy`|`/x%2fy`| Structural, kept encoded |
124
+
|`/100%25`|`/100%25`| Structural, kept encoded |
125
+
|`/a%20b`|`/a%20b`| Re-encoded by the URL serializer |
126
+
|`/caf%C3%A9`|`/caf%C3%A9`| Re-encoded by the URL serializer |
127
+
128
+
Either way, a route param can never contain a path separator that the router did not match on: `%2f` stays encoded, and `%5c` decodes to `\`, which the URL parser then normalizes into a real `/` the router splits on (`/a%5cb` matches the route `/a/:id`).
129
+
130
+
> [!WARNING]
131
+
> Never decode `event.url.pathname` again. A second `decodeURIComponent` can reintroduce a `/` or `..` that routing and middleware never saw, which is a path traversal vector when the value reaches a filesystem or an upstream URL. To read a route param in decoded form, use [`getRouterParams(event, { decode: true })`](/utils/request#getrouterparamsevent-opts-decode), which decodes everything else but keeps encoded separators encoded.
132
+
133
+
Requests with malformed percent-encoding (such as `/foo%` or `/%ZZ`) are rejected with a `400 Bad Request` before any handler runs. Set the [`allowMalformedURL`](/guide/api/h3#h3-options) app option to receive the raw pathname instead.
0 commit comments