Skip to content

Commit 98a0f4b

Browse files
committed
http2: don't send bogus :path pseudo headers if Request.URL.Opaque is set
Fixes golang/go#16847 Change-Id: I1e6ae1e0746051fd4cf7afc9beae7853293d5b68 Reviewed-on: https://go-review.googlesource.com/27632 Reviewed-by: Chris Broadfoot <[email protected]>
1 parent 5684454 commit 98a0f4b

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

http2.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,13 @@ func (s *sorter) SortStrings(ss []string) {
350350
sort.Sort(s)
351351
s.v = save
352352
}
353+
354+
// validPseudoPath reports whether v is a valid :path pseudo-header
355+
// value. It must be a non-empty string starting with '/', and not
356+
// start with two slashes.
357+
// For now this is only used a quick check for deciding when to clean
358+
// up Opaque URLs before sending requests from the Transport.
359+
// See golang.org/issue/16847
360+
func validPseudoPath(v string) bool {
361+
return len(v) > 0 && v[0] == '/' && (len(v) == 1 || v[1] != '/')
362+
}

transport.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,22 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
998998
host = req.URL.Host
999999
}
10001000

1001+
var path string
1002+
if req.Method != "CONNECT" {
1003+
path = req.URL.RequestURI()
1004+
if !validPseudoPath(path) {
1005+
orig := path
1006+
path = strings.TrimPrefix(path, req.URL.Scheme+"://"+host)
1007+
if !validPseudoPath(path) {
1008+
if req.URL.Opaque != "" {
1009+
return nil, fmt.Errorf("invalid request :path %q from URL.Opaque = %q", orig, req.URL.Opaque)
1010+
} else {
1011+
return nil, fmt.Errorf("invalid request :path %q", orig)
1012+
}
1013+
}
1014+
}
1015+
}
1016+
10011017
// Check for any invalid headers and return an error before we
10021018
// potentially pollute our hpack state. (We want to be able to
10031019
// continue to reuse the hpack encoder for future requests)
@@ -1020,7 +1036,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
10201036
cc.writeHeader(":authority", host)
10211037
cc.writeHeader(":method", req.Method)
10221038
if req.Method != "CONNECT" {
1023-
cc.writeHeader(":path", req.URL.RequestURI())
1039+
cc.writeHeader(":path", path)
10241040
cc.writeHeader(":scheme", "https")
10251041
}
10261042
if trailers != "" {

transport_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,3 +2468,132 @@ func TestTransportBodyDoubleEndStream(t *testing.T) {
24682468
defer res.Body.Close()
24692469
}
24702470
}
2471+
2472+
// golangorg/issue/16847
2473+
func TestTransportRequestPathPseudo(t *testing.T) {
2474+
type result struct {
2475+
path string
2476+
err string
2477+
}
2478+
tests := []struct {
2479+
req *http.Request
2480+
want result
2481+
}{
2482+
0: {
2483+
req: &http.Request{
2484+
Method: "GET",
2485+
URL: &url.URL{
2486+
Host: "foo.com",
2487+
Path: "/foo",
2488+
},
2489+
},
2490+
want: result{path: "/foo"},
2491+
},
2492+
// I guess we just don't let users request "//foo" as
2493+
// a path, since it's illegal to start with two
2494+
// slashes....
2495+
1: {
2496+
req: &http.Request{
2497+
Method: "GET",
2498+
URL: &url.URL{
2499+
Host: "foo.com",
2500+
Path: "//foo",
2501+
},
2502+
},
2503+
want: result{err: `invalid request :path "//foo"`},
2504+
},
2505+
2506+
// Opaque with //$Matching_Hostname/path
2507+
2: {
2508+
req: &http.Request{
2509+
Method: "GET",
2510+
URL: &url.URL{
2511+
Scheme: "https",
2512+
Opaque: "//foo.com/path",
2513+
Host: "foo.com",
2514+
Path: "/ignored",
2515+
},
2516+
},
2517+
want: result{path: "/path"},
2518+
},
2519+
2520+
// Opaque with some other Request.Host instead:
2521+
3: {
2522+
req: &http.Request{
2523+
Method: "GET",
2524+
Host: "bar.com",
2525+
URL: &url.URL{
2526+
Scheme: "https",
2527+
Opaque: "//bar.com/path",
2528+
Host: "foo.com",
2529+
Path: "/ignored",
2530+
},
2531+
},
2532+
want: result{path: "/path"},
2533+
},
2534+
2535+
// Opaque without the leading "//":
2536+
4: {
2537+
req: &http.Request{
2538+
Method: "GET",
2539+
URL: &url.URL{
2540+
Opaque: "/path",
2541+
Host: "foo.com",
2542+
Path: "/ignored",
2543+
},
2544+
},
2545+
want: result{path: "/path"},
2546+
},
2547+
2548+
// Opaque we can't handle:
2549+
5: {
2550+
req: &http.Request{
2551+
Method: "GET",
2552+
URL: &url.URL{
2553+
Scheme: "https",
2554+
Opaque: "//unknown_host/path",
2555+
Host: "foo.com",
2556+
Path: "/ignored",
2557+
},
2558+
},
2559+
want: result{err: `invalid request :path "https://unknown_host/path" from URL.Opaque = "//unknown_host/path"`},
2560+
},
2561+
2562+
// A CONNECT request:
2563+
6: {
2564+
req: &http.Request{
2565+
Method: "CONNECT",
2566+
URL: &url.URL{
2567+
Host: "foo.com",
2568+
},
2569+
},
2570+
want: result{},
2571+
},
2572+
}
2573+
for i, tt := range tests {
2574+
cc := &ClientConn{}
2575+
cc.henc = hpack.NewEncoder(&cc.hbuf)
2576+
cc.mu.Lock()
2577+
hdrs, err := cc.encodeHeaders(tt.req, false, "", -1)
2578+
cc.mu.Unlock()
2579+
var got result
2580+
hpackDec := hpack.NewDecoder(initialHeaderTableSize, func(f hpack.HeaderField) {
2581+
if f.Name == ":path" {
2582+
got.path = f.Value
2583+
}
2584+
})
2585+
if err != nil {
2586+
got.err = err.Error()
2587+
} else if len(hdrs) > 0 {
2588+
if _, err := hpackDec.Write(hdrs); err != nil {
2589+
t.Errorf("%d. bogus hpack: %v", i, err)
2590+
continue
2591+
}
2592+
}
2593+
if got != tt.want {
2594+
t.Errorf("%d. got %+v; want %+v", i, got, tt.want)
2595+
}
2596+
2597+
}
2598+
2599+
}

0 commit comments

Comments
 (0)