diff options
Diffstat (limited to 'libgo/go/http/request.go')
-rw-r--r-- | libgo/go/http/request.go | 208 |
1 files changed, 49 insertions, 159 deletions
diff --git a/libgo/go/http/request.go b/libgo/go/http/request.go index 04bebaa..a7dc328 100644 --- a/libgo/go/http/request.go +++ b/libgo/go/http/request.go @@ -11,13 +11,13 @@ package http import ( "bufio" - "bytes" "container/vector" "fmt" "io" "io/ioutil" "mime" "mime/multipart" + "net/textproto" "os" "strconv" "strings" @@ -90,7 +90,7 @@ type Request struct { // The request parser implements this by canonicalizing the // name, making the first character and any characters // following a hyphen uppercase and the rest lowercase. - Header map[string]string + Header Header // The message body. Body io.ReadCloser @@ -133,7 +133,7 @@ type Request struct { // Trailer maps trailer keys to values. Like for Header, if the // response has multiple trailer lines with the same key, they will be // concatenated, delimited by commas. - Trailer map[string]string + Trailer Header } // ProtoAtLeast returns whether the HTTP protocol used @@ -146,8 +146,8 @@ func (r *Request) ProtoAtLeast(major, minor int) bool { // 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 { + v := r.Header.Get("Content-Type") + if v == "" { return nil, ErrNotMultipart } d, params := mime.ParseMediaType(v) @@ -184,6 +184,17 @@ const defaultUserAgent = "Go http package" // If Body is present, Write forces "Transfer-Encoding: chunked" as a header // and then closes Body when finished sending it. func (req *Request) Write(w io.Writer) os.Error { + return req.write(w, false) +} + +// 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. +func (req *Request) WriteProxy(w io.Writer) os.Error { + return req.write(w, true) +} + +func (req *Request) write(w io.Writer, usingProxy bool) os.Error { host := req.Host if host == "" { host = req.URL.Host @@ -197,10 +208,19 @@ func (req *Request) Write(w io.Writer) os.Error { } } + if usingProxy { + if uri == "" || uri[0] != '/' { + uri = "/" + uri + } + uri = req.URL.Scheme + "://" + host + uri + } + fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri) // Header lines - fmt.Fprintf(w, "Host: %s\r\n", host) + if !usingProxy { + fmt.Fprintf(w, "Host: %s\r\n", host) + } fmt.Fprintf(w, "User-Agent: %s\r\n", valueOrDefault(req.UserAgent, defaultUserAgent)) if req.Referer != "" { fmt.Fprintf(w, "Referer: %s\r\n", req.Referer) @@ -277,78 +297,6 @@ func readLine(b *bufio.Reader) (s string, err os.Error) { return string(p), nil } -var colon = []byte{':'} - -// Read a key/value pair from b. -// A key/value has the form Key: Value\r\n -// and the Value can continue on multiple lines if each continuation line -// starts with a space. -func readKeyValue(b *bufio.Reader) (key, value string, err os.Error) { - line, e := readLineBytes(b) - if e != nil { - return "", "", e - } - if len(line) == 0 { - return "", "", nil - } - - // Scan first line for colon. - i := bytes.Index(line, colon) - if i < 0 { - goto Malformed - } - - key = string(line[0:i]) - if strings.Contains(key, " ") { - // Key field has space - no good. - goto Malformed - } - - // Skip initial space before value. - for i++; i < len(line); i++ { - if line[i] != ' ' { - break - } - } - value = string(line[i:]) - - // Look for extension lines, which must begin with space. - for { - c, e := b.ReadByte() - if c != ' ' { - if e != os.EOF { - b.UnreadByte() - } - break - } - - // Eat leading space. - for c == ' ' { - if c, e = b.ReadByte(); e != nil { - if e == os.EOF { - e = io.ErrUnexpectedEOF - } - return "", "", e - } - } - b.UnreadByte() - - // Read the rest of the line and add to value. - if line, e = readLineBytes(b); e != nil { - return "", "", e - } - value += " " + string(line) - - if len(value) >= maxValueLength { - return "", "", &badStringError{"value too long for key", key} - } - } - return key, value, nil - -Malformed: - return "", "", &badStringError{"malformed header line", string(line)} -} - // Convert decimal at s[i:len(s)] to integer, // returning value, string position where the digits stopped, // and whether there was a valid number (digits, not too big). @@ -367,8 +315,9 @@ func atoi(s string, i int) (n, i1 int, ok bool) { return n, i, true } -// Parse HTTP version: "HTTP/1.2" -> (1, 2, true). -func parseHTTPVersion(vers string) (int, int, bool) { +// ParseHTTPVersion parses a HTTP version string. +// "HTTP/1.0" returns (1, 0, true). +func ParseHTTPVersion(vers string) (major, minor int, ok bool) { if len(vers) < 5 || vers[0:5] != "HTTP/" { return 0, 0, false } @@ -376,7 +325,6 @@ func parseHTTPVersion(vers string) (int, int, bool) { if !ok || i >= len(vers) || vers[i] != '.' { return 0, 0, false } - var minor int minor, i, ok = atoi(vers, i+1) if !ok || i != len(vers) { return 0, 0, false @@ -384,43 +332,6 @@ func parseHTTPVersion(vers string) (int, int, bool) { return major, minor, true } -// 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 { - // 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. - var a []byte - upper := true - 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 - if v == '-' { - upper = true - } - } - if a != nil { - return string(a) - } - return s -} - type chunkedReader struct { r *bufio.Reader n uint64 // unread bytes in chunk @@ -486,11 +397,16 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err os.Error) { // ReadRequest reads and parses a request from b. func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { + + tp := textproto.NewReader(b) req = new(Request) // First line: GET /index.html HTTP/1.0 var s string - if s, err = readLine(b); err != nil { + if s, err = tp.ReadLine(); err != nil { + if err == os.EOF { + err = io.ErrUnexpectedEOF + } return nil, err } @@ -500,7 +416,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { } req.Method, req.RawURL, req.Proto = f[0], f[1], f[2] var ok bool - if req.ProtoMajor, req.ProtoMinor, ok = parseHTTPVersion(req.Proto); !ok { + if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok { return nil, &badStringError{"malformed HTTP version", req.Proto} } @@ -509,32 +425,11 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { } // Subsequent lines: Key: value. - nheader := 0 - req.Header = make(map[string]string) - for { - var key, value string - if key, value, err = readKeyValue(b); err != nil { - return nil, err - } - if key == "" { - break - } - if nheader++; nheader >= maxHeaderLines { - return nil, ErrHeaderTooLong - } - - key = CanonicalHeaderKey(key) - - // RFC 2616 says that if you send the same header key - // multiple times, it has to be semantically equivalent - // to concatenating the values separated by commas. - oldvalue, present := req.Header[key] - if present { - req.Header[key] = oldvalue + "," + value - } else { - req.Header[key] = value - } + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil { + return nil, err } + req.Header = Header(mimeHeader) // RFC2616: Must treat // GET /index.html HTTP/1.1 @@ -545,18 +440,18 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) { // the same. In the second case, any Host line is ignored. req.Host = req.URL.Host if req.Host == "" { - req.Host = req.Header["Host"] + req.Host = req.Header.Get("Host") } - req.Header["Host"] = "", false + req.Header.Del("Host") fixPragmaCacheControl(req.Header) // Pull out useful fields as a convenience to clients. - req.Referer = req.Header["Referer"] - req.Header["Referer"] = "", false + req.Referer = req.Header.Get("Referer") + req.Header.Del("Referer") - req.UserAgent = req.Header["User-Agent"] - req.Header["User-Agent"] = "", false + req.UserAgent = req.Header.Get("User-Agent") + req.Header.Del("User-Agent") // TODO: Parse specific header values: // Accept @@ -642,7 +537,7 @@ func (r *Request) ParseForm() (err os.Error) { if r.Body == nil { return os.ErrorString("missing form body") } - ct := r.Header["Content-Type"] + ct := r.Header.Get("Content-Type") switch strings.Split(ct, ";", 2)[0] { case "text/plain", "application/x-www-form-urlencoded", "": b, e := ioutil.ReadAll(r.Body) @@ -677,17 +572,12 @@ func (r *Request) FormValue(key string) string { } func (r *Request) expectsContinue() bool { - expectation, ok := r.Header["Expect"] - return ok && strings.ToLower(expectation) == "100-continue" + return strings.ToLower(r.Header.Get("Expect")) == "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") + return strings.Contains(strings.ToLower(r.Header.Get("Connection")), "keep-alive") } |