diff options
| author | Ondřej Surý <ondrej@sury.org> | 2011-04-20 15:44:41 +0200 | 
|---|---|---|
| committer | Ondřej Surý <ondrej@sury.org> | 2011-04-20 15:44:41 +0200 | 
| commit | 50104cc32a498f7517a51c8dc93106c51c7a54b4 (patch) | |
| tree | 47af80be259cc7c45d0eaec7d42e61fa38c8e4fb /src/pkg/http/request.go | |
| parent | c072558b90f1bbedc2022b0f30c8b1ac4712538e (diff) | |
| download | golang-50104cc32a498f7517a51c8dc93106c51c7a54b4.tar.gz | |
Imported Upstream version 2011.03.07.1upstream/2011.03.07.1
Diffstat (limited to 'src/pkg/http/request.go')
| -rw-r--r-- | src/pkg/http/request.go | 220 | 
1 files changed, 60 insertions, 160 deletions
| diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go index 04bebaaf5..d8456bab3 100644 --- a/src/pkg/http/request.go +++ b/src/pkg/http/request.go @@ -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,10 @@ 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 + +	// Cookie records the HTTP cookies sent with the request. +	Cookie []*Cookie  	// The message body.  	Body io.ReadCloser @@ -133,7 +136,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 +149,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) @@ -184,6 +187,19 @@ const defaultUserAgent = "Go http package"  // If Body is present, Write forces "Transfer-Encoding: chunked" as a header  // and then closes Body when finished sending it.  func (req *Request) Write(w io.Writer) os.Error { +	return req.write(w, false) +} + +// WriteProxy is like Write but writes the request in the form +// expected by an HTTP proxy.  It includes the scheme and host +// name in the URI instead of using a separate Host: header line. +// If req.RawURL is non-empty, WriteProxy uses it unchanged +// instead of URL but still omits the Host: header. +func (req *Request) WriteProxy(w io.Writer) os.Error { +	return req.write(w, true) +} + +func (req *Request) write(w io.Writer, usingProxy bool) os.Error {  	host := req.Host  	if host == "" {  		host = req.URL.Host @@ -195,12 +211,20 @@ func (req *Request) Write(w io.Writer) os.Error {  		if req.URL.RawQuery != "" {  			uri += "?" + req.URL.RawQuery  		} +		if usingProxy { +			if uri == "" || uri[0] != '/' { +				uri = "/" + uri +			} +			uri = req.URL.Scheme + "://" + host + uri +		}  	}  	fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri)  	// Header lines -	fmt.Fprintf(w, "Host: %s\r\n", host) +	if !usingProxy { +		fmt.Fprintf(w, "Host: %s\r\n", host) +	}  	fmt.Fprintf(w, "User-Agent: %s\r\n", valueOrDefault(req.UserAgent, defaultUserAgent))  	if req.Referer != "" {  		fmt.Fprintf(w, "Referer: %s\r\n", req.Referer) @@ -223,11 +247,15 @@ func (req *Request) Write(w io.Writer) os.Error {  	// from Request, and introduce Request methods along the lines of  	// Response.{GetHeader,AddHeader} and string constants for "Host",  	// "User-Agent" and "Referer". -	err = writeSortedKeyValue(w, req.Header, reqExcludeHeader) +	err = writeSortedHeader(w, req.Header, reqExcludeHeader)  	if err != nil {  		return err  	} +	if err = writeCookies(w, req.Cookie); err != nil { +		return err +	} +  	io.WriteString(w, "\r\n")  	// Write body and trailer @@ -277,78 +305,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). @@ -367,8 +323,9 @@ func atoi(s string, i int) (n, i1 int, ok bool) {  	return n, i, true  } -// Parse HTTP version: "HTTP/1.2" -> (1, 2, true). -func parseHTTPVersion(vers string) (int, int, bool) { +// ParseHTTPVersion parses a HTTP version string. +// "HTTP/1.0" returns (1, 0, true). +func ParseHTTPVersion(vers string) (major, minor int, ok bool) {  	if len(vers) < 5 || vers[0:5] != "HTTP/" {  		return 0, 0, false  	} @@ -376,7 +333,6 @@ func parseHTTPVersion(vers string) (int, int, bool) {  	if !ok || i >= len(vers) || vers[i] != '.' {  		return 0, 0, false  	} -	var minor int  	minor, i, ok = atoi(vers, i+1)  	if !ok || i != len(vers) {  		return 0, 0, false @@ -384,43 +340,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 @@ -486,11 +405,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  	} @@ -500,7 +424,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {  	}  	req.Method, req.RawURL, req.Proto = f[0], f[1], f[2]  	var ok bool -	if req.ProtoMajor, req.ProtoMinor, ok = parseHTTPVersion(req.Proto); !ok { +	if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {  		return nil, &badStringError{"malformed HTTP version", req.Proto}  	} @@ -509,32 +433,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 @@ -545,18 +448,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 @@ -589,6 +492,8 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {  		return nil, err  	} +	req.Cookie = readCookies(req.Header) +  	return req, nil  } @@ -642,7 +547,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) @@ -677,17 +582,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")  } | 
