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)) |