Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(req): correct c.req.param decodes invalid percent strings #3573

Merged
merged 3 commits into from
Oct 30, 2024

Conversation

yusukebe
Copy link
Member

@yusukebe yusukebe commented Oct 28, 2024

Fixes #3550

In the current behavior, the c.req.param() will throw the error when decoding invalid percent encoding or invalid UTF-8 sequence.

// app
app.get('/static/:path', (c) => c.text(c.req.param('path'))

// throw `URIError: URI malformed` error:
await app.request('/static/%A4%A2') //  %A4%A2 is invalid UTF-8 sequence

// throw `URIError: URI malformed` error:
await app.request('/static/%a') // %a is invalid percent encoding

These behaviors are expected in the current version implementation; they are tested:

https://github.com/honojs/hono/compare/fix/request-dont-throw-error-with-percent-strings?expand=1#diff-8ac13809c9886e994d1db33943de82df4d6c5d88b73fd0270c0189804ff565c2L772-L814

However, according to #3550, throwing the error will stop the application procedure, and it can accept malicious requests. I think it should not throw the error. This should be fixed, though we should rewrite the test code.

The correct behavior

How to handle the invalid path parameter strings. One way is throwing 400 Bad Request, but as I commented, it should follow the decode method of the getPath() to resolve the URL path:

return tryDecodeURI(path.includes('%25') ? path.replace(/%25/g, '%2525') : path)

So, the result will be like the following:

res = await app.request('/static/%A4%A2')
console.log(await res.text()) // %A4%A2

res = await app.request('/static/%a')
console.log(await res.text()) // %a

In this PR, I used the same logic of tryDecodeURI for tryDecodeURIComponent.

The author should do the following, if applicable

  • Add tests
  • Run tests
  • bun run format:fix && bun run lint:fix to format the code
  • Add TSDoc/JSDoc to document the code

Copy link

codecov bot commented Oct 28, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.71%. Comparing base (3a59d86) to head (546a188).
Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3573      +/-   ##
==========================================
- Coverage   94.71%   94.71%   -0.01%     
==========================================
  Files         157      158       +1     
  Lines        9539     9537       -2     
  Branches     2774     2794      +20     
==========================================
- Hits         9035     9033       -2     
  Misses        504      504              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@yusukebe
Copy link
Member Author

Hi @usualoma !

Can you review this?

Copy link
Member

@usualoma usualoma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yusukebe Thank you! I agree with this approach.

tryDecodeURIComponent

This is a pure function that does not require 'this', so I think it is better to make it an independent function rather than an object property. I think that would also improve performance.

Also, even if we merge this pull request, I think the minified result will be smaller if we use decodeURIComponent_. However, it's only a few characters, so it may not matter.

diff --git a/src/request.ts b/src/request.ts
index 92ebc771..6239ebac 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -12,7 +12,7 @@ import type {
 import { parseBody } from './utils/body'
 import type { BodyData, ParseBodyOptions } from './utils/body'
 import type { Simplify, UnionToIntersection } from './utils/types'
-import { getQueryParam, getQueryParams, tryDecode } from './utils/url'
+import { decodeURIComponent_, getQueryParam, getQueryParams, tryDecode } from './utils/url'
 
 type Body = {
   json: any
@@ -23,6 +23,8 @@ type Body = {
 }
 type BodyCache = Partial<Body & { parsedBody: BodyData }>
 
+const tryDecodeURIComponent = (str: string) => tryDecode(str, decodeURIComponent_)
+
 export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
   /**
    * `.raw` can get the raw Request object.
@@ -91,12 +93,10 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
     return key ? this.getDecodedParam(key) : this.getAllDecodedParams()
   }
 
-  #tryDecodeURIComponent = (str: string) => tryDecode(str, decodeURIComponent)
-
   private getDecodedParam(key: string): string | undefined {
     const paramKey = this.#matchResult[0][this.routeIndex][1][key]
     const param = this.getParamValue(paramKey)
-    return param ? this.#tryDecodeURIComponent(param) : undefined
+    return param ? tryDecodeURIComponent(param) : undefined
   }
 
   private getAllDecodedParams(): Record<string, string> {
@@ -106,7 +106,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
     for (const key of keys) {
       const value = this.getParamValue(this.#matchResult[0][this.routeIndex][1][key])
       if (value && typeof value === 'string') {
-        decoded[key] = this.#tryDecodeURIComponent(value)
+        decoded[key] = tryDecodeURIComponent(value)
       }
     }
 

src/request.ts Outdated Show resolved Hide resolved
@yusukebe
Copy link
Member Author

@usualoma Thanks! Agree all. Can you review this again?

Copy link
Member

@usualoma usualoma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yusukebe Thank you!

Copy link

@devlopersabbir devlopersabbir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@yusukebe
Copy link
Member Author

@usualoma

Thanks! Merging now.

@yusukebe yusukebe merged commit 2bde76d into main Oct 30, 2024
16 checks passed
@yusukebe yusukebe deleted the fix/request-dont-throw-error-with-percent-strings branch October 30, 2024 01:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Path param decoding failure not handled
3 participants