2016-03-01 22:57:46 +00:00
|
|
|
// Copyright 2010 The Go Authors. All rights reserved.
|
2010-01-19 17:46:56 -08:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package http
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"bytes"
|
2011-04-27 14:23:25 -07:00
|
|
|
"compress/gzip"
|
|
|
|
|
"crypto/rand"
|
2010-01-19 17:46:56 -08:00
|
|
|
"fmt"
|
all: clean up code with token.IsExported
A handful of packages were reimplementing IsExported, so use
token.IsExported instead. This caused the deps test to fail for net/rpc.
However, net/rpc deals with Go types, and go/token is light and fairly
low-level in terms of Go tooling packages, so that's okay.
While at it, replace all uses of ast.IsExported with token.IsExported.
This is more consistent, and also means that the import graphs are
leaner. A couple of files no longer need to import go/ast, for example.
We can't get rid of cmd/compile/internal/types.IsExported, as the
compiler can only depend on go/token as of Go 1.4. However, gc used
different implementations in a couple of places, so consolidate the use
of types.IsExported there.
Finally, we can't get rid of the copied IsExported implementation in
encoding/gob, as go/token depends on it as part of a test. That test
can't be an external test either, so there's no easy way to break the
import cycle.
Overall, this removes about forty lines of unnecessary code.
Change-Id: I86a475b7614261e6a7b0b153d5ca02b9f64a7b2d
Reviewed-on: https://go-review.googlesource.com/c/go/+/172037
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-04-15 23:10:50 +09:00
|
|
|
"go/token"
|
2010-01-19 17:46:56 -08:00
|
|
|
"io"
|
2011-04-26 12:32:59 -07:00
|
|
|
"io/ioutil"
|
2014-07-21 12:18:14 -07:00
|
|
|
"net/http/internal"
|
2011-11-08 15:41:54 -08:00
|
|
|
"net/url"
|
2010-01-19 17:46:56 -08:00
|
|
|
"reflect"
|
2014-01-07 10:40:56 -08:00
|
|
|
"regexp"
|
2012-05-21 11:07:27 -07:00
|
|
|
"strings"
|
2010-01-19 17:46:56 -08:00
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type respTest struct {
|
|
|
|
|
Raw string
|
|
|
|
|
Resp Response
|
|
|
|
|
Body string
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-13 07:31:24 -07:00
|
|
|
func dummyReq(method string) *Request {
|
|
|
|
|
return &Request{Method: method}
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-10 17:12:31 -07:00
|
|
|
func dummyReq11(method string) *Request {
|
|
|
|
|
return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
|
|
|
|
|
}
|
|
|
|
|
|
2010-01-19 17:46:56 -08:00
|
|
|
var respTests = []respTest{
|
|
|
|
|
// Unchunked response without Content-Length.
|
2010-10-22 10:06:33 -07:00
|
|
|
{
|
2010-01-19 17:46:56 -08:00
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2011-05-13 07:31:24 -07:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{
|
|
|
|
|
"Connection": {"close"}, // TODO(rsc): Delete?
|
2010-01-19 17:46:56 -08:00
|
|
|
},
|
2010-03-02 13:46:51 -08:00
|
|
|
Close: true,
|
2010-01-19 17:46:56 -08:00
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
|
|
|
|
|
2011-02-08 20:35:02 -08:00
|
|
|
// Unchunked HTTP/1.1 response without Content-Length or
|
|
|
|
|
// Connection headers.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
2011-11-15 14:04:58 +09:00
|
|
|
Header: Header{},
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-08 20:35:02 -08:00
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Unchunked HTTP/1.1 204 response without Content-Length.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 204 No Content\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body should not be read!\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "204 No Content",
|
|
|
|
|
StatusCode: 204,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
2011-11-15 14:04:58 +09:00
|
|
|
Header: Header{},
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-08 20:35:02 -08:00
|
|
|
Close: false,
|
|
|
|
|
ContentLength: 0,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
2010-01-19 17:46:56 -08:00
|
|
|
// Unchunked response with Content-Length.
|
2010-10-22 10:06:33 -07:00
|
|
|
{
|
2010-01-19 17:46:56 -08:00
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 10\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2011-05-13 07:31:24 -07:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{
|
2013-04-04 13:40:26 -07:00
|
|
|
"Connection": {"close"},
|
|
|
|
|
"Content-Length": {"10"},
|
2010-01-19 17:46:56 -08:00
|
|
|
},
|
2010-03-02 13:46:51 -08:00
|
|
|
Close: true,
|
2010-01-19 17:46:56 -08:00
|
|
|
ContentLength: 10,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Chunked response without Content-Length.
|
2010-10-22 10:06:33 -07:00
|
|
|
{
|
2013-01-25 10:20:19 -08:00
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
2010-01-19 17:46:56 -08:00
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"0a\r\n" +
|
2011-04-26 12:32:59 -07:00
|
|
|
"Body here\n\r\n" +
|
|
|
|
|
"09\r\n" +
|
|
|
|
|
"continued\r\n" +
|
2010-01-19 17:46:56 -08:00
|
|
|
"0\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2010-03-02 13:46:51 -08:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
2013-01-25 10:20:19 -08:00
|
|
|
Proto: "HTTP/1.1",
|
2010-03-02 13:46:51 -08:00
|
|
|
ProtoMajor: 1,
|
2013-01-25 10:20:19 -08:00
|
|
|
ProtoMinor: 1,
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{},
|
2013-01-25 10:20:19 -08:00
|
|
|
Close: false,
|
2010-03-02 13:46:51 -08:00
|
|
|
ContentLength: -1,
|
2010-01-19 17:46:56 -08:00
|
|
|
TransferEncoding: []string{"chunked"},
|
|
|
|
|
},
|
|
|
|
|
|
2011-04-26 12:32:59 -07:00
|
|
|
"Body here\ncontinued",
|
2010-01-19 17:46:56 -08:00
|
|
|
},
|
|
|
|
|
|
2018-10-29 11:45:16 +01:00
|
|
|
// Trailer header but no TransferEncoding
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Trailer: Content-MD5, Content-Sources\r\n" +
|
|
|
|
|
"Content-Length: 10\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Connection": {"close"},
|
|
|
|
|
"Content-Length": {"10"},
|
|
|
|
|
"Trailer": []string{"Content-MD5, Content-Sources"},
|
|
|
|
|
},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: 10,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
|
|
|
|
|
2010-01-19 17:46:56 -08:00
|
|
|
// Chunked response with Content-Length.
|
2010-10-22 10:06:33 -07:00
|
|
|
{
|
2013-01-25 10:20:19 -08:00
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
2010-01-19 17:46:56 -08:00
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
|
"Content-Length: 10\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"0a\r\n" +
|
2013-02-28 09:29:50 -08:00
|
|
|
"Body here\n\r\n" +
|
2010-01-19 17:46:56 -08:00
|
|
|
"0\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2010-03-02 13:46:51 -08:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
2013-01-25 10:20:19 -08:00
|
|
|
Proto: "HTTP/1.1",
|
2010-03-02 13:46:51 -08:00
|
|
|
ProtoMajor: 1,
|
2013-01-25 10:20:19 -08:00
|
|
|
ProtoMinor: 1,
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{},
|
2013-01-25 10:20:19 -08:00
|
|
|
Close: false,
|
2013-04-04 13:40:26 -07:00
|
|
|
ContentLength: -1,
|
2010-01-19 17:46:56 -08:00
|
|
|
TransferEncoding: []string{"chunked"},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
2011-01-05 13:09:38 -05:00
|
|
|
|
2013-01-25 10:20:19 -08:00
|
|
|
// Chunked response in response to a HEAD request
|
2011-04-04 19:43:36 -07:00
|
|
|
{
|
2013-01-25 10:20:19 -08:00
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
2011-04-04 19:43:36 -07:00
|
|
|
"Transfer-Encoding: chunked\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2013-01-25 10:20:19 -08:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{},
|
|
|
|
|
TransferEncoding: []string{"chunked"},
|
|
|
|
|
Close: false,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Content-Length in response to a HEAD request
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 256\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{"Content-Length": {"256"}},
|
|
|
|
|
TransferEncoding: nil,
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: 256,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Content-Length in response to a HEAD request with HTTP/1.1
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 256\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{"Content-Length": {"256"}},
|
|
|
|
|
TransferEncoding: nil,
|
|
|
|
|
Close: false,
|
|
|
|
|
ContentLength: 256,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// No Content-Length or Chunked in response to a HEAD request
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{},
|
|
|
|
|
TransferEncoding: nil,
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
2011-04-04 19:43:36 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
2011-04-26 12:32:59 -07:00
|
|
|
// explicit Content-Length of 0.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 0\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
2011-05-13 07:31:24 -07:00
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("GET"),
|
2011-04-26 12:32:59 -07:00
|
|
|
Header: Header{
|
|
|
|
|
"Content-Length": {"0"},
|
|
|
|
|
},
|
|
|
|
|
Close: false,
|
|
|
|
|
ContentLength: 0,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
2011-01-05 13:09:38 -05:00
|
|
|
// Status line without a Reason-Phrase, but trailing space.
|
2018-02-13 22:03:05 +01:00
|
|
|
// (permitted by RFC 7230, section 3.1.2)
|
2011-01-05 13:09:38 -05:00
|
|
|
{
|
|
|
|
|
"HTTP/1.0 303 \r\n\r\n",
|
|
|
|
|
Response{
|
|
|
|
|
Status: "303 ",
|
|
|
|
|
StatusCode: 303,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{},
|
2011-01-05 13:09:38 -05:00
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Status line without a Reason-Phrase, and no trailing space.
|
2018-02-13 22:03:05 +01:00
|
|
|
// (not permitted by RFC 7230, but we'll accept it anyway)
|
2011-01-05 13:09:38 -05:00
|
|
|
{
|
|
|
|
|
"HTTP/1.0 303\r\n\r\n",
|
|
|
|
|
Response{
|
2017-04-17 11:58:30 -07:00
|
|
|
Status: "303",
|
2011-01-05 13:09:38 -05:00
|
|
|
StatusCode: 303,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
2011-05-13 07:31:24 -07:00
|
|
|
Request: dummyReq("GET"),
|
2011-02-23 00:39:25 -05:00
|
|
|
Header: Header{},
|
2011-01-05 13:09:38 -05:00
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
2013-02-28 16:58:26 -08:00
|
|
|
|
|
|
|
|
// golang.org/issue/4767: don't special-case multipart/byteranges responses
|
|
|
|
|
{
|
|
|
|
|
`HTTP/1.1 206 Partial Content
|
|
|
|
|
Connection: close
|
|
|
|
|
Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
|
|
|
|
|
|
|
|
|
|
some body`,
|
|
|
|
|
Response{
|
|
|
|
|
Status: "206 Partial Content",
|
|
|
|
|
StatusCode: 206,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
|
|
|
|
|
},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"some body",
|
|
|
|
|
},
|
2013-08-09 15:11:03 -07:00
|
|
|
|
|
|
|
|
// Unchunked response without Content-Length, Request is nil
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Connection": {"close"}, // TODO(rsc): Delete?
|
|
|
|
|
},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
net/http: don't send implicit gzip Accept-Encoding on Range requests
The http package by default adds "Accept-Encoding: gzip" to outgoing
requests, unless it's a bad idea, or the user requested otherwise.
Only when the http package adds its own implicit Accept-Encoding header
does the http package also transparently un-gzip the response.
If the user requested part of a document (e.g. bytes 40 to 50), it appears
that Github/Varnish send:
range(gzip(content), 40, 50)
And not:
gzip(range(content, 40, 50))
The RFC 2616 set of replacements (with the purpose of
clarifying ambiguities since 1999) has an RFC about Range
requests (http://tools.ietf.org/html/rfc7233) but does not
mention the interaction with encodings.
Regardless of whether range(gzip(content)) or gzip(range(content)) is
correct, this change prevents the Go package from asking for gzip
in requests if we're also asking for Range, avoiding the issue.
If the user cared, they can do it themselves. But Go transparently
un-gzipping a fragment of gzip is never useful.
Fixes #8923
LGTM=adg
R=adg
CC=golang-codereviews
https://golang.org/cl/155420044
2014-10-15 17:51:30 +02:00
|
|
|
|
|
|
|
|
// 206 Partial Content. golang.org/issue/8923
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 206 Partial Content\r\n" +
|
|
|
|
|
"Content-Type: text/plain; charset=utf-8\r\n" +
|
|
|
|
|
"Accept-Ranges: bytes\r\n" +
|
|
|
|
|
"Content-Range: bytes 0-5/1862\r\n" +
|
|
|
|
|
"Content-Length: 6\r\n\r\n" +
|
|
|
|
|
"foobar",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "206 Partial Content",
|
|
|
|
|
StatusCode: 206,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Accept-Ranges": []string{"bytes"},
|
|
|
|
|
"Content-Length": []string{"6"},
|
|
|
|
|
"Content-Type": []string{"text/plain; charset=utf-8"},
|
|
|
|
|
"Content-Range": []string{"bytes 0-5/1862"},
|
|
|
|
|
},
|
|
|
|
|
ContentLength: 6,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"foobar",
|
|
|
|
|
},
|
2015-04-28 13:10:25 -07:00
|
|
|
|
|
|
|
|
// Both keep-alive and close, on the same Connection line. (Issue 8840)
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 256\r\n" +
|
|
|
|
|
"Connection: keep-alive, close\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Length": {"256"},
|
|
|
|
|
},
|
|
|
|
|
TransferEncoding: nil,
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: 256,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Both keep-alive and close, on different Connection lines. (Issue 8840)
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 256\r\n" +
|
|
|
|
|
"Connection: keep-alive\r\n" +
|
|
|
|
|
"Connection: close\r\n" +
|
|
|
|
|
"\r\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("HEAD"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Length": {"256"},
|
|
|
|
|
},
|
|
|
|
|
TransferEncoding: nil,
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: 256,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"",
|
|
|
|
|
},
|
2015-10-13 00:29:44 +00:00
|
|
|
|
|
|
|
|
// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
|
|
|
|
|
// Without a Content-Length.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Transfer-Encoding: bogus\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
|
|
|
|
|
// With a Content-Length.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 200 OK\r\n" +
|
|
|
|
|
"Transfer-Encoding: bogus\r\n" +
|
|
|
|
|
"Content-Length: 10\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
"Body here\n",
|
|
|
|
|
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Length": {"10"},
|
|
|
|
|
},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: 10,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"Body here\n",
|
|
|
|
|
},
|
2016-04-16 09:35:32 -07:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Encoding: gzip\r\n" +
|
|
|
|
|
"Content-Length: 23\r\n" +
|
|
|
|
|
"Connection: keep-alive\r\n" +
|
|
|
|
|
"Keep-Alive: timeout=7200\r\n\r\n" +
|
|
|
|
|
"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
|
|
|
|
|
Response{
|
|
|
|
|
Status: "200 OK",
|
|
|
|
|
StatusCode: 200,
|
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 1,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Length": {"23"},
|
|
|
|
|
"Content-Encoding": {"gzip"},
|
|
|
|
|
"Connection": {"keep-alive"},
|
|
|
|
|
"Keep-Alive": {"timeout=7200"},
|
|
|
|
|
},
|
|
|
|
|
Close: false,
|
|
|
|
|
ContentLength: 23,
|
|
|
|
|
},
|
|
|
|
|
"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
|
|
|
|
|
},
|
2017-04-17 11:58:30 -07:00
|
|
|
|
|
|
|
|
// Issue 19989: two spaces between HTTP version and status.
|
|
|
|
|
{
|
|
|
|
|
"HTTP/1.0 401 Unauthorized\r\n" +
|
|
|
|
|
"Content-type: text/html\r\n" +
|
|
|
|
|
"WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
|
|
|
|
|
"Your Authentication failed.\r\n",
|
|
|
|
|
Response{
|
|
|
|
|
Status: "401 Unauthorized",
|
|
|
|
|
StatusCode: 401,
|
|
|
|
|
Proto: "HTTP/1.0",
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 0,
|
|
|
|
|
Request: dummyReq("GET"),
|
|
|
|
|
Header: Header{
|
|
|
|
|
"Content-Type": {"text/html"},
|
|
|
|
|
"Www-Authenticate": {`Basic realm=""`},
|
|
|
|
|
},
|
|
|
|
|
Close: true,
|
|
|
|
|
ContentLength: -1,
|
|
|
|
|
},
|
|
|
|
|
"Your Authentication failed.\r\n",
|
|
|
|
|
},
|
2010-01-19 17:46:56 -08:00
|
|
|
}
|
|
|
|
|
|
2016-01-03 07:32:52 -07:00
|
|
|
// tests successful calls to ReadResponse, and inspects the returned Response.
|
|
|
|
|
// For error cases, see TestReadResponseErrors below.
|
2010-01-19 17:46:56 -08:00
|
|
|
func TestReadResponse(t *testing.T) {
|
2013-02-28 09:29:50 -08:00
|
|
|
for i, tt := range respTests {
|
|
|
|
|
resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
|
2010-01-19 17:46:56 -08:00
|
|
|
if err != nil {
|
2013-02-28 09:29:50 -08:00
|
|
|
t.Errorf("#%d: %v", i, err)
|
2010-01-19 17:46:56 -08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
rbody := resp.Body
|
|
|
|
|
resp.Body = nil
|
|
|
|
|
diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
|
|
|
|
|
var bout bytes.Buffer
|
|
|
|
|
if rbody != nil {
|
2013-02-28 09:29:50 -08:00
|
|
|
_, err = io.Copy(&bout, rbody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("#%d: %v", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2010-01-19 17:46:56 -08:00
|
|
|
rbody.Close()
|
|
|
|
|
}
|
|
|
|
|
body := bout.String()
|
|
|
|
|
if body != tt.Body {
|
|
|
|
|
t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-28 09:29:50 -08:00
|
|
|
func TestWriteResponse(t *testing.T) {
|
|
|
|
|
for i, tt := range respTests {
|
|
|
|
|
resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("#%d: %v", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2014-01-07 10:40:56 -08:00
|
|
|
err = resp.Write(ioutil.Discard)
|
2013-02-28 09:29:50 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("#%d: %v", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 14:23:25 -07:00
|
|
|
var readResponseCloseInMiddleTests = []struct {
|
|
|
|
|
chunked, compressed bool
|
|
|
|
|
}{
|
|
|
|
|
{false, false},
|
|
|
|
|
{true, false},
|
|
|
|
|
{true, true},
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-02 14:17:23 +02:00
|
|
|
type readerAndCloser struct {
|
|
|
|
|
io.Reader
|
|
|
|
|
io.Closer
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 14:23:25 -07:00
|
|
|
// TestReadResponseCloseInMiddle tests that closing a body after
|
|
|
|
|
// reading only part of its contents advances the read to the end of
|
|
|
|
|
// the request, right up until the next request.
|
2011-04-26 12:32:59 -07:00
|
|
|
func TestReadResponseCloseInMiddle(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
t.Parallel()
|
2011-04-27 14:23:25 -07:00
|
|
|
for _, test := range readResponseCloseInMiddleTests {
|
|
|
|
|
fatalf := func(format string, args ...interface{}) {
|
|
|
|
|
args = append([]interface{}{test.chunked, test.compressed}, args...)
|
|
|
|
|
t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
|
|
|
|
|
}
|
2011-11-01 22:04:37 -04:00
|
|
|
checkErr := func(err error, msg string) {
|
2011-04-27 14:23:25 -07:00
|
|
|
if err == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
fatalf(msg+": %v", err)
|
|
|
|
|
}
|
2011-04-26 12:32:59 -07:00
|
|
|
var buf bytes.Buffer
|
|
|
|
|
buf.WriteString("HTTP/1.1 200 OK\r\n")
|
2011-04-27 14:23:25 -07:00
|
|
|
if test.chunked {
|
|
|
|
|
buf.WriteString("Transfer-Encoding: chunked\r\n")
|
2011-04-26 12:32:59 -07:00
|
|
|
} else {
|
2011-04-27 14:23:25 -07:00
|
|
|
buf.WriteString("Content-Length: 1000000\r\n")
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
2011-04-27 14:23:25 -07:00
|
|
|
var wr io.Writer = &buf
|
|
|
|
|
if test.chunked {
|
2014-07-21 12:18:14 -07:00
|
|
|
wr = internal.NewChunkedWriter(wr)
|
2011-04-27 14:23:25 -07:00
|
|
|
}
|
|
|
|
|
if test.compressed {
|
|
|
|
|
buf.WriteString("Content-Encoding: gzip\r\n")
|
2012-02-10 18:49:19 +11:00
|
|
|
wr = gzip.NewWriter(wr)
|
2011-04-27 14:23:25 -07:00
|
|
|
}
|
|
|
|
|
buf.WriteString("\r\n")
|
|
|
|
|
|
|
|
|
|
chunk := bytes.Repeat([]byte{'x'}, 1000)
|
2011-04-26 12:32:59 -07:00
|
|
|
for i := 0; i < 1000; i++ {
|
2011-04-27 14:23:25 -07:00
|
|
|
if test.compressed {
|
|
|
|
|
// Otherwise this compresses too well.
|
|
|
|
|
_, err := io.ReadFull(rand.Reader, chunk)
|
|
|
|
|
checkErr(err, "rand.Reader ReadFull")
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
2011-04-27 14:23:25 -07:00
|
|
|
wr.Write(chunk)
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
2011-04-27 14:23:25 -07:00
|
|
|
if test.compressed {
|
2012-02-10 18:49:19 +11:00
|
|
|
err := wr.(*gzip.Writer).Close()
|
2011-04-27 14:23:25 -07:00
|
|
|
checkErr(err, "compressor close")
|
|
|
|
|
}
|
|
|
|
|
if test.chunked {
|
2011-04-26 12:32:59 -07:00
|
|
|
buf.WriteString("0\r\n\r\n")
|
|
|
|
|
}
|
|
|
|
|
buf.WriteString("Next Request Here")
|
2011-04-27 14:23:25 -07:00
|
|
|
|
2011-04-26 12:32:59 -07:00
|
|
|
bufr := bufio.NewReader(&buf)
|
2011-05-13 07:31:24 -07:00
|
|
|
resp, err := ReadResponse(bufr, dummyReq("GET"))
|
2011-04-27 14:23:25 -07:00
|
|
|
checkErr(err, "ReadResponse")
|
2011-04-26 12:32:59 -07:00
|
|
|
expectedLength := int64(-1)
|
2011-04-27 14:23:25 -07:00
|
|
|
if !test.chunked {
|
2011-04-26 12:32:59 -07:00
|
|
|
expectedLength = 1000000
|
|
|
|
|
}
|
|
|
|
|
if resp.ContentLength != expectedLength {
|
2011-04-27 14:23:25 -07:00
|
|
|
fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
|
|
|
|
|
}
|
|
|
|
|
if resp.Body == nil {
|
|
|
|
|
fatalf("nil body")
|
|
|
|
|
}
|
|
|
|
|
if test.compressed {
|
|
|
|
|
gzReader, err := gzip.NewReader(resp.Body)
|
|
|
|
|
checkErr(err, "gzip.NewReader")
|
2013-03-05 18:47:27 -08:00
|
|
|
resp.Body = &readerAndCloser{gzReader, resp.Body}
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
2011-04-27 14:23:25 -07:00
|
|
|
|
2011-04-26 12:32:59 -07:00
|
|
|
rbuf := make([]byte, 2500)
|
|
|
|
|
n, err := io.ReadFull(resp.Body, rbuf)
|
2011-04-27 14:23:25 -07:00
|
|
|
checkErr(err, "2500 byte ReadFull")
|
2011-04-26 12:32:59 -07:00
|
|
|
if n != 2500 {
|
2011-04-27 14:23:25 -07:00
|
|
|
fatalf("ReadFull only read %d bytes", n)
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
2011-04-27 14:23:25 -07:00
|
|
|
if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
|
|
|
|
|
fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
rest, err := ioutil.ReadAll(bufr)
|
2011-04-27 14:23:25 -07:00
|
|
|
checkErr(err, "ReadAll on remainder")
|
2011-04-26 12:32:59 -07:00
|
|
|
if e, g := "Next Request Here", string(rest); e != g {
|
2014-01-07 10:40:56 -08:00
|
|
|
g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
|
|
|
|
|
return fmt.Sprintf("x(repeated x%d)", len(match))
|
|
|
|
|
})
|
2011-05-14 20:43:18 -07:00
|
|
|
fatalf("remainder = %q, expected %q", g, e)
|
2011-04-26 12:32:59 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-01-19 17:46:56 -08:00
|
|
|
func diff(t *testing.T, prefix string, have, want interface{}) {
|
2020-05-01 00:58:55 -04:00
|
|
|
t.Helper()
|
2011-04-25 13:39:36 -04:00
|
|
|
hv := reflect.ValueOf(have).Elem()
|
|
|
|
|
wv := reflect.ValueOf(want).Elem()
|
2010-01-19 17:46:56 -08:00
|
|
|
if hv.Type() != wv.Type() {
|
2011-05-14 20:43:18 -07:00
|
|
|
t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
|
2010-01-19 17:46:56 -08:00
|
|
|
}
|
|
|
|
|
for i := 0; i < hv.NumField(); i++ {
|
2016-04-04 13:31:08 -07:00
|
|
|
name := hv.Type().Field(i).Name
|
all: clean up code with token.IsExported
A handful of packages were reimplementing IsExported, so use
token.IsExported instead. This caused the deps test to fail for net/rpc.
However, net/rpc deals with Go types, and go/token is light and fairly
low-level in terms of Go tooling packages, so that's okay.
While at it, replace all uses of ast.IsExported with token.IsExported.
This is more consistent, and also means that the import graphs are
leaner. A couple of files no longer need to import go/ast, for example.
We can't get rid of cmd/compile/internal/types.IsExported, as the
compiler can only depend on go/token as of Go 1.4. However, gc used
different implementations in a couple of places, so consolidate the use
of types.IsExported there.
Finally, we can't get rid of the copied IsExported implementation in
encoding/gob, as go/token depends on it as part of a test. That test
can't be an external test either, so there's no easy way to break the
import cycle.
Overall, this removes about forty lines of unnecessary code.
Change-Id: I86a475b7614261e6a7b0b153d5ca02b9f64a7b2d
Reviewed-on: https://go-review.googlesource.com/c/go/+/172037
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-04-15 23:10:50 +09:00
|
|
|
if !token.IsExported(name) {
|
2016-04-04 13:31:08 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2010-01-19 17:46:56 -08:00
|
|
|
hf := hv.Field(i).Interface()
|
|
|
|
|
wf := wv.Field(i).Interface()
|
|
|
|
|
if !reflect.DeepEqual(hf, wf) {
|
2016-04-04 13:31:08 -07:00
|
|
|
t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
|
2010-01-19 17:46:56 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-09-23 10:57:31 -07:00
|
|
|
|
|
|
|
|
type responseLocationTest struct {
|
|
|
|
|
location string // Response's Location header or ""
|
|
|
|
|
requrl string // Response.Request.URL or ""
|
|
|
|
|
want string
|
2011-11-01 22:04:37 -04:00
|
|
|
wantErr error
|
2011-09-23 10:57:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var responseLocationTests = []responseLocationTest{
|
|
|
|
|
{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
|
|
|
|
|
{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
|
|
|
|
|
{"", "http://bar.com/baz", "", ErrNoLocation},
|
2016-01-03 07:32:52 -07:00
|
|
|
{"/bar", "", "/bar", nil},
|
2011-09-23 10:57:31 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLocationResponse(t *testing.T) {
|
|
|
|
|
for i, tt := range responseLocationTests {
|
|
|
|
|
res := new(Response)
|
|
|
|
|
res.Header = make(Header)
|
|
|
|
|
res.Header.Set("Location", tt.location)
|
|
|
|
|
if tt.requrl != "" {
|
|
|
|
|
res.Request = &Request{}
|
2011-11-01 22:04:37 -04:00
|
|
|
var err error
|
2011-09-23 10:57:31 -07:00
|
|
|
res.Request.URL, err = url.Parse(tt.requrl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("bad test URL %q: %v", tt.requrl, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got, err := res.Location()
|
|
|
|
|
if tt.wantErr != nil {
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2011-11-01 22:04:37 -04:00
|
|
|
if g, e := err.Error(), tt.wantErr.Error(); g != e {
|
2011-09-23 10:57:31 -07:00
|
|
|
t.Errorf("%d. err=%q; want %q", i, g, e)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("%d. err=%q", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if g, e := got.String(), tt.want; g != e {
|
|
|
|
|
t.Errorf("%d. Location=%q; want %q", i, g, e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-05-21 11:07:27 -07:00
|
|
|
|
|
|
|
|
func TestResponseStatusStutter(t *testing.T) {
|
|
|
|
|
r := &Response{
|
|
|
|
|
Status: "123 some status",
|
|
|
|
|
StatusCode: 123,
|
|
|
|
|
ProtoMajor: 1,
|
|
|
|
|
ProtoMinor: 3,
|
|
|
|
|
}
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
r.Write(&buf)
|
|
|
|
|
if strings.Contains(buf.String(), "123 123") {
|
|
|
|
|
t.Errorf("stutter in status: %s", buf.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-06-24 13:27:56 -07:00
|
|
|
|
|
|
|
|
func TestResponseContentLengthShortBody(t *testing.T) {
|
|
|
|
|
const shortBody = "Short body, not 123 bytes."
|
|
|
|
|
br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
|
|
|
|
|
"Content-Length: 123\r\n" +
|
|
|
|
|
"\r\n" +
|
|
|
|
|
shortBody))
|
|
|
|
|
res, err := ReadResponse(br, &Request{Method: "GET"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if res.ContentLength != 123 {
|
|
|
|
|
t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
|
|
|
|
|
}
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
n, err := io.Copy(&buf, res.Body)
|
|
|
|
|
if n != int64(len(shortBody)) {
|
|
|
|
|
t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
|
|
|
|
|
}
|
|
|
|
|
if buf.String() != shortBody {
|
|
|
|
|
t.Errorf("Read body %q; want %q", buf.String(), shortBody)
|
|
|
|
|
}
|
|
|
|
|
if err != io.ErrUnexpectedEOF {
|
|
|
|
|
t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-10-15 08:22:04 +11:00
|
|
|
|
2016-01-03 07:32:52 -07:00
|
|
|
// Test various ReadResponse error cases. (also tests success cases, but mostly
|
|
|
|
|
// it's about errors). This does not test anything involving the bodies. Only
|
|
|
|
|
// the return value from ReadResponse itself.
|
|
|
|
|
func TestReadResponseErrors(t *testing.T) {
|
|
|
|
|
type testCase struct {
|
|
|
|
|
name string // optional, defaults to in
|
|
|
|
|
in string
|
|
|
|
|
wantErr interface{} // nil, err value, or string substring
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status := func(s string, wantErr interface{}) testCase {
|
|
|
|
|
if wantErr == true {
|
|
|
|
|
wantErr = "malformed HTTP status code"
|
|
|
|
|
}
|
|
|
|
|
return testCase{
|
|
|
|
|
name: fmt.Sprintf("status %q", s),
|
|
|
|
|
in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
|
|
|
|
|
wantErr: wantErr,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
version := func(s string, wantErr interface{}) testCase {
|
|
|
|
|
if wantErr == true {
|
|
|
|
|
wantErr = "malformed HTTP version"
|
|
|
|
|
}
|
|
|
|
|
return testCase{
|
|
|
|
|
name: fmt.Sprintf("version %q", s),
|
|
|
|
|
in: s + " 200 OK\r\n\r\n",
|
|
|
|
|
wantErr: wantErr,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
net/textproto: reject all headers with a leading space
Previously, golang.org/cl/75350 updated ReadMIMEHeader to ignore the
first header line when it begins with a leading space, as in the
following example:
GET / HTTP/1.1
Host: foo.com
Accept-Encoding: gzip
However, golang.org/cl/75350 changed ReadMIMEHeader's behavior for the
following example: before the CL it returned an error, but after the
CL it ignored the first line.
GET / HTTP/1.1
Host foo.com
Accept-Encoding: gzip
This change updates ReadMIMEHeader to always fail when the first header
line starts with a space. During the discussion for golang.org/cl/75350,
we realized we had three competing needs:
1. HTTP clients should accept malformed response headers when possible
(ignoring the malformed lines).
2. HTTP servers should reject all malformed request headers.
3. The net/textproto package is used by multiple protocols (most notably,
HTTP and SMTP) which have slightly different parsing semantics. This
complicates changes to net/textproto.
We weren't sure how to best fix net/textproto without an API change, but
it is too late for API changes in Go 1.10. We decided to ignore initial
lines that begin with spaces, thinking that would have the least impact on
existing users -- malformed headers would continue to parse, but the
initial lines would be ignored. Instead, golang.org/cl/75350 actually
changed ReadMIMEHeader to succeed in cases where it previously failed
(as in the above example).
Reconsidering the above two examples, there does not seem to be a good
argument to silently ignore ` Host: foo.com` but fail on ` Host foo.com`.
Hence, this change fails for *all* headers where the initial line begins
with a space.
Updates #22464
Change-Id: I68d3d190489c350b0bc1549735bf6593fe11a94c
Reviewed-on: https://go-review.googlesource.com/80055
Run-TryBot: Tom Bergan <tombergan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-11-27 11:25:14 -08:00
|
|
|
contentLength := func(status, body string, wantErr interface{}) testCase {
|
2016-10-16 05:00:27 -07:00
|
|
|
return testCase{
|
|
|
|
|
name: fmt.Sprintf("status %q %q", status, body),
|
|
|
|
|
in: fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
|
|
|
|
|
wantErr: wantErr,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errMultiCL := "message cannot contain multiple Content-Length headers"
|
|
|
|
|
|
2016-01-03 07:32:52 -07:00
|
|
|
tests := []testCase{
|
net/textproto: reject all headers with a leading space
Previously, golang.org/cl/75350 updated ReadMIMEHeader to ignore the
first header line when it begins with a leading space, as in the
following example:
GET / HTTP/1.1
Host: foo.com
Accept-Encoding: gzip
However, golang.org/cl/75350 changed ReadMIMEHeader's behavior for the
following example: before the CL it returned an error, but after the
CL it ignored the first line.
GET / HTTP/1.1
Host foo.com
Accept-Encoding: gzip
This change updates ReadMIMEHeader to always fail when the first header
line starts with a space. During the discussion for golang.org/cl/75350,
we realized we had three competing needs:
1. HTTP clients should accept malformed response headers when possible
(ignoring the malformed lines).
2. HTTP servers should reject all malformed request headers.
3. The net/textproto package is used by multiple protocols (most notably,
HTTP and SMTP) which have slightly different parsing semantics. This
complicates changes to net/textproto.
We weren't sure how to best fix net/textproto without an API change, but
it is too late for API changes in Go 1.10. We decided to ignore initial
lines that begin with spaces, thinking that would have the least impact on
existing users -- malformed headers would continue to parse, but the
initial lines would be ignored. Instead, golang.org/cl/75350 actually
changed ReadMIMEHeader to succeed in cases where it previously failed
(as in the above example).
Reconsidering the above two examples, there does not seem to be a good
argument to silently ignore ` Host: foo.com` but fail on ` Host foo.com`.
Hence, this change fails for *all* headers where the initial line begins
with a space.
Updates #22464
Change-Id: I68d3d190489c350b0bc1549735bf6593fe11a94c
Reviewed-on: https://go-review.googlesource.com/80055
Run-TryBot: Tom Bergan <tombergan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-11-27 11:25:14 -08:00
|
|
|
{"", "", io.ErrUnexpectedEOF},
|
|
|
|
|
{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF},
|
|
|
|
|
{"", "HTTP/1.1", "malformed HTTP response"},
|
|
|
|
|
{"", "HTTP/2.0", "malformed HTTP response"},
|
2016-01-03 07:32:52 -07:00
|
|
|
status("20X Unknown", true),
|
|
|
|
|
status("abcd Unknown", true),
|
|
|
|
|
status("二百/两百 OK", true),
|
|
|
|
|
status(" Unknown", true),
|
|
|
|
|
status("c8 OK", true),
|
|
|
|
|
status("0x12d Moved Permanently", true),
|
|
|
|
|
status("200 OK", nil),
|
2016-01-10 23:20:06 -07:00
|
|
|
status("000 OK", nil),
|
|
|
|
|
status("001 OK", nil),
|
|
|
|
|
status("404 NOTFOUND", nil),
|
|
|
|
|
status("20 OK", true),
|
|
|
|
|
status("00 OK", true),
|
|
|
|
|
status("-10 OK", true),
|
|
|
|
|
status("1000 OK", true),
|
|
|
|
|
status("999 Done", nil),
|
|
|
|
|
status("-1 OK", true),
|
|
|
|
|
status("-200 OK", true),
|
2016-01-03 07:32:52 -07:00
|
|
|
version("HTTP/1.2", nil),
|
|
|
|
|
version("HTTP/2.0", nil),
|
|
|
|
|
version("HTTP/1.100000000002", true),
|
|
|
|
|
version("HTTP/1.-1", true),
|
|
|
|
|
version("HTTP/A.B", true),
|
|
|
|
|
version("HTTP/1", true),
|
|
|
|
|
version("http/1.1", true),
|
2016-10-16 05:00:27 -07:00
|
|
|
|
net/textproto: reject all headers with a leading space
Previously, golang.org/cl/75350 updated ReadMIMEHeader to ignore the
first header line when it begins with a leading space, as in the
following example:
GET / HTTP/1.1
Host: foo.com
Accept-Encoding: gzip
However, golang.org/cl/75350 changed ReadMIMEHeader's behavior for the
following example: before the CL it returned an error, but after the
CL it ignored the first line.
GET / HTTP/1.1
Host foo.com
Accept-Encoding: gzip
This change updates ReadMIMEHeader to always fail when the first header
line starts with a space. During the discussion for golang.org/cl/75350,
we realized we had three competing needs:
1. HTTP clients should accept malformed response headers when possible
(ignoring the malformed lines).
2. HTTP servers should reject all malformed request headers.
3. The net/textproto package is used by multiple protocols (most notably,
HTTP and SMTP) which have slightly different parsing semantics. This
complicates changes to net/textproto.
We weren't sure how to best fix net/textproto without an API change, but
it is too late for API changes in Go 1.10. We decided to ignore initial
lines that begin with spaces, thinking that would have the least impact on
existing users -- malformed headers would continue to parse, but the
initial lines would be ignored. Instead, golang.org/cl/75350 actually
changed ReadMIMEHeader to succeed in cases where it previously failed
(as in the above example).
Reconsidering the above two examples, there does not seem to be a good
argument to silently ignore ` Host: foo.com` but fail on ` Host foo.com`.
Hence, this change fails for *all* headers where the initial line begins
with a space.
Updates #22464
Change-Id: I68d3d190489c350b0bc1549735bf6593fe11a94c
Reviewed-on: https://go-review.googlesource.com/80055
Run-TryBot: Tom Bergan <tombergan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-11-27 11:25:14 -08:00
|
|
|
contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL),
|
|
|
|
|
contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
|
|
|
|
|
contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
|
|
|
|
|
contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
|
|
|
|
|
contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
|
|
|
|
|
contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
|
2016-10-16 05:00:27 -07:00
|
|
|
|
|
|
|
|
// multiple content-length headers for 204 and 304 should still be checked
|
net/textproto: reject all headers with a leading space
Previously, golang.org/cl/75350 updated ReadMIMEHeader to ignore the
first header line when it begins with a leading space, as in the
following example:
GET / HTTP/1.1
Host: foo.com
Accept-Encoding: gzip
However, golang.org/cl/75350 changed ReadMIMEHeader's behavior for the
following example: before the CL it returned an error, but after the
CL it ignored the first line.
GET / HTTP/1.1
Host foo.com
Accept-Encoding: gzip
This change updates ReadMIMEHeader to always fail when the first header
line starts with a space. During the discussion for golang.org/cl/75350,
we realized we had three competing needs:
1. HTTP clients should accept malformed response headers when possible
(ignoring the malformed lines).
2. HTTP servers should reject all malformed request headers.
3. The net/textproto package is used by multiple protocols (most notably,
HTTP and SMTP) which have slightly different parsing semantics. This
complicates changes to net/textproto.
We weren't sure how to best fix net/textproto without an API change, but
it is too late for API changes in Go 1.10. We decided to ignore initial
lines that begin with spaces, thinking that would have the least impact on
existing users -- malformed headers would continue to parse, but the
initial lines would be ignored. Instead, golang.org/cl/75350 actually
changed ReadMIMEHeader to succeed in cases where it previously failed
(as in the above example).
Reconsidering the above two examples, there does not seem to be a good
argument to silently ignore ` Host: foo.com` but fail on ` Host foo.com`.
Hence, this change fails for *all* headers where the initial line begins
with a space.
Updates #22464
Change-Id: I68d3d190489c350b0bc1549735bf6593fe11a94c
Reviewed-on: https://go-review.googlesource.com/80055
Run-TryBot: Tom Bergan <tombergan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-11-27 11:25:14 -08:00
|
|
|
contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL),
|
|
|
|
|
contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil),
|
|
|
|
|
contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL),
|
|
|
|
|
contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil),
|
|
|
|
|
|
|
|
|
|
// golang.org/issue/22464
|
|
|
|
|
{"leading space in header", "HTTP/1.1 200 OK\r\n Content-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
|
|
|
|
|
{"leading tab in header", "HTTP/1.1 200 OK\r\n\tContent-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
|
2016-01-03 07:32:52 -07:00
|
|
|
}
|
2016-10-16 05:00:27 -07:00
|
|
|
|
2016-01-03 07:32:52 -07:00
|
|
|
for i, tt := range tests {
|
|
|
|
|
br := bufio.NewReader(strings.NewReader(tt.in))
|
|
|
|
|
_, rerr := ReadResponse(br, nil)
|
|
|
|
|
if err := matchErr(rerr, tt.wantErr); err != nil {
|
|
|
|
|
name := tt.name
|
|
|
|
|
if name == "" {
|
2016-01-26 16:29:47 +09:00
|
|
|
name = fmt.Sprintf("%d. input %q", i, tt.in)
|
2016-01-03 07:32:52 -07:00
|
|
|
}
|
|
|
|
|
t.Errorf("%s: %v", name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wantErr can be nil, an error value to match exactly, or type string to
|
|
|
|
|
// match a substring.
|
|
|
|
|
func matchErr(err error, wantErr interface{}) error {
|
|
|
|
|
if err == nil {
|
|
|
|
|
if wantErr == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if sub, ok := wantErr.(string); ok {
|
|
|
|
|
return fmt.Errorf("unexpected success; want error with substring %q", sub)
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("unexpected success; want error %v", wantErr)
|
|
|
|
|
}
|
|
|
|
|
if wantErr == nil {
|
|
|
|
|
return fmt.Errorf("%v; want success", err)
|
|
|
|
|
}
|
|
|
|
|
if sub, ok := wantErr.(string); ok {
|
|
|
|
|
if strings.Contains(err.Error(), sub) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
|
|
|
|
|
}
|
|
|
|
|
if err == wantErr {
|
|
|
|
|
return nil
|
2014-01-14 19:08:40 -08:00
|
|
|
}
|
2016-01-03 07:32:52 -07:00
|
|
|
return fmt.Errorf("%v; want %v", err, wantErr)
|
2014-01-14 19:08:40 -08:00
|
|
|
}
|
|
|
|
|
|
2013-10-15 08:22:04 +11:00
|
|
|
func TestNeedsSniff(t *testing.T) {
|
|
|
|
|
// needsSniff returns true with an empty response.
|
|
|
|
|
r := &response{}
|
|
|
|
|
if got, want := r.needsSniff(), true; got != want {
|
|
|
|
|
t.Errorf("needsSniff = %t; want %t", got, want)
|
|
|
|
|
}
|
|
|
|
|
// needsSniff returns false when Content-Type = nil.
|
|
|
|
|
r.handlerHeader = Header{"Content-Type": nil}
|
|
|
|
|
if got, want := r.needsSniff(), false; got != want {
|
|
|
|
|
t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-11 13:38:14 -08:00
|
|
|
|
|
|
|
|
// A response should only write out single Connection: close header. Tests #19499.
|
|
|
|
|
func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
|
|
|
|
|
const connectionCloseHeader = "Connection: close"
|
|
|
|
|
|
|
|
|
|
res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ReadResponse failed %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf1 bytes.Buffer
|
|
|
|
|
if err = res.Write(&buf1); err != nil {
|
|
|
|
|
t.Fatalf("Write failed %v", err)
|
|
|
|
|
}
|
|
|
|
|
if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
|
|
|
|
|
t.Fatalf("ReadResponse failed %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buf2 bytes.Buffer
|
|
|
|
|
if err = res.Write(&buf2); err != nil {
|
|
|
|
|
t.Fatalf("Write failed %v", err)
|
|
|
|
|
}
|
|
|
|
|
if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
|
|
|
|
|
t.Errorf("Found %d %q header", count, connectionCloseHeader)
|
|
|
|
|
}
|
|
|
|
|
}
|