aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/http/request.go
diff options
context:
space:
mode:
authorIan Lance Taylor <ian@gcc.gnu.org>2011-10-26 23:57:58 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2011-10-26 23:57:58 +0000
commitd8f412571f8768df2d3239e72392dfeabbad1559 (patch)
tree19d182df05ead7ff8ba7ee00a7d57555e1383fdf /libgo/go/http/request.go
parente0c39d66d4f0607177b1cf8995dda56a667e07b3 (diff)
downloadgcc-d8f412571f8768df2d3239e72392dfeabbad1559.zip
gcc-d8f412571f8768df2d3239e72392dfeabbad1559.tar.gz
gcc-d8f412571f8768df2d3239e72392dfeabbad1559.tar.bz2
Update Go library to last weekly.
From-SVN: r180552
Diffstat (limited to 'libgo/go/http/request.go')
-rw-r--r--libgo/go/http/request.go176
1 files changed, 148 insertions, 28 deletions
diff --git a/libgo/go/http/request.go b/libgo/go/http/request.go
index ed41fa4..02317e0 100644
--- a/libgo/go/http/request.go
+++ b/libgo/go/http/request.go
@@ -64,18 +64,24 @@ func (e *badStringError) String() string { return fmt.Sprintf("%s %q", e.what, e
// Headers that Request.Write handles itself and should be skipped.
var reqWriteExcludeHeader = map[string]bool{
- "Host": true,
+ "Host": true, // not in Header map anyway
"User-Agent": true,
"Content-Length": true,
"Transfer-Encoding": true,
"Trailer": true,
}
+var reqWriteExcludeHeaderDump = map[string]bool{
+ "Host": true, // not in Header map anyway
+ "Content-Length": true,
+ "Transfer-Encoding": true,
+ "Trailer": true,
+}
+
// 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.URL // Parsed URL.
+ Method string // GET, POST, PUT, etc.
+ URL *url.URL
// The protocol version for incoming requests.
// Outgoing requests always use HTTP/1.1.
@@ -234,8 +240,8 @@ func (r *Request) multipartReader() (*multipart.Reader, os.Error) {
if v == "" {
return nil, ErrNotMultipart
}
- d, params := mime.ParseMediaType(v)
- if d != "multipart/form-data" {
+ d, params, err := mime.ParseMediaType(v)
+ if err != nil || d != "multipart/form-data" {
return nil, ErrNotMultipart
}
boundary, ok := params["boundary"]
@@ -258,7 +264,7 @@ const defaultUserAgent = "Go http package"
// Write writes an HTTP/1.1 request -- header and body -- in wire format.
// This method consults the following fields of req:
// Host
-// RawURL, if non-empty, or else URL
+// URL
// Method (defaults to "GET")
// Header
// ContentLength
@@ -269,19 +275,66 @@ const defaultUserAgent = "Go http package"
// 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)
+ return req.write(w, false, nil)
}
// WriteProxy is like Write but writes the request in the form
-// expected by an HTTP proxy. It includes the scheme and host
-// name in the URI instead of using a separate Host: header line.
-// If req.RawURL is non-empty, WriteProxy uses it unchanged
-// instead of URL but still omits the Host: header.
+// expected by an HTTP proxy. In particular, WriteProxy writes the
+// initial Request-URI line of the request with an absolute URI, per
+// section 5.1.2 of RFC 2616, including the scheme and host. In
+// either case, WriteProxy also writes a Host header, using either
+// req.Host or req.URL.Host.
func (req *Request) WriteProxy(w io.Writer) os.Error {
- return req.write(w, true)
+ return req.write(w, true, nil)
+}
+
+func (req *Request) dumpWrite(w io.Writer) os.Error {
+ // TODO(bradfitz): RawPath here?
+ urlStr := valueOrDefault(req.URL.EncodedPath(), "/")
+ if req.URL.RawQuery != "" {
+ urlStr += "?" + req.URL.RawQuery
+ }
+
+ bw := bufio.NewWriter(w)
+ fmt.Fprintf(bw, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), urlStr,
+ req.ProtoMajor, req.ProtoMinor)
+
+ host := req.Host
+ if host == "" && req.URL != nil {
+ host = req.URL.Host
+ }
+ if host != "" {
+ fmt.Fprintf(bw, "Host: %s\r\n", host)
+ }
+
+ // Process Body,ContentLength,Close,Trailer
+ tw, err := newTransferWriter(req)
+ if err != nil {
+ return err
+ }
+ err = tw.WriteHeader(bw)
+ if err != nil {
+ return err
+ }
+
+ err = req.Header.WriteSubset(bw, reqWriteExcludeHeaderDump)
+ if err != nil {
+ return err
+ }
+
+ io.WriteString(bw, "\r\n")
+
+ // Write body and trailer
+ err = tw.WriteBody(bw)
+ if err != nil {
+ return err
+ }
+ bw.Flush()
+ return nil
}
-func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
+// extraHeaders may be nil
+func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) os.Error {
host := req.Host
if host == "" {
if req.URL == nil {
@@ -290,9 +343,12 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
host = req.URL.Host
}
- urlStr := req.RawURL
+ urlStr := req.URL.RawPath
+ if strings.HasPrefix(urlStr, "?") {
+ urlStr = "/" + urlStr // Issue 2344
+ }
if urlStr == "" {
- urlStr = valueOrDefault(req.URL.EncodedPath(), "/")
+ urlStr = valueOrDefault(req.URL.RawPath, valueOrDefault(req.URL.EncodedPath(), "/"))
if req.URL.RawQuery != "" {
urlStr += "?" + req.URL.RawQuery
}
@@ -303,6 +359,7 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
urlStr = req.URL.Scheme + "://" + host + urlStr
}
}
+ // TODO(bradfitz): escape at least newlines in urlStr?
bw := bufio.NewWriter(w)
fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), urlStr)
@@ -338,6 +395,13 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
return err
}
+ if extraHeaders != nil {
+ err = extraHeaders.Write(bw)
+ if err != nil {
+ return err
+ }
+ }
+
io.WriteString(bw, "\r\n")
// Write body and trailer
@@ -542,13 +606,14 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
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]
+ var rawurl string
+ req.Method, rawurl, req.Proto = f[0], f[1], f[2]
var ok bool
if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
return nil, &badStringError{"malformed HTTP version", req.Proto}
}
- if req.URL, err = url.ParseRequest(req.RawURL); err != nil {
+ if req.URL, err = url.ParseRequest(rawurl); err != nil {
return nil, err
}
@@ -608,27 +673,77 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
return req, nil
}
-// ParseForm parses the raw query.
-// For POST requests, it also parses the request body as a form.
+// MaxBytesReader is similar to io.LimitReader but is intended for
+// limiting the size of incoming request bodies. In contrast to
+// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
+// non-EOF error for a Read beyond the limit, and Closes the
+// underlying reader when its Close method is called.
+//
+// MaxBytesReader prevents clients from accidentally or maliciously
+// sending a large request and wasting server resources.
+func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
+ return &maxBytesReader{w: w, r: r, n: n}
+}
+
+type maxBytesReader struct {
+ w ResponseWriter
+ r io.ReadCloser // underlying reader
+ n int64 // max bytes remaining
+ stopped bool
+}
+
+func (l *maxBytesReader) Read(p []byte) (n int, err os.Error) {
+ if l.n <= 0 {
+ if !l.stopped {
+ l.stopped = true
+ if res, ok := l.w.(*response); ok {
+ res.requestTooLarge()
+ }
+ }
+ return 0, os.NewError("http: request body too large")
+ }
+ if int64(len(p)) > l.n {
+ p = p[:l.n]
+ }
+ n, err = l.r.Read(p)
+ l.n -= int64(n)
+ return
+}
+
+func (l *maxBytesReader) Close() os.Error {
+ return l.r.Close()
+}
+
+// ParseForm parses the raw query from the URL.
+//
+// For POST or PUT requests, it also parses the request body as a form.
+// If the request Body's size has not already been limited by MaxBytesReader,
+// the size is capped at 10MB.
+//
// ParseMultipartForm calls ParseForm automatically.
// It is idempotent.
func (r *Request) ParseForm() (err os.Error) {
if r.Form != nil {
return
}
-
if r.URL != nil {
r.Form, err = url.ParseQuery(r.URL.RawQuery)
}
- if r.Method == "POST" {
+ if r.Method == "POST" || r.Method == "PUT" {
if r.Body == nil {
return os.NewError("missing form body")
}
ct := r.Header.Get("Content-Type")
- 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))
+ ct, _, err := mime.ParseMediaType(ct)
+ switch {
+ case ct == "text/plain" || ct == "application/x-www-form-urlencoded" || ct == "":
+ var reader io.Reader = r.Body
+ maxFormSize := int64(1<<63 - 1)
+ if _, ok := r.Body.(*maxBytesReader); !ok {
+ maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
+ reader = io.LimitReader(r.Body, maxFormSize+1)
+ }
+ b, e := ioutil.ReadAll(reader)
if e != nil {
if err == nil {
err = e
@@ -652,8 +767,13 @@ func (r *Request) ParseForm() (err os.Error) {
r.Form.Add(k, value)
}
}
- case "multipart/form-data":
- // handled by ParseMultipartForm
+ case ct == "multipart/form-data":
+ // handled by ParseMultipartForm (which is calling us, or should be)
+ // TODO(bradfitz): there are too many possible
+ // orders to call too many functions here.
+ // Clean this up and write more tests.
+ // request_test.go contains the start of this,
+ // in TestRequestMultipartCallOrder.
default:
return &badStringError{"unknown Content-Type", ct}
}