diff options
Diffstat (limited to 'libgo/go/http/request.go')
-rw-r--r-- | libgo/go/http/request.go | 263 |
1 files changed, 136 insertions, 127 deletions
diff --git a/libgo/go/http/request.go b/libgo/go/http/request.go index 8545d75..ed41fa4 100644 --- a/libgo/go/http/request.go +++ b/libgo/go/http/request.go @@ -10,8 +10,9 @@ package http import ( "bufio" + "bytes" "crypto/tls" - "container/vector" + "encoding/base64" "fmt" "io" "io/ioutil" @@ -21,6 +22,7 @@ import ( "os" "strconv" "strings" + "url" ) const ( @@ -33,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"} @@ -58,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, @@ -69,9 +73,9 @@ var reqExcludeHeader = map[string]bool{ // A Request represents a parsed HTTP request header. type Request struct { - Method string // GET, POST, PUT, etc. - RawURL string // The raw URL given in the request. - URL *URL // Parsed URL. + Method string // GET, POST, PUT, etc. + RawURL string // The raw URL given in the request. + URL *url.URL // Parsed URL. // The protocol version for incoming requests. // Outgoing requests always use HTTP/1.1. @@ -88,10 +92,10 @@ type Request struct { // // then // - // Header = map[string]string{ - // "Accept-Encoding": "gzip, deflate", - // "Accept-Language": "en-us", - // "Connection": "keep-alive", + // Header = map[string][]string{ + // "Accept-Encoding": {"gzip, deflate"}, + // "Accept-Language": {"en-us"}, + // "Connection": {"keep-alive"}, // } // // HTTP defines that header names are case-insensitive. @@ -100,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 @@ -123,23 +124,8 @@ 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 map[string][]string + Form url.Values // The parsed multipart form, including file uploads. // Only available after ParseMultipartForm is called. @@ -174,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. @@ -186,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") } @@ -197,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 @@ -228,17 +260,14 @@ const defaultUserAgent = "Go http package" // Host // RawURL, if non-empty, or else URL // Method (defaults to "GET") -// UserAgent (defaults to defaultUserAgent) -// Referer // Header -// Cookie // ContentLength // TransferEncoding // Body // -// If Body is present but Content-Length is <= 0, Write adds -// "Transfer-Encoding: chunked" to the header. Body is closed after -// it is sent. +// If Body is present, Content-Length is <= 0 and TransferEncoding +// hasn't been set to "identity", Write adds "Transfer-Encoding: +// chunked" to the header. Body is closed after it is sent. func (req *Request) Write(w io.Writer) os.Error { return req.write(w, false) } @@ -255,32 +284,42 @@ func (req *Request) WriteProxy(w io.Writer) os.Error { func (req *Request) write(w io.Writer, usingProxy bool) os.Error { host := req.Host if host == "" { + if req.URL == nil { + return os.NewError("http: Request.Write on Request with no Host or URL set") + } host = req.URL.Host } - uri := req.RawURL - if uri == "" { - uri = valueOrDefault(urlEscape(req.URL.Path, encodePath), "/") + urlStr := req.RawURL + if urlStr == "" { + urlStr = valueOrDefault(req.URL.EncodedPath(), "/") if req.URL.RawQuery != "" { - uri += "?" + req.URL.RawQuery + urlStr += "?" + req.URL.RawQuery } if usingProxy { - if uri == "" || uri[0] != '/' { - uri = "/" + uri + if urlStr == "" || urlStr[0] != '/' { + urlStr = "/" + urlStr } - uri = req.URL.Scheme + "://" + host + uri + urlStr = req.URL.Scheme + "://" + host + urlStr } } - 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"), urlStr) // Header lines - if !usingProxy { - fmt.Fprintf(w, "Host: %s\r\n", host) + 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] + } } - fmt.Fprintf(w, "User-Agent: %s\r\n", valueOrDefault(req.UserAgent, defaultUserAgent)) - if req.Referer != "" { - fmt.Fprintf(w, "Referer: %s\r\n", req.Referer) + if userAgent != "" { + fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) } // Process Body,ContentLength,Close,Trailer @@ -288,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 } @@ -399,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 @@ -457,8 +482,8 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err os.Error) { } // NewRequest returns a new Request given a method, URL, and optional body. -func NewRequest(method, url string, body io.Reader) (*Request, os.Error) { - u, err := ParseURL(url) +func NewRequest(method, urlStr string, body io.Reader) (*Request, os.Error) { + u, err := url.Parse(urlStr) if err != nil { return nil, err } @@ -476,9 +501,28 @@ func NewRequest(method, url string, body io.Reader) (*Request, os.Error) { Body: rc, Host: u.Host, } + if body != nil { + switch v := body.(type) { + case *strings.Reader: + req.ContentLength = int64(v.Len()) + case *bytes.Buffer: + req.ContentLength = int64(v.Len()) + } + } + return req, nil } +// SetBasicAuth sets the request's Authorization header to use HTTP +// Basic Authentication with the provided username and password. +// +// With HTTP Basic Authentication the provided username and password +// are not encrypted. +func (r *Request) SetBasicAuth(username, password string) { + s := username + ":" + password + r.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) +} + // ReadRequest reads and parses a request from b. func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { @@ -495,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] @@ -504,7 +548,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { return nil, &badStringError{"malformed HTTP version", req.Proto} } - if req.URL, err = ParseRequestURL(req.RawURL); err != nil { + if req.URL, err = url.ParseRequest(req.RawURL); err != nil { return nil, err } @@ -530,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 @@ -568,46 +605,9 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { return nil, err } - req.Cookie = readCookies(req.Header) - return req, nil } -// ParseQuery parses the URL-encoded query string and returns -// a map listing the values specified for each key. -// ParseQuery always returns a non-nil map containing all the -// valid query parameters found; err describes the first decoding error -// encountered, if any. -func ParseQuery(query string) (m map[string][]string, err os.Error) { - m = make(map[string][]string) - err = parseQuery(m, query) - return -} - -func parseQuery(m map[string][]string, query string) (err os.Error) { - for _, kv := range strings.Split(query, "&", -1) { - if len(kv) == 0 { - continue - } - kvPair := strings.Split(kv, "=", 2) - - var key, value string - var e os.Error - key, e = URLUnescape(kvPair[0]) - if e == nil && len(kvPair) > 1 { - value, e = URLUnescape(kvPair[1]) - } - if e != nil { - err = e - continue - } - vec := vector.StringVector(m[key]) - vec.Push(value) - m[key] = vec - } - return err -} - // ParseForm parses the raw query. // For POST requests, it also parses the request body as a form. // ParseMultipartForm calls ParseForm automatically. @@ -617,16 +617,15 @@ func (r *Request) ParseForm() (err os.Error) { return } - r.Form = make(map[string][]string) if r.URL != nil { - err = parseQuery(r.Form, r.URL.RawQuery) + r.Form, err = url.ParseQuery(r.URL.RawQuery) } 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)) @@ -639,10 +638,20 @@ func (r *Request) ParseForm() (err os.Error) { if int64(len(b)) > maxFormSize { return os.NewError("http: POST too large") } - e = parseQuery(r.Form, string(b)) + var newValues url.Values + newValues, e = url.ParseQuery(string(b)) if err == nil { err = e } + if r.Form == nil { + r.Form = make(url.Values) + } + // Copy values into r.Form. TODO: make this smoother. + for k, vs := range newValues { + for _, value := range vs { + r.Form.Add(k, value) + } + } case "multipart/form-data": // handled by ParseMultipartForm default: @@ -659,6 +668,9 @@ func (r *Request) ParseForm() (err os.Error) { // ParseMultipartForm calls ParseForm if necessary. // After one call to ParseMultipartForm, subsequent calls have no effect. func (r *Request) ParseMultipartForm(maxMemory int64) os.Error { + if r.MultipartForm == multipartByReader { + return os.NewError("http: multipart handled by MultipartReader") + } if r.Form == nil { err := r.ParseForm() if err != nil { @@ -668,9 +680,6 @@ func (r *Request) ParseMultipartForm(maxMemory int64) os.Error { if r.MultipartForm != nil { return nil } - if r.MultipartForm == multipartByReader { - return os.NewError("http: multipart handled by MultipartReader") - } mr, err := r.multipartReader() if err == ErrNotMultipart { |