aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/http/request.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/http/request.go')
-rw-r--r--libgo/go/http/request.go263
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 {