Skip to content

Commit

Permalink
net/http: represent multi wildcards properly
Browse files Browse the repository at this point in the history
The routing tree used for matching ServeMux patterns used the
key "*" to hold a child node for a multi-segment wildcard.
The problem is that "*" is a valid path segment, which confused
the matching algorithm: it would fetch the multi wildcard child
when looking for the literal child for "*".

Eschew clever encodings. Use a separate field in the node to
represent the multi wildcard child.

Fixes #67067.

Change-Id: I300ca08b8628f5367626cf41979f6c238ed8c831
Reviewed-on: https://go-review.googlesource.com/c/go/+/582115
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
  • Loading branch information
jba committed Apr 30, 2024
1 parent 8509f69 commit cf05873
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 5 deletions.
8 changes: 8 additions & 0 deletions src/net/http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,14 @@ func TestPathValue(t *testing.T) {
"other": "there/is//more",
},
},
{
"/names/{name}/{other...}",
"/names/n/*",
map[string]string{
"name": "n",
"other": "*",
},
},
} {
mux := NewServeMux()
mux.HandleFunc(test.pattern, func(w ResponseWriter, r *Request) {
Expand Down
8 changes: 5 additions & 3 deletions src/net/http/routing_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ type routingNode struct {
// special children keys:
// "/" trailing slash (resulting from {$})
// "" single wildcard
// "*" multi wildcard
children mapping[string, *routingNode]
multiChild *routingNode // child with multi wildcard
emptyChild *routingNode // optimization: child with key ""
}

Expand Down Expand Up @@ -63,7 +63,9 @@ func (n *routingNode) addSegments(segs []segment, p *pattern, h Handler) {
if len(segs) != 1 {
panic("multi wildcard not last")
}
n.addChild("*").set(p, h)
c := &routingNode{}
n.multiChild = c
c.set(p, h)
} else if seg.wild {
n.addChild("").addSegments(segs[1:], p, h)
} else {
Expand Down Expand Up @@ -185,7 +187,7 @@ func (n *routingNode) matchPath(path string, matches []string) (*routingNode, []
}
// Lastly, match the pattern (there can be at most one) that has a multi
// wildcard in this position to the rest of the path.
if c := n.findChild("*"); c != nil {
if c := n.multiChild; c != nil {
// Don't record a match for a nameless wildcard (which arises from a
// trailing slash in the pattern).
if c.pattern.lastSegment().s != "" {
Expand Down
11 changes: 9 additions & 2 deletions src/net/http/routing_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ func TestRoutingAddPattern(t *testing.T) {
"/a/b"
"":
"/a/b/{y}"
"*":
"/a/b/{x...}"
"/":
"/a/b/{$}"
MULTI:
"/a/b/{x...}"
"g":
"":
"j":
Expand Down Expand Up @@ -172,6 +172,8 @@ func TestRoutingNodeMatch(t *testing.T) {
"HEAD /headwins", nil},
{"GET", "", "/path/to/file",
"/path/{p...}", []string{"to/file"}},
{"GET", "", "/path/*",
"/path/{p...}", []string{"*"}},
})

// A pattern ending in {$} should only match URLS with a trailing slash.
Expand Down Expand Up @@ -291,4 +293,9 @@ func (n *routingNode) print(w io.Writer, level int) {
n, _ := n.children.find(k)
n.print(w, level+1)
}

if n.multiChild != nil {
fmt.Fprintf(w, "%sMULTI:\n", indent)
n.multiChild.print(w, level+1)
}
}

0 comments on commit cf05873

Please sign in to comment.