Skip to content

Commit 88548d0

Browse files
odeke-embradfitz
authored andcommitted
net/http: make Server return 501 for unsupported transfer-encodings
Ensures that our HTTP/1.X Server properly responds with a 501 Unimplemented as mandated by the spec at RFC 7230 Section 3.3.1, which says: A server that receives a request message with a transfer coding it does not understand SHOULD respond with 501 (Unimplemented). Fixes #30710 Change-Id: I096904e6df053cd1e4b551774cc27523ff3d09f6 Reviewed-on: https://go-review.googlesource.com/c/go/+/167017 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent c66ab9b commit 88548d0

File tree

4 files changed

+129
-11
lines changed

4 files changed

+129
-11
lines changed

src/net/http/serve_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6071,6 +6071,62 @@ func TestServerContexts(t *testing.T) {
60716071
}
60726072
}
60736073

6074+
// Issue 30710: ensure that as per the spec, a server responds
6075+
// with 501 Not Implemented for unsupported transfer-encodings.
6076+
func TestUnsupportedTransferEncodingsReturn501(t *testing.T) {
6077+
cst := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
6078+
w.Write([]byte("Hello, World!"))
6079+
}))
6080+
defer cst.Close()
6081+
6082+
serverURL, err := url.Parse(cst.URL)
6083+
if err != nil {
6084+
t.Fatalf("Failed to parse server URL: %v", err)
6085+
}
6086+
6087+
unsupportedTEs := []string{
6088+
"fugazi",
6089+
"foo-bar",
6090+
"unknown",
6091+
}
6092+
6093+
for _, badTE := range unsupportedTEs {
6094+
http1ReqBody := fmt.Sprintf(""+
6095+
"POST / HTTP/1.1\r\nConnection: close\r\n"+
6096+
"Host: localhost\r\nTransfer-Encoding: %s\r\n\r\n", badTE)
6097+
6098+
gotBody, err := fetchWireResponse(serverURL.Host, []byte(http1ReqBody))
6099+
if err != nil {
6100+
t.Errorf("%q. unexpected error: %v", badTE, err)
6101+
continue
6102+
}
6103+
6104+
wantBody := fmt.Sprintf("" +
6105+
"HTTP/1.1 501 Not Implemented\r\nContent-Type: text/plain; charset=utf-8\r\n" +
6106+
"Connection: close\r\n\r\nUnsupported transfer encoding")
6107+
6108+
if string(gotBody) != wantBody {
6109+
t.Errorf("%q. body\ngot\n%q\nwant\n%q", badTE, gotBody, wantBody)
6110+
}
6111+
}
6112+
}
6113+
6114+
// fetchWireResponse is a helper for dialing to host,
6115+
// sending http1ReqBody as the payload and retrieving
6116+
// the response as it was sent on the wire.
6117+
func fetchWireResponse(host string, http1ReqBody []byte) ([]byte, error) {
6118+
conn, err := net.Dial("tcp", host)
6119+
if err != nil {
6120+
return nil, err
6121+
}
6122+
defer conn.Close()
6123+
6124+
if _, err := conn.Write(http1ReqBody); err != nil {
6125+
return nil, err
6126+
}
6127+
return ioutil.ReadAll(conn)
6128+
}
6129+
60746130
func BenchmarkResponseStatusLine(b *testing.B) {
60756131
b.ReportAllocs()
60766132
b.RunParallel(func(pb *testing.PB) {

src/net/http/server.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,7 +1822,8 @@ func (c *conn) serve(ctx context.Context) {
18221822
if err != nil {
18231823
const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
18241824

1825-
if err == errTooLarge {
1825+
switch {
1826+
case err == errTooLarge:
18261827
// Their HTTP client may or may not be
18271828
// able to read this if we're
18281829
// responding to them and hanging up
@@ -1832,18 +1833,31 @@ func (c *conn) serve(ctx context.Context) {
18321833
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
18331834
c.closeWriteAndWait()
18341835
return
1835-
}
1836-
if isCommonNetReadError(err) {
1836+
1837+
case isUnsupportedTEError(err):
1838+
// Respond as per RFC 7230 Section 3.3.1 which says,
1839+
// A server that receives a request message with a
1840+
// transfer coding it does not understand SHOULD
1841+
// respond with 501 (Unimplemented).
1842+
code := StatusNotImplemented
1843+
1844+
// We purposefully aren't echoing back the transfer-encoding's value,
1845+
// so as to mitigate the risk of cross side scripting by an attacker.
1846+
fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
1847+
return
1848+
1849+
case isCommonNetReadError(err):
18371850
return // don't reply
1838-
}
18391851

1840-
publicErr := "400 Bad Request"
1841-
if v, ok := err.(badRequestError); ok {
1842-
publicErr = publicErr + ": " + string(v)
1843-
}
1852+
default:
1853+
publicErr := "400 Bad Request"
1854+
if v, ok := err.(badRequestError); ok {
1855+
publicErr = publicErr + ": " + string(v)
1856+
}
18441857

1845-
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
1846-
return
1858+
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
1859+
return
1860+
}
18471861
}
18481862

18491863
// Expect 100 Continue support

src/net/http/transfer.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,22 @@ func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
589589
// Checks whether the encoding is explicitly "identity".
590590
func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }
591591

592+
// unsupportedTEError reports unsupported transfer-encodings.
593+
type unsupportedTEError struct {
594+
err string
595+
}
596+
597+
func (uste *unsupportedTEError) Error() string {
598+
return uste.err
599+
}
600+
601+
// isUnsupportedTEError checks if the error is of type
602+
// unsupportedTEError. It is usually invoked with a non-nil err.
603+
func isUnsupportedTEError(err error) bool {
604+
_, ok := err.(*unsupportedTEError)
605+
return ok
606+
}
607+
592608
// fixTransferEncoding sanitizes t.TransferEncoding, if needed.
593609
func (t *transferReader) fixTransferEncoding() error {
594610
raw, present := t.Header["Transfer-Encoding"]
@@ -615,7 +631,7 @@ func (t *transferReader) fixTransferEncoding() error {
615631
break
616632
}
617633
if encoding != "chunked" {
618-
return &badStringError{"unsupported transfer encoding", encoding}
634+
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", encoding)}
619635
}
620636
te = te[0 : len(te)+1]
621637
te[len(te)-1] = encoding

src/net/http/transfer_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,35 @@ func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
278278
})
279279
}
280280
}
281+
282+
func TestFixTransferEncoding(t *testing.T) {
283+
tests := []struct {
284+
hdr Header
285+
wantErr error
286+
}{
287+
{
288+
hdr: Header{"Transfer-Encoding": {"fugazi"}},
289+
wantErr: &unsupportedTEError{`unsupported transfer encoding: "fugazi"`},
290+
},
291+
{
292+
hdr: Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}},
293+
wantErr: &badStringError{"too many transfer encodings", "chunked,chunked"},
294+
},
295+
{
296+
hdr: Header{"Transfer-Encoding": {"chunked"}},
297+
wantErr: nil,
298+
},
299+
}
300+
301+
for i, tt := range tests {
302+
tr := &transferReader{
303+
Header: tt.hdr,
304+
ProtoMajor: 1,
305+
ProtoMinor: 1,
306+
}
307+
gotErr := tr.fixTransferEncoding()
308+
if !reflect.DeepEqual(gotErr, tt.wantErr) {
309+
t.Errorf("%d.\ngot error:\n%v\nwant error:\n%v\n\n", i, gotErr, tt.wantErr)
310+
}
311+
}
312+
}

0 commit comments

Comments
 (0)