Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/gin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@ jobs:
TESTTAGS: ${{ matrix.test-tags }}
GOPROXY: https://proxy.golang.org
steps:
- name: Checkout Code
uses: actions/checkout@v5
with:
ref: ${{ github.ref }}

- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
cache: false

- name: Checkout Code
uses: actions/checkout@v5
with:
ref: ${{ github.ref }}

- uses: actions/cache@v4
with:
path: |
Expand Down
60 changes: 36 additions & 24 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package gin

import (
"math"
"bytes"
"net/url"
"strings"
"unicode"
Expand All @@ -14,6 +14,12 @@ import (
"github.com/gin-gonic/gin/internal/bytesconv"
)

var (
strColon = []byte(":")
strStar = []byte("*")
strSlash = []byte("/")
)

// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
Key string
Expand Down Expand Up @@ -78,22 +84,38 @@ func (n *node) addChild(child *node) {
}
}

// safeUint16 converts int to uint16 safely, capping at math.MaxUint16
func safeUint16(n int) uint16 {
if n > math.MaxUint16 {
return math.MaxUint16
func countParams(path string) uint16 {
s := bytesconv.StringToBytes(path)
colons := bytes.Count(s, strColon)
stars := bytes.Count(s, strStar)
total := colons + stars
// Cap at max uint16 to prevent overflow
if total > 0xFFFF {
return 0xFFFF
}
return uint16(n)
return uint16(total)
}

func countParams(path string) uint16 {
colons := strings.Count(path, ":")
stars := strings.Count(path, "*")
return safeUint16(colons + stars)
func countSections(path string) uint16 {
s := bytesconv.StringToBytes(path)
count := bytes.Count(s, strSlash)
// Cap at max uint16 to prevent overflow
if count > 0xFFFF {
return 0xFFFF
}
return uint16(count)
}

func countSections(path string) uint16 {
return safeUint16(strings.Count(path, "/"))
// unescapePathValue unescapes a path parameter value if unescape is enabled.
// Only unescapes if the value contains percent-encoded characters or plus signs.
// This prevents double unescaping and potential path traversal vulnerabilities.
func unescapePathValue(val string, unescape bool) string {
if unescape && strings.ContainsAny(val, "%+") {
if v, err := url.QueryUnescape(val); err == nil {
return v
}
}
return val
}

type nodeType uint8
Expand Down Expand Up @@ -518,12 +540,7 @@ walk: // Outer loop for walking the tree
// Expand slice within preallocated capacity
i := len(*value.params)
*value.params = (*value.params)[:i+1]
val := path[:end]
if unescape {
if v, err := url.QueryUnescape(val); err == nil {
val = v
}
}
val := unescapePathValue(path[:end], unescape)
(*value.params)[i] = Param{
Key: n.path[1:],
Value: val,
Expand Down Expand Up @@ -571,12 +588,7 @@ walk: // Outer loop for walking the tree
// Expand slice within preallocated capacity
i := len(*value.params)
*value.params = (*value.params)[:i+1]
val := path
if unescape {
if v, err := url.QueryUnescape(path); err == nil {
val = v
}
}
val := unescapePathValue(path, unescape)
(*value.params)[i] = Param{
Key: n.path[2:],
Value: val,
Expand Down
Loading
Loading