Description
Even despite setting
UnescapePathValues = false
UseRawPath = true
the URL path value is still unescaped.
This is probably related to that gin sometimes routes request based on either URL.Path
or URL.RawPath
when UseRawPath
is true. URL.RawPath
is not always populated (See https://github.com/golang/go/issues/33596), so when it is missing, it falls back to route on URL.Path
instead. To me, gin should not fall back to using URL.Path
since this path is unescaped, and not raw when UseRawPath
is enabled.
Reference: https://github.com/gin-gonic/gin/blob/cc4e11438cd6c0bcc632fe3492d3817dfa21c337/gin.go#L650-L653
To fix this issue, gin should use URL.EscapedPath() which should return URL.RawPath
if it is non empty, or compute an escaped path if it is empty.
It is possible to work around this issue by ensuring that URL.RawPath
always exists when the gin router handles the request.
Example Workaround
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/testy/:name", func(c *gin.Context) {
n := c.Param("name")
c.Writer.Write([]byte(n))
})
r.UnescapePathValues = false
r.UseRawPath = true
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
req.URL.RawPath = req.URL.EscapedPath()
r.Handler().ServeHTTP(w, req)
})
http.ListenAndServe(":8181", mux)
}
How to reproduce
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/testy/:name", func(c *gin.Context) {
n := c.Param("name")
c.Writer.Write([]byte(n))
})
r.UnescapePathValues = false
r.UseRawPath = true
r.Run(":8181")
}
Expectations
$ curl 'localhost:8181/testy/hi%2Fworld'
hi%2Fworld
$ curl 'localhost:8181/testy/%E3%81%82'
%E3%81%82
Actual result
$ curl 'localhost:8181/testy/hi%2Fworld'
hi%2Fworld
$ curl 'localhost:8181/testy/%E3%81%82'
あ
Environment
- go version: 1.23.0
- gin version (or commit ref): v1.10.0
- operating system: macOS Sequoia