diff options
| author | Russ Cox <rsc@golang.org> | 2008-09-24 15:26:55 -0700 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2008-09-24 15:26:55 -0700 |
| commit | d7958892a5a7a352826380421b090e37c98b0111 (patch) | |
| tree | 923d12ecfd9480dafbb8120aae8791032131ec06 /src/lib/http/request.go | |
| parent | 19650c539ee3f8f9516f633c12a0280485737256 (diff) | |
| download | golang-d7958892a5a7a352826380421b090e37c98b0111.tar.gz | |
early HTTP library and trivial server
R=r
OCL=15777
CL=15791
Diffstat (limited to 'src/lib/http/request.go')
| -rw-r--r-- | src/lib/http/request.go | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/lib/http/request.go b/src/lib/http/request.go new file mode 100644 index 000000000..bed911eb4 --- /dev/null +++ b/src/lib/http/request.go @@ -0,0 +1,300 @@ +// 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. + +// HTTP Request reading and parsing. + +package http + +import ( + "bufio"; + "http"; + "os"; + "strings" +) + +const ( + MaxLineLength = 1024; // assumed < bufio.DefaultBufSize + MaxValueLength = 1024; + MaxHeaderLines = 1024; +) + +export var ( + LineTooLong = os.NewError("http header line too long"); + ValueTooLong = os.NewError("http header value too long"); + HeaderTooLong = os.NewError("http header too long"); + BadHeader = os.NewError("malformed http header"); + BadRequest = os.NewError("invalid http request"); + BadHTTPVersion = os.NewError("unsupported http version"); +) + +// HTTP Request +export type Request struct { + method string; // GET, PUT,etc. + rawurl string; + url *URL; // URI after GET, PUT etc. + proto string; // "HTTP/1.0" + pmajor int; // 1 + pminor int; // 0 + + header *map[string] string; + + close bool; + host string; + referer string; + useragent string; +} + +// Read a line of bytes (up to \n) from b. +// Give up if the line exceeds MaxLineLength. +// The returned bytes are a pointer into storage in +// the bufio, so they are only valid until the next bufio read. +func ReadLineBytes(b *bufio.BufRead) (p *[]byte, err *os.Error) { + if p, err = b.ReadLineSlice('\n'); err != nil { + return nil, err + } + if len(p) >= MaxLineLength { + return nil, LineTooLong + } + + // Chop off trailing white space. + var i int + for i = len(p); i > 0; i-- { + if c := p[i-1]; c != ' ' && c != '\r' && c != '\t' && c != '\n' { + break + } + } + return p[0:i], nil +} + +// ReadLineByte, but convert the bytes into a string. +func ReadLine(b *bufio.BufRead) (s string, err *os.Error) { + p, e := ReadLineBytes(b) + if e != nil { + return "", e + } + return string(p), nil +} + +// 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.BufRead) (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. + for i := 0; i < len(line); i++ { + switch line[i] { + case ' ': + // Key field has space - no good. + return "", "", BadHeader + case ':': + key = string(line[0:i]); + // Skip initial space before value. + for i++; i < len(line); i++ { + if line[i] != ' ' { + break + } + } + value = string(line[i:len(line)]) + + // Look for extension lines, which must begin with space. + for { + var c byte; + + if c, e = b.ReadByte(); e != nil { + return "", "", e + } + if c != ' ' { + // Not leading space; stop. + b.UnreadByte() + break + } + + // Eat leading space. + for c == ' ' { + if c, e = b.ReadByte(); e != nil { + 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 "", "", ValueTooLong + } + } + return key, value, nil + } + } + + // Line ended before space or colon. + return "", "", BadHeader; +} + +// 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). +func atoi(s string, i int) (n, i1 int, ok bool) { + const Big = 1000000 + if i >= len(s) || s[i] < '0' || s[i] > '9' { + return 0, 0, false + } + n = 0 + for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + n = n*10 + int(s[i]-'0') + if n > Big { + return 0, 0, false + } + } + return n, i, true +} + +// Parse HTTP version: "HTTP/1.2" -> (1, 2, true). +func ParseHTTPVersion(vers string) (int, int, bool) { + if vers[0:5] != "HTTP/" { + return 0, 0, false + } + major, i, ok := atoi(vers, 5) + 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 + } + return major, minor, true +} + +// Read and parse a request from b. +export func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) { + req = new(Request); + + // First line: GET /index.html HTTP/1.0 + var s string + if s, err = ReadLine(b); err != nil { + return nil, err + } + + var f *[]string + if f = strings.split(s, " "); len(f) != 3 { + return nil, BadRequest + } + req.method, req.rawurl, req.proto = f[0], f[1], f[2] + var ok bool; + if req.pmajor, req.pminor, ok = ParseHTTPVersion(req.proto); !ok { + return nil, BadHTTPVersion + } + + if req.url, err = ParseURL(req.rawurl); err != nil { + return nil, err + } + + // Subsequent lines: Key: value. + nheader := 0; + req.header = new(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, HeaderTooLong + } + + // 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 + } + } + + // RFC2616: Must treat + // GET /index.html HTTP/1.1 + // Host: www.google.com + // and + // GET http://www.google.com/index.html HTTP/1.1 + // Host: doesntmatter + // the same. In the second case, any Host line is ignored. + if v, have := req.header["Host"]; have && req.url.host == "" { + req.host = v + } + + // RFC2616: Should treat + // Pragma: no-cache + // like + // Cache-control: no-cache + if v, have := req.header["Pragma"]; have && v == "no-cache" { + if cc, havecc := req.header["Cache-control"]; !havecc { + req.header["Cache-control"] = "no-cache" + } + } + + // Determine whether to hang up after sending the reply. + if req.pmajor < 1 || (req.pmajor == 1 && req.pminor < 1) { + req.close = true + } else if v, have := req.header["Connection"]; have { + // TODO: Should split on commas, toss surrounding white space, + // and check each field. + if v == "close" { + req.close = true + } + } + + // Pull out useful fields as a convenience to clients. + if v, have := req.header["Referer"]; have { + req.referer = v + } + if v, have := req.header["User-Agent"]; have { + req.useragent = v + } + + + // TODO: Parse specific header values: + // Accept + // Accept-Encoding + // Accept-Language + // Authorization + // Cache-Control + // Connection + // Date + // Expect + // From + // If-Match + // If-Modified-Since + // If-None-Match + // If-Range + // If-Unmodified-Since + // Max-Forwards + // Proxy-Authorization + // Referer [sic] + // TE (transfer-codings) + // Trailer + // Transfer-Encoding + // Upgrade + // User-Agent + // Via + // Warning + + return req, nil; +} |
