diff options
Diffstat (limited to 'src/pkg/http/request.go')
| -rw-r--r-- | src/pkg/http/request.go | 152 | 
1 files changed, 79 insertions, 73 deletions
| diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go index bdc3a7e4f..2917cc1e6 100644 --- a/src/pkg/http/request.go +++ b/src/pkg/http/request.go @@ -35,13 +35,15 @@ const (  // ErrMissingFile is returned by FormFile when the provided file field name  // is either not present in the request or not a file field. -var ErrMissingFile = os.ErrorString("http: no such file") +var ErrMissingFile = os.NewError("http: no such file")  // HTTP request parsing errors.  type ProtocolError struct { -	os.ErrorString +	ErrorString string  } +func (err *ProtocolError) String() string { return err.ErrorString } +  var (  	ErrLineTooLong          = &ProtocolError{"header line too long"}  	ErrHeaderTooLong        = &ProtocolError{"header too long"} @@ -60,10 +62,10 @@ type badStringError struct {  func (e *badStringError) String() string { return fmt.Sprintf("%s %q", e.what, e.str) } -var reqExcludeHeader = map[string]bool{ +// Headers that Request.Write handles itself and should be skipped. +var reqWriteExcludeHeader = map[string]bool{  	"Host":              true,  	"User-Agent":        true, -	"Referer":           true,  	"Content-Length":    true,  	"Transfer-Encoding": true,  	"Trailer":           true, @@ -102,9 +104,6 @@ type Request struct {  	// following a hyphen uppercase and the rest lowercase.  	Header Header -	// Cookie records the HTTP cookies sent with the request. -	Cookie []*Cookie -  	// The message body.  	Body io.ReadCloser @@ -125,21 +124,6 @@ type Request struct {  	// or the host name given in the URL itself.  	Host string -	// The referring URL, if sent in the request. -	// -	// Referer is misspelled as in the request itself, -	// a mistake from the earliest days of HTTP. -	// This value can also be fetched from the Header map -	// as Header["Referer"]; the benefit of making it -	// available as a structure field is that the compiler -	// can diagnose programs that use the alternate -	// (correct English) spelling req.Referrer but cannot -	// diagnose programs that use Header["Referrer"]. -	Referer string - -	// The User-Agent: header string, if sent in the request. -	UserAgent string -  	// The parsed form. Only available after ParseForm is called.  	Form Values @@ -176,6 +160,52 @@ func (r *Request) ProtoAtLeast(major, minor int) bool {  		r.ProtoMajor == major && r.ProtoMinor >= minor  } +// UserAgent returns the client's User-Agent, if sent in the request. +func (r *Request) UserAgent() string { +	return r.Header.Get("User-Agent") +} + +// Cookies parses and returns the HTTP cookies sent with the request. +func (r *Request) Cookies() []*Cookie { +	return readCookies(r.Header, "") +} + +var ErrNoCookie = os.NewError("http: named cookied not present") + +// Cookie returns the named cookie provided in the request or +// ErrNoCookie if not found. +func (r *Request) Cookie(name string) (*Cookie, os.Error) { +	for _, c := range readCookies(r.Header, name) { +		return c, nil +	} +	return nil, ErrNoCookie +} + +// AddCookie adds a cookie to the request.  Per RFC 6265 section 5.4, +// AddCookie does not attach more than one Cookie header field.  That +// means all cookies, if any, are written into the same line, +// separated by semicolon. +func (r *Request) AddCookie(c *Cookie) { +	s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) +	if c := r.Header.Get("Cookie"); c != "" { +		r.Header.Set("Cookie", c+"; "+s) +	} else { +		r.Header.Set("Cookie", s) +	} +} + +// Referer returns the referring URL, if sent in the request. +// +// Referer is misspelled as in the request itself, a mistake from the +// earliest days of HTTP.  This value can also be fetched from the +// Header map as Header["Referer"]; the benefit of making it available +// as a method is that the compiler can diagnose programs that use the +// alternate (correct English) spelling req.Referrer() but cannot +// diagnose programs that use Header["Referrer"]. +func (r *Request) Referer() string { +	return r.Header.Get("Referer") +} +  // multipartByReader is a sentinel value.  // Its presence in Request.MultipartForm indicates that parsing of the request  // body has been handed off to a MultipartReader instead of ParseMultipartFrom. @@ -188,7 +218,7 @@ var multipartByReader = &multipart.Form{  // multipart/form-data POST request, else returns nil and an error.  // Use this function instead of ParseMultipartForm to  // process the request body as a stream. -func (r *Request) MultipartReader() (multipart.Reader, os.Error) { +func (r *Request) MultipartReader() (*multipart.Reader, os.Error) {  	if r.MultipartForm == multipartByReader {  		return nil, os.NewError("http: MultipartReader called twice")  	} @@ -199,7 +229,7 @@ func (r *Request) MultipartReader() (multipart.Reader, os.Error) {  	return r.multipartReader()  } -func (r *Request) multipartReader() (multipart.Reader, os.Error) { +func (r *Request) multipartReader() (*multipart.Reader, os.Error) {  	v := r.Header.Get("Content-Type")  	if v == "" {  		return nil, ErrNotMultipart @@ -230,10 +260,7 @@ const defaultUserAgent = "Go http package"  //	Host  //	RawURL, if non-empty, or else URL  //	Method (defaults to "GET") -//	UserAgent (defaults to defaultUserAgent) -//	Referer -//	Header (only keys not already in this list) -//	Cookie +//	Header  //	ContentLength  //	TransferEncoding  //	Body @@ -277,13 +304,22 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {  		}  	} -	fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri) +	bw := bufio.NewWriter(w) +	fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri)  	// Header lines -	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) +	fmt.Fprintf(bw, "Host: %s\r\n", host) + +	// Use the defaultUserAgent unless the Header contains one, which +	// may be blank to not send the header. +	userAgent := defaultUserAgent +	if req.Header != nil { +		if ua := req.Header["User-Agent"]; len(ua) > 0 { +			userAgent = ua[0] +		} +	} +	if userAgent != "" { +		fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent)  	}  	// Process Body,ContentLength,Close,Trailer @@ -291,35 +327,25 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {  	if err != nil {  		return err  	} -	err = tw.WriteHeader(w) +	err = tw.WriteHeader(bw)  	if err != nil {  		return err  	}  	// TODO: split long values?  (If so, should share code with Conn.Write) -	// TODO: if Header includes values for Host, User-Agent, or Referer, this -	// may conflict with the User-Agent or Referer headers we add manually. -	// One solution would be to remove the Host, UserAgent, and Referer fields -	// from Request, and introduce Request methods along the lines of -	// Response.{GetHeader,AddHeader} and string constants for "Host", -	// "User-Agent" and "Referer". -	err = req.Header.WriteSubset(w, reqExcludeHeader) +	err = req.Header.WriteSubset(bw, reqWriteExcludeHeader)  	if err != nil {  		return err  	} -	if err = writeCookies(w, req.Cookie); err != nil { -		return err -	} - -	io.WriteString(w, "\r\n") +	io.WriteString(bw, "\r\n")  	// Write body and trailer -	err = tw.WriteBody(w) +	err = tw.WriteBody(bw)  	if err != nil {  		return err  	} - +	bw.Flush()  	return nil  } @@ -402,10 +428,6 @@ type chunkedReader struct {  	err os.Error  } -func newChunkedReader(r *bufio.Reader) *chunkedReader { -	return &chunkedReader{r: r} -} -  func (cr *chunkedReader) beginChunk() {  	// chunk-size CRLF  	var line string @@ -485,13 +507,6 @@ func NewRequest(method, url string, body io.Reader) (*Request, os.Error) {  			req.ContentLength = int64(v.Len())  		case *bytes.Buffer:  			req.ContentLength = int64(v.Len()) -		default: -			req.ContentLength = -1 // chunked -		} -		if req.ContentLength == 0 { -			// To prevent chunking and disambiguate this -			// from the default ContentLength zero value. -			req.TransferEncoding = []string{"identity"}  		}  	} @@ -524,7 +539,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {  	}  	var f []string -	if f = strings.Split(s, " ", 3); len(f) < 3 { +	if f = strings.SplitN(s, " ", 3); len(f) < 3 {  		return nil, &badStringError{"malformed HTTP request", s}  	}  	req.Method, req.RawURL, req.Proto = f[0], f[1], f[2] @@ -559,13 +574,6 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {  	fixPragmaCacheControl(req.Header) -	// Pull out useful fields as a convenience to clients. -	req.Referer = req.Header.Get("Referer") -	req.Header.Del("Referer") - -	req.UserAgent = req.Header.Get("User-Agent") -	req.Header.Del("User-Agent") -  	// TODO: Parse specific header values:  	//	Accept  	//	Accept-Encoding @@ -597,8 +605,6 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {  		return nil, err  	} -	req.Cookie = readCookies(req.Header) -  	return req, nil  } @@ -652,11 +658,11 @@ func ParseQuery(query string) (m Values, err os.Error) {  }  func parseQuery(m Values, query string) (err os.Error) { -	for _, kv := range strings.Split(query, "&", -1) { +	for _, kv := range strings.Split(query, "&") {  		if len(kv) == 0 {  			continue  		} -		kvPair := strings.Split(kv, "=", 2) +		kvPair := strings.SplitN(kv, "=", 2)  		var key, value string  		var e os.Error @@ -690,10 +696,10 @@ func (r *Request) ParseForm() (err os.Error) {  	}  	if r.Method == "POST" {  		if r.Body == nil { -			return os.ErrorString("missing form body") +			return os.NewError("missing form body")  		}  		ct := r.Header.Get("Content-Type") -		switch strings.Split(ct, ";", 2)[0] { +		switch strings.SplitN(ct, ";", 2)[0] {  		case "text/plain", "application/x-www-form-urlencoded", "":  			const maxFormSize = int64(10 << 20) // 10 MB is a lot of text.  			b, e := ioutil.ReadAll(io.LimitReader(r.Body, maxFormSize+1)) | 
