From 022ee0c26fc96942a970a03b208224e93b3b4af9 Mon Sep 17 00:00:00 2001 From: David Symonds Date: Fri, 19 Jun 2009 18:02:15 -0700 Subject: [PATCH] Add form body parsing to http.Request. better error handling throughout. R=r,rsc APPROVED=r DELTA=254 (201 added, 3 deleted, 50 changed) OCL=30515 CL=30545 --- src/pkg/Make.deps | 2 +- src/pkg/http/request.go | 48 ++++++++++++++++++++++++++++ src/pkg/http/request_test.go | 62 ++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/pkg/http/request_test.go diff --git a/src/pkg/Make.deps b/src/pkg/Make.deps index eb99e558c9..e70aebaad1 100644 --- a/src/pkg/Make.deps +++ b/src/pkg/Make.deps @@ -25,7 +25,7 @@ go/token.install: strconv.install hash.install: io.install hash/adler32.install: hash.install os.install hash/crc32.install: hash.install os.install -http.install: bufio.install fmt.install io.install log.install net.install os.install path.install strconv.install strings.install utf8.install +http.install: bufio.install container/vector.install fmt.install io.install log.install net.install os.install path.install strconv.install strings.install utf8.install io.install: bytes.install os.install sync.install json.install: container/vector.install fmt.install io.install math.install reflect.install strconv.install strings.install utf8.install log.install: fmt.install io.install os.install runtime.install time.install diff --git a/src/pkg/http/request.go b/src/pkg/http/request.go index f8c37ec1ea..5356f55253 100644 --- a/src/pkg/http/request.go +++ b/src/pkg/http/request.go @@ -11,6 +11,7 @@ package http import ( "bufio"; + "container/vector"; "fmt"; "http"; "io"; @@ -38,6 +39,7 @@ var ( BadHeader = &ProtocolError{"malformed http header"}; BadRequest = &ProtocolError{"invalid http request"}; BadHTTPVersion = &ProtocolError{"unsupported http version"}; + UnknownContentType = &ProtocolError{"unknown content type"}; ) // A Request represents a parsed HTTP request header. @@ -95,6 +97,10 @@ type Request struct { // The User-Agent: header string, if sent in the request. UserAgent string; + + // The parsed form data. Only available after ParseForm is called. + FormData map[string] *vector.StringVector + } // ProtoAtLeast returns whether the HTTP protocol used @@ -459,3 +465,45 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { return req, nil } + +func parseForm(body string) (data map[string] *vector.StringVector, err os.Error) { + data = make(map[string] *vector.StringVector); + for _, kv := range strings.Split(body, "&") { + kvPair := strings.Split(kv, "="); + + 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; + } + + vec, ok := data[key]; + if !ok { + vec = vector.NewStringVector(0); + data[key] = vec; + } + vec.Push(value); + } + return +} + +// ParseForm parses the request body as a form. +func (r *Request) ParseForm() (err os.Error) { + ct, ok := r.Header["Content-Type"]; + if !ok { + ct = "application/x-www-form-urlencoded"; // default + } + switch ct { + case "text/plain", "application/x-www-form-urlencoded": + buf := new(io.ByteBuffer); + io.Copy(r.Body, buf); + r.FormData, err = parseForm(string(buf.Data())); + return err + // TODO(dsymonds): Handle multipart/form-data + } + return UnknownContentType +} diff --git a/src/pkg/http/request_test.go b/src/pkg/http/request_test.go new file mode 100644 index 0000000000..ab611a0ca8 --- /dev/null +++ b/src/pkg/http/request_test.go @@ -0,0 +1,62 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http + +import ( + "fmt"; + "http"; + "testing"; +) + +type stringMultimap map[string] []string + +type parseTest struct { + body string; + out stringMultimap; +} + +var parseTests = []parseTest{ + parseTest{ + body: "a=1&b=2", + out: stringMultimap{ "a": []string{ "1" }, "b": []string{ "2" } }, + }, + parseTest{ + body: "a=1&a=2&a=banana", + out: stringMultimap{ "a": []string{ "1", "2", "banana" } }, + }, + parseTest{ + body: "ascii=%3Ckey%3A+0x90%3E", + out: stringMultimap{ "ascii": []string{ "" } }, + }, +} + +func TestParseForm(t *testing.T) { + for i, test := range parseTests { + data, err := parseForm(test.body); + if err != nil { + t.Errorf("test %d: Unexpected error: %v", i, err); + continue + } + if dlen, olen := len(data), len(test.out); dlen != olen { + t.Errorf("test %d: Have %d keys, want %d keys", i, dlen, olen); + } + for k, vs := range(test.out) { + vec, ok := data[k]; + if !ok { + t.Errorf("test %d: Missing key %q", i, k); + continue + } + if dlen, olen := vec.Len(), len(vs); dlen != olen { + t.Errorf("test %d: key %q: Have %d keys, want %d keys", i, k, dlen, olen); + continue + } + for j, v := range(vs) { + if dv := vec.At(j); dv != v { + t.Errorf("test %d: key %q: val %d: Have %q, want %q", i, k, j, dv, v); + } + } + } + } +}