diff options
Diffstat (limited to 'src/pkg/http/request.go')
-rw-r--r-- | src/pkg/http/request.go | 113 |
1 files changed, 82 insertions, 31 deletions
diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go index 8a72d6cfa..b88689988 100644 --- a/src/pkg/http/request.go +++ b/src/pkg/http/request.go @@ -16,6 +16,8 @@ import ( "fmt" "io" "io/ioutil" + "mime" + "mime/multipart" "os" "strconv" "strings" @@ -40,6 +42,8 @@ var ( ErrNotSupported = &ProtocolError{"feature not supported"} ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} + ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} + ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} ) type badStringError struct { @@ -67,7 +71,7 @@ type Request struct { ProtoMajor int // 1 ProtoMinor int // 0 - // A header mapping request lines to their values. + // A header maps request lines to their values. // If the header says // // accept-encoding: gzip, deflate @@ -139,6 +143,24 @@ func (r *Request) ProtoAtLeast(major, minor int) bool { r.ProtoMajor == major && r.ProtoMinor >= minor } +// 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 { + return nil, ErrNotMultipart + } + d, params := mime.ParseMediaType(v) + if d != "multipart/form-data" { + return nil, ErrNotMultipart + } + boundary, ok := params["boundary"] + if !ok { + return nil, ErrMissingBoundary + } + return multipart.NewReader(r.Body, boundary), nil +} + // Return value if nonempty, def otherwise. func valueOrDefault(value, def string) string { if value != "" { @@ -169,7 +191,7 @@ func (req *Request) Write(w io.Writer) os.Error { uri := req.RawURL if uri == "" { - uri = valueOrDefault(urlEscape(req.URL.Path, false), "/") + uri = valueOrDefault(urlEscape(req.URL.Path, encodePath), "/") if req.URL.RawQuery != "" { uri += "?" + req.URL.RawQuery } @@ -227,6 +249,8 @@ func readLineBytes(b *bufio.Reader) (p []byte, err os.Error) { // If the caller asked for a line, there should be a line. if err == os.EOF { err = io.ErrUnexpectedEOF + } else if err == bufio.ErrBufferFull { + err = ErrLineTooLong } return nil, err } @@ -275,7 +299,7 @@ func readKeyValue(b *bufio.Reader) (key, value string, err os.Error) { } key = string(line[0:i]) - if strings.Index(key, " ") >= 0 { + if strings.Contains(key, " ") { // Key field has space - no good. goto Malformed } @@ -360,29 +384,30 @@ func parseHTTPVersion(vers string) (int, int, bool) { return major, minor, true } -var cmap = make(map[string]string) - // 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 { - if t, ok := cmap[s]; ok { - return t - } - // 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. - a := []byte(s) + var a []byte upper := true - for i, v := range a { + 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 @@ -390,9 +415,10 @@ func CanonicalHeaderKey(s string) string { upper = true } } - t := string(a) - cmap[s] = t - return t + if a != nil { + return string(a) + } + return s } type chunkedReader struct { @@ -566,9 +592,22 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { 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 @@ -579,14 +618,13 @@ func ParseQuery(query string) (m map[string][]string, err os.Error) { } if e != nil { err = e + continue } - vec := vector.StringVector(m[key]) vec.Push(value) m[key] = vec } - - return + return err } // ParseForm parses the request body as a form for POST requests, or the raw query for GET requests. @@ -596,32 +634,34 @@ func (r *Request) ParseForm() (err os.Error) { return } - var query string - switch r.Method { - case "GET": - query = r.URL.RawQuery - case "POST": + r.Form = make(map[string][]string) + if r.URL != nil { + err = parseQuery(r.Form, r.URL.RawQuery) + } + if r.Method == "POST" { if r.Body == nil { - r.Form = make(map[string][]string) return os.ErrorString("missing form body") } ct := r.Header["Content-Type"] switch strings.Split(ct, ";", 2)[0] { case "text/plain", "application/x-www-form-urlencoded", "": - var b []byte - if b, err = ioutil.ReadAll(r.Body); err != nil { - r.Form = make(map[string][]string) - return err + b, e := ioutil.ReadAll(r.Body) + if e != nil { + if err == nil { + err = e + } + break + } + e = parseQuery(r.Form, string(b)) + if err == nil { + err = e } - query = string(b) // TODO(dsymonds): Handle multipart/form-data default: - r.Form = make(map[string][]string) return &badStringError{"unknown Content-Type", ct} } } - r.Form, err = ParseQuery(query) - return + return err } // FormValue returns the first value for the named component of the query. @@ -640,3 +680,14 @@ func (r *Request) expectsContinue() bool { expectation, ok := r.Header["Expect"] return ok && strings.ToLower(expectation) == "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") +} |