mirror of
https://github.com/golang/go
synced 2024-11-20 07:54:39 -07:00
http: introduce Header type, implement with net/textproto
textproto: introduce Header type websocket: use new interface to access Header R=rsc, mattn CC=golang-dev https://golang.org/cl/4185053
This commit is contained in:
parent
07cc8b9ad2
commit
b8fa61885a
@ -10,6 +10,7 @@ GOFILES=\
|
||||
client.go\
|
||||
dump.go\
|
||||
fs.go\
|
||||
header.go\
|
||||
lex.go\
|
||||
persist.go\
|
||||
request.go\
|
||||
|
@ -85,9 +85,9 @@ func send(req *Request) (resp *Response, err os.Error) {
|
||||
encoded := make([]byte, enc.EncodedLen(len(info)))
|
||||
enc.Encode(encoded, []byte(info))
|
||||
if req.Header == nil {
|
||||
req.Header = make(map[string]string)
|
||||
req.Header = make(Header)
|
||||
}
|
||||
req.Header["Authorization"] = "Basic " + string(encoded)
|
||||
req.Header.Set("Authorization", "Basic "+string(encoded))
|
||||
}
|
||||
|
||||
var proxyURL *URL
|
||||
@ -130,7 +130,7 @@ func send(req *Request) (resp *Response, err os.Error) {
|
||||
if req.URL.Scheme == "http" {
|
||||
// Include proxy http header if needed.
|
||||
if proxyAuth != "" {
|
||||
req.Header["Proxy-Authorization"] = proxyAuth
|
||||
req.Header.Set("Proxy-Authorization", proxyAuth)
|
||||
}
|
||||
} else { // https
|
||||
if proxyURL != nil {
|
||||
@ -241,7 +241,7 @@ func Get(url string) (r *Response, finalURL string, err os.Error) {
|
||||
}
|
||||
if shouldRedirect(r.StatusCode) {
|
||||
r.Body.Close()
|
||||
if url = r.GetHeader("Location"); url == "" {
|
||||
if url = r.Header.Get("Location"); url == "" {
|
||||
err = os.ErrorString(fmt.Sprintf("%d response missing Location header", r.StatusCode))
|
||||
break
|
||||
}
|
||||
@ -266,8 +266,8 @@ func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Erro
|
||||
req.ProtoMinor = 1
|
||||
req.Close = true
|
||||
req.Body = nopCloser{body}
|
||||
req.Header = map[string]string{
|
||||
"Content-Type": bodyType,
|
||||
req.Header = Header{
|
||||
"Content-Type": {bodyType},
|
||||
}
|
||||
req.TransferEncoding = []string{"chunked"}
|
||||
|
||||
@ -291,9 +291,9 @@ func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
|
||||
req.Close = true
|
||||
body := urlencode(data)
|
||||
req.Body = nopCloser{body}
|
||||
req.Header = map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Content-Length": strconv.Itoa(body.Len()),
|
||||
req.Header = Header{
|
||||
"Content-Type": {"application/x-www-form-urlencoded"},
|
||||
"Content-Length": {strconv.Itoa(body.Len())},
|
||||
}
|
||||
req.ContentLength = int64(body.Len())
|
||||
|
||||
|
@ -104,7 +104,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if t, _ := time.Parse(TimeFormat, r.Header["If-Modified-Since"]); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
|
||||
if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
|
||||
w.WriteHeader(StatusNotModified)
|
||||
return
|
||||
}
|
||||
@ -153,7 +153,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
|
||||
|
||||
// handle Content-Range header.
|
||||
// TODO(adg): handle multiple ranges
|
||||
ranges, err := parseRange(r.Header["Range"], size)
|
||||
ranges, err := parseRange(r.Header.Get("Range"), size)
|
||||
if err != nil || len(ranges) > 1 {
|
||||
Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
|
||||
return
|
||||
|
@ -109,7 +109,7 @@ func TestServeFile(t *testing.T) {
|
||||
|
||||
// set up the Request (re-used for all tests)
|
||||
var req Request
|
||||
req.Header = make(map[string]string)
|
||||
req.Header = make(Header)
|
||||
if req.URL, err = ParseURL("http://" + serverAddr + "/ServeFile"); err != nil {
|
||||
t.Fatal("ParseURL:", err)
|
||||
}
|
||||
@ -123,9 +123,9 @@ func TestServeFile(t *testing.T) {
|
||||
|
||||
// Range tests
|
||||
for _, rt := range ServeFileRangeTests {
|
||||
req.Header["Range"] = "bytes=" + rt.r
|
||||
req.Header.Set("Range", "bytes="+rt.r)
|
||||
if rt.r == "" {
|
||||
req.Header["Range"] = ""
|
||||
req.Header["Range"] = nil
|
||||
}
|
||||
r, body := getBody(t, req)
|
||||
if r.StatusCode != rt.code {
|
||||
@ -138,8 +138,9 @@ func TestServeFile(t *testing.T) {
|
||||
if rt.r == "" {
|
||||
h = ""
|
||||
}
|
||||
if r.Header["Content-Range"] != h {
|
||||
t.Errorf("header mismatch: range=%q: got %q, want %q", rt.r, r.Header["Content-Range"], h)
|
||||
cr := r.Header.Get("Content-Range")
|
||||
if cr != h {
|
||||
t.Errorf("header mismatch: range=%q: got %q, want %q", rt.r, cr, h)
|
||||
}
|
||||
if !equal(body, file[rt.start:rt.end]) {
|
||||
t.Errorf("body mismatch: range=%q: got %q, want %q", rt.r, body, file[rt.start:rt.end])
|
||||
|
43
src/pkg/http/header.go
Normal file
43
src/pkg/http/header.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http
|
||||
|
||||
import "net/textproto"
|
||||
|
||||
// A Header represents the key-value pairs in an HTTP header.
|
||||
type Header map[string][]string
|
||||
|
||||
// Add adds the key, value pair to the header.
|
||||
// It appends to any existing values associated with key.
|
||||
func (h Header) Add(key, value string) {
|
||||
textproto.MIMEHeader(h).Add(key, value)
|
||||
}
|
||||
|
||||
// Set sets the header entries associated with key to
|
||||
// the single element value. It replaces any existing
|
||||
// values associated with key.
|
||||
func (h Header) Set(key, value string) {
|
||||
textproto.MIMEHeader(h).Set(key, value)
|
||||
}
|
||||
|
||||
// Get gets the first value associated with the given key.
|
||||
// If there are no values associated with the key, Get returns "".
|
||||
// Get is a convenience method. For more complex queries,
|
||||
// access the map directly.
|
||||
func (h Header) Get(key string) string {
|
||||
return textproto.MIMEHeader(h).Get(key)
|
||||
}
|
||||
|
||||
// Del deletes the values associated with key.
|
||||
func (h Header) Del(key string) {
|
||||
textproto.MIMEHeader(h).Del(key)
|
||||
}
|
||||
|
||||
// CanonicalHeaderKey returns the canonical format of the
|
||||
// header key s. The canonicalization converts the first
|
||||
// letter and any letter following a hyphen to upper case;
|
||||
// the rest are converted to lowercase. For example, the
|
||||
// canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
|
@ -50,14 +50,14 @@ var reqTests = []reqTest{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: map[string]string{
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "en-us,en;q=0.5",
|
||||
"Accept-Encoding": "gzip,deflate",
|
||||
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
|
||||
"Keep-Alive": "300",
|
||||
"Proxy-Connection": "keep-alive",
|
||||
"Content-Length": "7",
|
||||
Header: Header{
|
||||
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
|
||||
"Accept-Language": {"en-us,en;q=0.5"},
|
||||
"Accept-Encoding": {"gzip,deflate"},
|
||||
"Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
|
||||
"Keep-Alive": {"300"},
|
||||
"Proxy-Connection": {"keep-alive"},
|
||||
"Content-Length": {"7"},
|
||||
},
|
||||
Close: false,
|
||||
ContentLength: 7,
|
||||
@ -93,7 +93,7 @@ var reqTests = []reqTest{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Close: false,
|
||||
ContentLength: -1,
|
||||
Host: "test",
|
||||
|
@ -11,13 +11,13 @@ package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"container/vector"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -90,7 +90,7 @@ type Request struct {
|
||||
// The request parser implements this by canonicalizing the
|
||||
// name, making the first character and any characters
|
||||
// following a hyphen uppercase and the rest lowercase.
|
||||
Header map[string]string
|
||||
Header Header
|
||||
|
||||
// The message body.
|
||||
Body io.ReadCloser
|
||||
@ -133,7 +133,7 @@ type Request struct {
|
||||
// Trailer maps trailer keys to values. Like for Header, if the
|
||||
// response has multiple trailer lines with the same key, they will be
|
||||
// concatenated, delimited by commas.
|
||||
Trailer map[string]string
|
||||
Trailer Header
|
||||
}
|
||||
|
||||
// ProtoAtLeast returns whether the HTTP protocol used
|
||||
@ -146,8 +146,8 @@ func (r *Request) ProtoAtLeast(major, minor int) bool {
|
||||
// MultipartReader returns a MIME multipart reader if this is a
|
||||
// multipart/form-data POST request, else returns nil and an error.
|
||||
func (r *Request) MultipartReader() (multipart.Reader, os.Error) {
|
||||
v, ok := r.Header["Content-Type"]
|
||||
if !ok {
|
||||
v := r.Header.Get("Content-Type")
|
||||
if v == "" {
|
||||
return nil, ErrNotMultipart
|
||||
}
|
||||
d, params := mime.ParseMediaType(v)
|
||||
@ -297,78 +297,6 @@ func readLine(b *bufio.Reader) (s string, err os.Error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
var colon = []byte{':'}
|
||||
|
||||
// Read a key/value pair from b.
|
||||
// A key/value has the form Key: Value\r\n
|
||||
// and the Value can continue on multiple lines if each continuation line
|
||||
// starts with a space.
|
||||
func readKeyValue(b *bufio.Reader) (key, value string, err os.Error) {
|
||||
line, e := readLineBytes(b)
|
||||
if e != nil {
|
||||
return "", "", e
|
||||
}
|
||||
if len(line) == 0 {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// Scan first line for colon.
|
||||
i := bytes.Index(line, colon)
|
||||
if i < 0 {
|
||||
goto Malformed
|
||||
}
|
||||
|
||||
key = string(line[0:i])
|
||||
if strings.Contains(key, " ") {
|
||||
// Key field has space - no good.
|
||||
goto Malformed
|
||||
}
|
||||
|
||||
// Skip initial space before value.
|
||||
for i++; i < len(line); i++ {
|
||||
if line[i] != ' ' {
|
||||
break
|
||||
}
|
||||
}
|
||||
value = string(line[i:])
|
||||
|
||||
// Look for extension lines, which must begin with space.
|
||||
for {
|
||||
c, e := b.ReadByte()
|
||||
if c != ' ' {
|
||||
if e != os.EOF {
|
||||
b.UnreadByte()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Eat leading space.
|
||||
for c == ' ' {
|
||||
if c, e = b.ReadByte(); e != nil {
|
||||
if e == os.EOF {
|
||||
e = io.ErrUnexpectedEOF
|
||||
}
|
||||
return "", "", e
|
||||
}
|
||||
}
|
||||
b.UnreadByte()
|
||||
|
||||
// Read the rest of the line and add to value.
|
||||
if line, e = readLineBytes(b); e != nil {
|
||||
return "", "", e
|
||||
}
|
||||
value += " " + string(line)
|
||||
|
||||
if len(value) >= maxValueLength {
|
||||
return "", "", &badStringError{"value too long for key", key}
|
||||
}
|
||||
}
|
||||
return key, value, nil
|
||||
|
||||
Malformed:
|
||||
return "", "", &badStringError{"malformed header line", string(line)}
|
||||
}
|
||||
|
||||
// Convert decimal at s[i:len(s)] to integer,
|
||||
// returning value, string position where the digits stopped,
|
||||
// and whether there was a valid number (digits, not too big).
|
||||
@ -404,43 +332,6 @@ func parseHTTPVersion(vers string) (int, int, bool) {
|
||||
return major, minor, true
|
||||
}
|
||||
|
||||
// CanonicalHeaderKey returns the canonical format of the
|
||||
// HTTP header key s. The canonicalization converts the first
|
||||
// letter and any letter following a hyphen to upper case;
|
||||
// the rest are converted to lowercase. For example, the
|
||||
// canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
func CanonicalHeaderKey(s string) string {
|
||||
// canonicalize: first letter upper case
|
||||
// and upper case after each dash.
|
||||
// (Host, User-Agent, If-Modified-Since).
|
||||
// HTTP headers are ASCII only, so no Unicode issues.
|
||||
var a []byte
|
||||
upper := true
|
||||
for i := 0; i < len(s); i++ {
|
||||
v := s[i]
|
||||
if upper && 'a' <= v && v <= 'z' {
|
||||
if a == nil {
|
||||
a = []byte(s)
|
||||
}
|
||||
a[i] = v + 'A' - 'a'
|
||||
}
|
||||
if !upper && 'A' <= v && v <= 'Z' {
|
||||
if a == nil {
|
||||
a = []byte(s)
|
||||
}
|
||||
a[i] = v + 'a' - 'A'
|
||||
}
|
||||
upper = false
|
||||
if v == '-' {
|
||||
upper = true
|
||||
}
|
||||
}
|
||||
if a != nil {
|
||||
return string(a)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type chunkedReader struct {
|
||||
r *bufio.Reader
|
||||
n uint64 // unread bytes in chunk
|
||||
@ -506,11 +397,16 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err os.Error) {
|
||||
|
||||
// ReadRequest reads and parses a request from b.
|
||||
func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
|
||||
|
||||
tp := textproto.NewReader(b)
|
||||
req = new(Request)
|
||||
|
||||
// First line: GET /index.html HTTP/1.0
|
||||
var s string
|
||||
if s, err = readLine(b); err != nil {
|
||||
if s, err = tp.ReadLine(); err != nil {
|
||||
if err == os.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -529,32 +425,11 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
|
||||
}
|
||||
|
||||
// Subsequent lines: Key: value.
|
||||
nheader := 0
|
||||
req.Header = make(map[string]string)
|
||||
for {
|
||||
var key, value string
|
||||
if key, value, err = readKeyValue(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "" {
|
||||
break
|
||||
}
|
||||
if nheader++; nheader >= maxHeaderLines {
|
||||
return nil, ErrHeaderTooLong
|
||||
}
|
||||
|
||||
key = CanonicalHeaderKey(key)
|
||||
|
||||
// RFC 2616 says that if you send the same header key
|
||||
// multiple times, it has to be semantically equivalent
|
||||
// to concatenating the values separated by commas.
|
||||
oldvalue, present := req.Header[key]
|
||||
if present {
|
||||
req.Header[key] = oldvalue + "," + value
|
||||
} else {
|
||||
req.Header[key] = value
|
||||
}
|
||||
mimeHeader, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = Header(mimeHeader)
|
||||
|
||||
// RFC2616: Must treat
|
||||
// GET /index.html HTTP/1.1
|
||||
@ -565,18 +440,18 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
|
||||
// the same. In the second case, any Host line is ignored.
|
||||
req.Host = req.URL.Host
|
||||
if req.Host == "" {
|
||||
req.Host = req.Header["Host"]
|
||||
req.Host = req.Header.Get("Host")
|
||||
}
|
||||
req.Header["Host"] = "", false
|
||||
req.Header.Del("Host")
|
||||
|
||||
fixPragmaCacheControl(req.Header)
|
||||
|
||||
// Pull out useful fields as a convenience to clients.
|
||||
req.Referer = req.Header["Referer"]
|
||||
req.Header["Referer"] = "", false
|
||||
req.Referer = req.Header.Get("Referer")
|
||||
req.Header.Del("Referer")
|
||||
|
||||
req.UserAgent = req.Header["User-Agent"]
|
||||
req.Header["User-Agent"] = "", false
|
||||
req.UserAgent = req.Header.Get("User-Agent")
|
||||
req.Header.Del("User-Agent")
|
||||
|
||||
// TODO: Parse specific header values:
|
||||
// Accept
|
||||
@ -662,7 +537,7 @@ func (r *Request) ParseForm() (err os.Error) {
|
||||
if r.Body == nil {
|
||||
return os.ErrorString("missing form body")
|
||||
}
|
||||
ct := r.Header["Content-Type"]
|
||||
ct := r.Header.Get("Content-Type")
|
||||
switch strings.Split(ct, ";", 2)[0] {
|
||||
case "text/plain", "application/x-www-form-urlencoded", "":
|
||||
b, e := ioutil.ReadAll(r.Body)
|
||||
@ -697,17 +572,12 @@ func (r *Request) FormValue(key string) string {
|
||||
}
|
||||
|
||||
func (r *Request) expectsContinue() bool {
|
||||
expectation, ok := r.Header["Expect"]
|
||||
return ok && strings.ToLower(expectation) == "100-continue"
|
||||
return strings.ToLower(r.Header.Get("Expect")) == "100-continue"
|
||||
}
|
||||
|
||||
func (r *Request) wantsHttp10KeepAlive() bool {
|
||||
if r.ProtoMajor != 1 || r.ProtoMinor != 0 {
|
||||
return false
|
||||
}
|
||||
value, exists := r.Header["Connection"]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(strings.ToLower(value), "keep-alive")
|
||||
return strings.Contains(strings.ToLower(r.Header.Get("Connection")), "keep-alive")
|
||||
}
|
||||
|
@ -74,7 +74,9 @@ func TestQuery(t *testing.T) {
|
||||
func TestPostQuery(t *testing.T) {
|
||||
req := &Request{Method: "POST"}
|
||||
req.URL, _ = ParseURL("http://www.google.com/search?q=foo&q=bar&both=x")
|
||||
req.Header = map[string]string{"Content-Type": "application/x-www-form-urlencoded; boo!"}
|
||||
req.Header = Header{
|
||||
"Content-Type": {"application/x-www-form-urlencoded; boo!"},
|
||||
}
|
||||
req.Body = nopCloser{strings.NewReader("z=post&both=y")}
|
||||
if q := req.FormValue("q"); q != "foo" {
|
||||
t.Errorf(`req.FormValue("q") = %q, want "foo"`, q)
|
||||
@ -87,18 +89,18 @@ func TestPostQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type stringMap map[string]string
|
||||
type stringMap map[string][]string
|
||||
type parseContentTypeTest struct {
|
||||
contentType stringMap
|
||||
error bool
|
||||
}
|
||||
|
||||
var parseContentTypeTests = []parseContentTypeTest{
|
||||
{contentType: stringMap{"Content-Type": "text/plain"}},
|
||||
{contentType: stringMap{"Content-Type": ""}},
|
||||
{contentType: stringMap{"Content-Type": "text/plain; boundary="}},
|
||||
{contentType: stringMap{"Content-Type": {"text/plain"}}},
|
||||
{contentType: stringMap{}}, // Non-existent keys are not placed. The value nil is illegal.
|
||||
{contentType: stringMap{"Content-Type": {"text/plain; boundary="}}},
|
||||
{
|
||||
contentType: stringMap{"Content-Type": "application/unknown"},
|
||||
contentType: stringMap{"Content-Type": {"application/unknown"}},
|
||||
error: true,
|
||||
},
|
||||
}
|
||||
@ -107,7 +109,7 @@ func TestPostContentTypeParsing(t *testing.T) {
|
||||
for i, test := range parseContentTypeTests {
|
||||
req := &Request{
|
||||
Method: "POST",
|
||||
Header: test.contentType,
|
||||
Header: Header(test.contentType),
|
||||
Body: nopCloser{bytes.NewBufferString("body")},
|
||||
}
|
||||
err := req.ParseForm()
|
||||
@ -123,7 +125,7 @@ func TestPostContentTypeParsing(t *testing.T) {
|
||||
func TestMultipartReader(t *testing.T) {
|
||||
req := &Request{
|
||||
Method: "POST",
|
||||
Header: stringMap{"Content-Type": `multipart/form-data; boundary="foo123"`},
|
||||
Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
|
||||
Body: nopCloser{new(bytes.Buffer)},
|
||||
}
|
||||
multipart, err := req.MultipartReader()
|
||||
@ -131,7 +133,7 @@ func TestMultipartReader(t *testing.T) {
|
||||
t.Errorf("expected multipart; error: %v", err)
|
||||
}
|
||||
|
||||
req.Header = stringMap{"Content-Type": "text/plain"}
|
||||
req.Header = Header{"Content-Type": {"text/plain"}}
|
||||
multipart, err = req.MultipartReader()
|
||||
if multipart != nil {
|
||||
t.Errorf("unexpected multipart for text/plain")
|
||||
|
@ -34,13 +34,13 @@ var reqWriteTests = []reqWriteTest{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: map[string]string{
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
|
||||
"Accept-Encoding": "gzip,deflate",
|
||||
"Accept-Language": "en-us,en;q=0.5",
|
||||
"Keep-Alive": "300",
|
||||
"Proxy-Connection": "keep-alive",
|
||||
Header: Header{
|
||||
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
|
||||
"Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
|
||||
"Accept-Encoding": {"gzip,deflate"},
|
||||
"Accept-Language": {"en-us,en;q=0.5"},
|
||||
"Keep-Alive": {"300"},
|
||||
"Proxy-Connection": {"keep-alive"},
|
||||
},
|
||||
Body: nil,
|
||||
Close: false,
|
||||
@ -53,10 +53,10 @@ var reqWriteTests = []reqWriteTest{
|
||||
"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
|
||||
"Host: www.techcrunch.com\r\n" +
|
||||
"User-Agent: Fake\r\n" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
|
||||
"Accept-Encoding: gzip,deflate\r\n" +
|
||||
"Accept-Language: en-us,en;q=0.5\r\n" +
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
|
||||
"Keep-Alive: 300\r\n" +
|
||||
"Proxy-Connection: keep-alive\r\n\r\n",
|
||||
},
|
||||
@ -71,7 +71,7 @@ var reqWriteTests = []reqWriteTest{
|
||||
},
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Body: nopCloser{bytes.NewBufferString("abcdef")},
|
||||
TransferEncoding: []string{"chunked"},
|
||||
},
|
||||
@ -93,7 +93,7 @@ var reqWriteTests = []reqWriteTest{
|
||||
},
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Close: true,
|
||||
Body: nopCloser{bytes.NewBufferString("abcdef")},
|
||||
TransferEncoding: []string{"chunked"},
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -43,7 +44,7 @@ type Response struct {
|
||||
// omitted from Header.
|
||||
//
|
||||
// Keys in the map are canonicalized (see CanonicalHeaderKey).
|
||||
Header map[string]string
|
||||
Header Header
|
||||
|
||||
// Body represents the response body.
|
||||
Body io.ReadCloser
|
||||
@ -66,7 +67,7 @@ type Response struct {
|
||||
// Trailer maps trailer keys to values. Like for Header, if the
|
||||
// response has multiple trailer lines with the same key, they will be
|
||||
// concatenated, delimited by commas.
|
||||
Trailer map[string]string
|
||||
Trailer map[string][]string
|
||||
}
|
||||
|
||||
// ReadResponse reads and returns an HTTP response from r. The RequestMethod
|
||||
@ -76,13 +77,17 @@ type Response struct {
|
||||
// key/value pairs included in the response trailer.
|
||||
func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os.Error) {
|
||||
|
||||
tp := textproto.NewReader(r)
|
||||
resp = new(Response)
|
||||
|
||||
resp.RequestMethod = strings.ToUpper(requestMethod)
|
||||
|
||||
// Parse the first line of the response.
|
||||
line, err := readLine(r)
|
||||
line, err := tp.ReadLine()
|
||||
if err != nil {
|
||||
if err == os.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
f := strings.Split(line, " ", 3)
|
||||
@ -106,21 +111,11 @@ func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os
|
||||
}
|
||||
|
||||
// Parse the response headers.
|
||||
nheader := 0
|
||||
resp.Header = make(map[string]string)
|
||||
for {
|
||||
key, value, err := readKeyValue(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "" {
|
||||
break // end of response header
|
||||
}
|
||||
if nheader++; nheader >= maxHeaderLines {
|
||||
return nil, ErrHeaderTooLong
|
||||
}
|
||||
resp.AddHeader(key, value)
|
||||
mimeHeader, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Header = Header(mimeHeader)
|
||||
|
||||
fixPragmaCacheControl(resp.Header)
|
||||
|
||||
@ -136,34 +131,14 @@ func ReadResponse(r *bufio.Reader, requestMethod string) (resp *Response, err os
|
||||
// Pragma: no-cache
|
||||
// like
|
||||
// Cache-Control: no-cache
|
||||
func fixPragmaCacheControl(header map[string]string) {
|
||||
if header["Pragma"] == "no-cache" {
|
||||
func fixPragmaCacheControl(header Header) {
|
||||
if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
|
||||
if _, presentcc := header["Cache-Control"]; !presentcc {
|
||||
header["Cache-Control"] = "no-cache"
|
||||
header["Cache-Control"] = []string{"no-cache"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddHeader adds a value under the given key. Keys are not case sensitive.
|
||||
func (r *Response) AddHeader(key, value string) {
|
||||
key = CanonicalHeaderKey(key)
|
||||
|
||||
oldValues, oldValuesPresent := r.Header[key]
|
||||
if oldValuesPresent {
|
||||
r.Header[key] = oldValues + "," + value
|
||||
} else {
|
||||
r.Header[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// GetHeader returns the value of the response header with the given key.
|
||||
// If there were multiple headers with this key, their values are concatenated,
|
||||
// with a comma delimiter. If there were no response headers with the given
|
||||
// key, GetHeader returns an empty string. Keys are not case sensitive.
|
||||
func (r *Response) GetHeader(key string) (value string) {
|
||||
return r.Header[CanonicalHeaderKey(key)]
|
||||
}
|
||||
|
||||
// ProtoAtLeast returns whether the HTTP protocol used
|
||||
// in the response is at least major.minor.
|
||||
func (r *Response) ProtoAtLeast(major, minor int) bool {
|
||||
@ -231,20 +206,19 @@ func (resp *Response) Write(w io.Writer) os.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeSortedKeyValue(w io.Writer, kvm map[string]string, exclude map[string]bool) os.Error {
|
||||
kva := make([]string, len(kvm))
|
||||
i := 0
|
||||
for k, v := range kvm {
|
||||
func writeSortedKeyValue(w io.Writer, kvm map[string][]string, exclude map[string]bool) os.Error {
|
||||
keys := make([]string, 0, len(kvm))
|
||||
for k := range kvm {
|
||||
if !exclude[k] {
|
||||
kva[i] = fmt.Sprint(k + ": " + v + "\r\n")
|
||||
i++
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
kva = kva[0:i]
|
||||
sort.SortStrings(kva)
|
||||
for _, l := range kva {
|
||||
if _, err := io.WriteString(w, l); err != nil {
|
||||
return err
|
||||
sort.SortStrings(keys)
|
||||
for _, k := range keys {
|
||||
for _, v := range kvm[k] {
|
||||
if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -34,8 +34,8 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{
|
||||
"Connection": "close", // TODO(rsc): Delete?
|
||||
Header: Header{
|
||||
"Connection": {"close"}, // TODO(rsc): Delete?
|
||||
},
|
||||
Close: true,
|
||||
ContentLength: -1,
|
||||
@ -100,9 +100,9 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{
|
||||
"Connection": "close", // TODO(rsc): Delete?
|
||||
"Content-Length": "10", // TODO(rsc): Delete?
|
||||
Header: Header{
|
||||
"Connection": {"close"}, // TODO(rsc): Delete?
|
||||
"Content-Length": {"10"}, // TODO(rsc): Delete?
|
||||
},
|
||||
Close: true,
|
||||
ContentLength: 10,
|
||||
@ -128,7 +128,7 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: Header{},
|
||||
Close: true,
|
||||
ContentLength: -1,
|
||||
TransferEncoding: []string{"chunked"},
|
||||
@ -155,7 +155,7 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: Header{},
|
||||
Close: true,
|
||||
ContentLength: -1, // TODO(rsc): Fix?
|
||||
TransferEncoding: []string{"chunked"},
|
||||
@ -175,7 +175,7 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: Header{},
|
||||
Close: true,
|
||||
ContentLength: -1,
|
||||
},
|
||||
@ -194,7 +194,7 @@ var respTests = []respTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: Header{},
|
||||
Close: true,
|
||||
ContentLength: -1,
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ var respWriteTests = []respWriteTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Body: nopCloser{bytes.NewBufferString("abcdef")},
|
||||
ContentLength: 6,
|
||||
},
|
||||
@ -38,7 +38,7 @@ var respWriteTests = []respWriteTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Body: nopCloser{bytes.NewBufferString("abcdef")},
|
||||
ContentLength: -1,
|
||||
},
|
||||
@ -53,7 +53,7 @@ var respWriteTests = []respWriteTest{
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
RequestMethod: "GET",
|
||||
Header: map[string]string{},
|
||||
Header: map[string][]string{},
|
||||
Body: nopCloser{bytes.NewBufferString("abcdef")},
|
||||
ContentLength: 6,
|
||||
TransferEncoding: []string{"chunked"},
|
||||
|
@ -197,7 +197,7 @@ func TestHostHandlers(t *testing.T) {
|
||||
t.Errorf("reading response: %v", err)
|
||||
continue
|
||||
}
|
||||
s := r.Header["Result"]
|
||||
s := r.Header.Get("Result")
|
||||
if s != vt.expected {
|
||||
t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type transferWriter struct {
|
||||
ContentLength int64
|
||||
Close bool
|
||||
TransferEncoding []string
|
||||
Trailer map[string]string
|
||||
Trailer Header
|
||||
}
|
||||
|
||||
func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
|
||||
@ -159,7 +159,7 @@ func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
|
||||
|
||||
type transferReader struct {
|
||||
// Input
|
||||
Header map[string]string
|
||||
Header Header
|
||||
StatusCode int
|
||||
RequestMethod string
|
||||
ProtoMajor int
|
||||
@ -169,7 +169,7 @@ type transferReader struct {
|
||||
ContentLength int64
|
||||
TransferEncoding []string
|
||||
Close bool
|
||||
Trailer map[string]string
|
||||
Trailer Header
|
||||
}
|
||||
|
||||
// bodyAllowedForStatus returns whether a given response status code
|
||||
@ -289,14 +289,14 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
|
||||
func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
|
||||
|
||||
// Sanitize transfer encoding
|
||||
func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
|
||||
func fixTransferEncoding(header Header) ([]string, os.Error) {
|
||||
raw, present := header["Transfer-Encoding"]
|
||||
if !present {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
header["Transfer-Encoding"] = "", false
|
||||
encodings := strings.Split(raw, ",", -1)
|
||||
header["Transfer-Encoding"] = nil, false
|
||||
encodings := strings.Split(raw[0], ",", -1)
|
||||
te := make([]string, 0, len(encodings))
|
||||
// TODO: Even though we only support "identity" and "chunked"
|
||||
// encodings, the loop below is designed with foresight. One
|
||||
@ -321,7 +321,7 @@ func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
|
||||
// Chunked encoding trumps Content-Length. See RFC 2616
|
||||
// Section 4.4. Currently len(te) > 0 implies chunked
|
||||
// encoding.
|
||||
header["Content-Length"] = "", false
|
||||
header["Content-Length"] = nil, false
|
||||
return te, nil
|
||||
}
|
||||
|
||||
@ -331,7 +331,7 @@ func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
|
||||
// Determine the expected body length, using RFC 2616 Section 4.4. This
|
||||
// function is not a method, because ultimately it should be shared by
|
||||
// ReadResponse and ReadRequest.
|
||||
func fixLength(status int, requestMethod string, header map[string]string, te []string) (int64, os.Error) {
|
||||
func fixLength(status int, requestMethod string, header Header, te []string) (int64, os.Error) {
|
||||
|
||||
// Logic based on response type or status
|
||||
if noBodyExpected(requestMethod) {
|
||||
@ -351,23 +351,21 @@ func fixLength(status int, requestMethod string, header map[string]string, te []
|
||||
}
|
||||
|
||||
// Logic based on Content-Length
|
||||
if cl, present := header["Content-Length"]; present {
|
||||
cl = strings.TrimSpace(cl)
|
||||
if cl != "" {
|
||||
n, err := strconv.Atoi64(cl)
|
||||
if err != nil || n < 0 {
|
||||
return -1, &badStringError{"bad Content-Length", cl}
|
||||
}
|
||||
return n, nil
|
||||
} else {
|
||||
header["Content-Length"] = "", false
|
||||
cl := strings.TrimSpace(header.Get("Content-Length"))
|
||||
if cl != "" {
|
||||
n, err := strconv.Atoi64(cl)
|
||||
if err != nil || n < 0 {
|
||||
return -1, &badStringError{"bad Content-Length", cl}
|
||||
}
|
||||
return n, nil
|
||||
} else {
|
||||
header.Del("Content-Length")
|
||||
}
|
||||
|
||||
// Logic based on media type. The purpose of the following code is just
|
||||
// to detect whether the unsupported "multipart/byteranges" is being
|
||||
// used. A proper Content-Type parser is needed in the future.
|
||||
if strings.Contains(strings.ToLower(header["Content-Type"]), "multipart/byteranges") {
|
||||
if strings.Contains(strings.ToLower(header.Get("Content-Type")), "multipart/byteranges") {
|
||||
return -1, ErrNotSupported
|
||||
}
|
||||
|
||||
@ -378,24 +376,19 @@ func fixLength(status int, requestMethod string, header map[string]string, te []
|
||||
// Determine whether to hang up after sending a request and body, or
|
||||
// receiving a response and body
|
||||
// 'header' is the request headers
|
||||
func shouldClose(major, minor int, header map[string]string) bool {
|
||||
func shouldClose(major, minor int, header Header) bool {
|
||||
if major < 1 {
|
||||
return true
|
||||
} else if major == 1 && minor == 0 {
|
||||
v, present := header["Connection"]
|
||||
if !present {
|
||||
return true
|
||||
}
|
||||
v = strings.ToLower(v)
|
||||
if !strings.Contains(v, "keep-alive") {
|
||||
if !strings.Contains(strings.ToLower(header.Get("Connection")), "keep-alive") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else if v, present := header["Connection"]; present {
|
||||
} else {
|
||||
// TODO: Should split on commas, toss surrounding white space,
|
||||
// and check each field.
|
||||
if v == "close" {
|
||||
header["Connection"] = "", false
|
||||
if strings.ToLower(header.Get("Connection")) == "close" {
|
||||
header.Del("Connection")
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -403,14 +396,14 @@ func shouldClose(major, minor int, header map[string]string) bool {
|
||||
}
|
||||
|
||||
// Parse the trailer header
|
||||
func fixTrailer(header map[string]string, te []string) (map[string]string, os.Error) {
|
||||
raw, present := header["Trailer"]
|
||||
if !present {
|
||||
func fixTrailer(header Header, te []string) (Header, os.Error) {
|
||||
raw := header.Get("Trailer")
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
header["Trailer"] = "", false
|
||||
trailer := make(map[string]string)
|
||||
header.Del("Trailer")
|
||||
trailer := make(Header)
|
||||
keys := strings.Split(raw, ",", -1)
|
||||
for _, key := range keys {
|
||||
key = CanonicalHeaderKey(strings.TrimSpace(key))
|
||||
@ -418,7 +411,7 @@ func fixTrailer(header map[string]string, te []string) (map[string]string, os.Er
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
return nil, &badStringError{"bad trailer key", key}
|
||||
}
|
||||
trailer[key] = ""
|
||||
trailer.Del(key)
|
||||
}
|
||||
if len(trailer) == 0 {
|
||||
return nil, nil
|
||||
|
@ -6,6 +6,7 @@ include ../../../Make.inc
|
||||
|
||||
TARG=net/textproto
|
||||
GOFILES=\
|
||||
header.go\
|
||||
pipeline.go\
|
||||
reader.go\
|
||||
textproto.go\
|
||||
|
43
src/pkg/net/textproto/header.go
Normal file
43
src/pkg/net/textproto/header.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package textproto
|
||||
|
||||
// A MIMEHeader represents a MIME-style header mapping
|
||||
// keys to sets of values.
|
||||
type MIMEHeader map[string][]string
|
||||
|
||||
// Add adds the key, value pair to the header.
|
||||
// It appends to any existing values associated with key.
|
||||
func (h MIMEHeader) Add(key, value string) {
|
||||
key = CanonicalMIMEHeaderKey(key)
|
||||
h[key] = append(h[key], value)
|
||||
}
|
||||
|
||||
// Set sets the header entries associated with key to
|
||||
// the single element value. It replaces any existing
|
||||
// values associated with key.
|
||||
func (h MIMEHeader) Set(key, value string) {
|
||||
h[CanonicalMIMEHeaderKey(key)] = []string{value}
|
||||
}
|
||||
|
||||
// Get gets the first value associated with the given key.
|
||||
// If there are no values associated with the key, Get returns "".
|
||||
// Get is a convenience method. For more complex queries,
|
||||
// access the map directly.
|
||||
func (h MIMEHeader) Get(key string) string {
|
||||
if h == nil {
|
||||
return ""
|
||||
}
|
||||
v := h[CanonicalMIMEHeaderKey(key)]
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
return v[0]
|
||||
}
|
||||
|
||||
// Del deletes the values associated with key.
|
||||
func (h MIMEHeader) Del(key string) {
|
||||
h[CanonicalMIMEHeaderKey(key)] = nil, false
|
||||
}
|
@ -402,7 +402,7 @@ func (r *Reader) ReadDotLines() ([]string, os.Error) {
|
||||
// ReadMIMEHeader reads a MIME-style header from r.
|
||||
// The header is a sequence of possibly continued Key: Value lines
|
||||
// ending in a blank line.
|
||||
// The returned map m maps CanonicalHeaderKey(key) to a
|
||||
// The returned map m maps CanonicalMIMEHeaderKey(key) to a
|
||||
// sequence of values in the same order encountered in the input.
|
||||
//
|
||||
// For example, consider this input:
|
||||
@ -415,12 +415,12 @@ func (r *Reader) ReadDotLines() ([]string, os.Error) {
|
||||
// Given that input, ReadMIMEHeader returns the map:
|
||||
//
|
||||
// map[string][]string{
|
||||
// "My-Key": []string{"Value 1", "Value 2"},
|
||||
// "Long-Key": []string{"Even Longer Value"},
|
||||
// "My-Key": {"Value 1", "Value 2"},
|
||||
// "Long-Key": {"Even Longer Value"},
|
||||
// }
|
||||
//
|
||||
func (r *Reader) ReadMIMEHeader() (map[string][]string, os.Error) {
|
||||
m := make(map[string][]string)
|
||||
func (r *Reader) ReadMIMEHeader() (MIMEHeader, os.Error) {
|
||||
m := make(MIMEHeader)
|
||||
for {
|
||||
kv, err := r.ReadContinuedLineBytes()
|
||||
if len(kv) == 0 {
|
||||
@ -432,7 +432,7 @@ func (r *Reader) ReadMIMEHeader() (map[string][]string, os.Error) {
|
||||
if i < 0 || bytes.IndexByte(kv[0:i], ' ') >= 0 {
|
||||
return m, ProtocolError("malformed MIME header line: " + string(kv))
|
||||
}
|
||||
key := CanonicalHeaderKey(string(kv[0:i]))
|
||||
key := CanonicalMIMEHeaderKey(string(kv[0:i]))
|
||||
|
||||
// Skip initial spaces in value.
|
||||
i++ // skip colon
|
||||
@ -452,12 +452,12 @@ func (r *Reader) ReadMIMEHeader() (map[string][]string, os.Error) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// CanonicalHeaderKey returns the canonical format of the
|
||||
// CanonicalMIMEHeaderKey returns the canonical format of the
|
||||
// MIME header key s. The canonicalization converts the first
|
||||
// letter and any letter following a hyphen to upper case;
|
||||
// the rest are converted to lowercase. For example, the
|
||||
// canonical key for "accept-encoding" is "Accept-Encoding".
|
||||
func CanonicalHeaderKey(s string) string {
|
||||
func CanonicalMIMEHeaderKey(s string) string {
|
||||
// Quick check for canonical encoding.
|
||||
needUpper := true
|
||||
for i := 0; i < len(s); i++ {
|
||||
|
@ -26,10 +26,10 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
|
||||
{"USER-AGENT", "User-Agent"},
|
||||
}
|
||||
|
||||
func TestCanonicalHeaderKey(t *testing.T) {
|
||||
func TestCanonicalMIMEHeaderKey(t *testing.T) {
|
||||
for _, tt := range canonicalHeaderKeyTests {
|
||||
if s := CanonicalHeaderKey(tt.in); s != tt.out {
|
||||
t.Errorf("CanonicalHeaderKey(%q) = %q, want %q", tt.in, s, tt.out)
|
||||
if s := CanonicalMIMEHeaderKey(tt.in); s != tt.out {
|
||||
t.Errorf("CanonicalMIMEHeaderKey(%q) = %q, want %q", tt.in, s, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,7 +130,7 @@ func TestReadDotBytes(t *testing.T) {
|
||||
func TestReadMIMEHeader(t *testing.T) {
|
||||
r := reader("my-key: Value 1 \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n")
|
||||
m, err := r.ReadMIMEHeader()
|
||||
want := map[string][]string{
|
||||
want := MIMEHeader{
|
||||
"My-Key": {"Value 1", "Value 2"},
|
||||
"Long-Key": {"Even Longer Value"},
|
||||
}
|
||||
|
@ -245,20 +245,20 @@ func handshake(resourceName, host, origin, location, protocol string, br *bufio.
|
||||
}
|
||||
|
||||
// Step 41. check websocket headers.
|
||||
if resp.Header["Upgrade"] != "WebSocket" ||
|
||||
strings.ToLower(resp.Header["Connection"]) != "upgrade" {
|
||||
if resp.Header.Get("Upgrade") != "WebSocket" ||
|
||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
||||
return ErrBadUpgrade
|
||||
}
|
||||
|
||||
if resp.Header["Sec-Websocket-Origin"] != origin {
|
||||
if resp.Header.Get("Sec-Websocket-Origin") != origin {
|
||||
return ErrBadWebSocketOrigin
|
||||
}
|
||||
|
||||
if resp.Header["Sec-Websocket-Location"] != location {
|
||||
if resp.Header.Get("Sec-Websocket-Location") != location {
|
||||
return ErrBadWebSocketLocation
|
||||
}
|
||||
|
||||
if protocol != "" && resp.Header["Sec-Websocket-Protocol"] != protocol {
|
||||
if protocol != "" && resp.Header.Get("Sec-Websocket-Protocol") != protocol {
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
|
||||
@ -304,17 +304,17 @@ func draft75handshake(resourceName, host, origin, location, protocol string, br
|
||||
if resp.Status != "101 Web Socket Protocol Handshake" {
|
||||
return ErrBadStatus
|
||||
}
|
||||
if resp.Header["Upgrade"] != "WebSocket" ||
|
||||
resp.Header["Connection"] != "Upgrade" {
|
||||
if resp.Header.Get("Upgrade") != "WebSocket" ||
|
||||
resp.Header.Get("Connection") != "Upgrade" {
|
||||
return ErrBadUpgrade
|
||||
}
|
||||
if resp.Header["Websocket-Origin"] != origin {
|
||||
if resp.Header.Get("Websocket-Origin") != origin {
|
||||
return ErrBadWebSocketOrigin
|
||||
}
|
||||
if resp.Header["Websocket-Location"] != location {
|
||||
if resp.Header.Get("Websocket-Location") != location {
|
||||
return ErrBadWebSocketLocation
|
||||
}
|
||||
if protocol != "" && resp.Header["Websocket-Protocol"] != protocol {
|
||||
if protocol != "" && resp.Header.Get("Websocket-Protocol") != protocol {
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
return
|
||||
|
@ -73,23 +73,23 @@ func (f Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
// HTTP version can be safely ignored.
|
||||
|
||||
if strings.ToLower(req.Header["Upgrade"]) != "websocket" ||
|
||||
strings.ToLower(req.Header["Connection"]) != "upgrade" {
|
||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
||||
strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(ukai): check Host
|
||||
origin, found := req.Header["Origin"]
|
||||
if !found {
|
||||
origin := req.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
return
|
||||
}
|
||||
|
||||
key1, found := req.Header["Sec-Websocket-Key1"]
|
||||
if !found {
|
||||
key1 := req.Header.Get("Sec-Websocket-Key1")
|
||||
if key1 == "" {
|
||||
return
|
||||
}
|
||||
key2, found := req.Header["Sec-Websocket-Key2"]
|
||||
if !found {
|
||||
key2 := req.Header.Get("Sec-Websocket-Key2")
|
||||
if key2 == "" {
|
||||
return
|
||||
}
|
||||
key3 := make([]byte, 8)
|
||||
@ -138,8 +138,8 @@ func (f Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Location: " + location + "\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Origin: " + origin + "\r\n")
|
||||
protocol, found := req.Header["Sec-Websocket-Protocol"]
|
||||
if found {
|
||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
||||
if protocol != "" {
|
||||
buf.WriteString("Sec-WebSocket-Protocol: " + protocol + "\r\n")
|
||||
}
|
||||
// Step 12. send CRLF.
|
||||
@ -167,18 +167,18 @@ func (f Draft75Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
io.WriteString(w, "Unexpected request")
|
||||
return
|
||||
}
|
||||
if req.Header["Upgrade"] != "WebSocket" {
|
||||
if req.Header.Get("Upgrade") != "WebSocket" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "missing Upgrade: WebSocket header")
|
||||
return
|
||||
}
|
||||
if req.Header["Connection"] != "Upgrade" {
|
||||
if req.Header.Get("Connection") != "Upgrade" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "missing Connection: Upgrade header")
|
||||
return
|
||||
}
|
||||
origin, found := req.Header["Origin"]
|
||||
if !found {
|
||||
origin := strings.TrimSpace(req.Header.Get("Origin"))
|
||||
if origin == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "missing Origin header")
|
||||
return
|
||||
@ -205,9 +205,9 @@ func (f Draft75Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("WebSocket-Origin: " + origin + "\r\n")
|
||||
buf.WriteString("WebSocket-Location: " + location + "\r\n")
|
||||
protocol, found := req.Header["Websocket-Protocol"]
|
||||
protocol := strings.TrimSpace(req.Header.Get("Websocket-Protocol"))
|
||||
// canonical header key of WebSocket-Protocol.
|
||||
if found {
|
||||
if protocol != "" {
|
||||
buf.WriteString("WebSocket-Protocol: " + protocol + "\r\n")
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
|
Loading…
Reference in New Issue
Block a user