diff options
| author | Rob Pike <r@golang.org> | 2009-06-09 09:53:44 -0700 |
|---|---|---|
| committer | Rob Pike <r@golang.org> | 2009-06-09 09:53:44 -0700 |
| commit | d90e7cbac65c5792ce312ee82fbe03a5dfc98c6f (patch) | |
| tree | 7032a11d0cac2ae4d3e90f7a189b575b5a50f848 /src/lib/http | |
| parent | bf5c0c957c3c3ea9add6cfd51b90c463cb4814b5 (diff) | |
| download | go-d90e7cbac65c5792ce312ee82fbe03a5dfc98c6f.tar.xz | |
mv src/lib to src/pkg
tests: all.bash passes, gobuild still works, godoc still works.
R=rsc
OCL=30096
CL=30102
Diffstat (limited to 'src/lib/http')
| -rw-r--r-- | src/lib/http/Makefile | 85 | ||||
| -rw-r--r-- | src/lib/http/fs.go | 184 | ||||
| -rw-r--r-- | src/lib/http/request.go | 413 | ||||
| -rw-r--r-- | src/lib/http/server.go | 575 | ||||
| -rw-r--r-- | src/lib/http/status.go | 101 | ||||
| -rw-r--r-- | src/lib/http/triv.go | 159 | ||||
| -rw-r--r-- | src/lib/http/url.go | 303 | ||||
| -rw-r--r-- | src/lib/http/url_test.go | 348 |
8 files changed, 0 insertions, 2168 deletions
diff --git a/src/lib/http/Makefile b/src/lib/http/Makefile deleted file mode 100644 index 0a029497c9..0000000000 --- a/src/lib/http/Makefile +++ /dev/null @@ -1,85 +0,0 @@ -# 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. - -# DO NOT EDIT. Automatically generated by gobuild. -# gobuild -m >Makefile - -D= - -include $(GOROOT)/src/Make.$(GOARCH) -AR=gopack - -default: packages - -clean: - rm -rf *.[$(OS)] *.a [$(OS)].out _obj - -test: packages - gotest - -coverage: packages - gotest - 6cov -g `pwd` | grep -v '_test\.go:' - -%.$O: %.go - $(GC) -I_obj $*.go - -%.$O: %.c - $(CC) $*.c - -%.$O: %.s - $(AS) $*.s - -O1=\ - status.$O\ - url.$O\ - -O2=\ - request.$O\ - -O3=\ - server.$O\ - -O4=\ - fs.$O\ - - -phases: a1 a2 a3 a4 -_obj$D/http.a: phases - -a1: $(O1) - $(AR) grc _obj$D/http.a status.$O url.$O - rm -f $(O1) - -a2: $(O2) - $(AR) grc _obj$D/http.a request.$O - rm -f $(O2) - -a3: $(O3) - $(AR) grc _obj$D/http.a server.$O - rm -f $(O3) - -a4: $(O4) - $(AR) grc _obj$D/http.a fs.$O - rm -f $(O4) - - -newpkg: clean - mkdir -p _obj$D - $(AR) grc _obj$D/http.a - -$(O1): newpkg -$(O2): a1 -$(O3): a2 -$(O4): a3 -$(O5): a4 - -nuke: clean - rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/http.a - -packages: _obj$D/http.a - -install: packages - test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D - cp _obj$D/http.a $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/http.a diff --git a/src/lib/http/fs.go b/src/lib/http/fs.go deleted file mode 100644 index 108734c47f..0000000000 --- a/src/lib/http/fs.go +++ /dev/null @@ -1,184 +0,0 @@ -// 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 file system request handler - -package http - -import ( - "fmt"; - "http"; - "io"; - "os"; - "path"; - "strings"; - "utf8"; -) - -// TODO this should be in a mime package somewhere -var contentByExt = map[string] string { - ".css": "text/css", - ".gif": "image/gif", - ".html": "text/html; charset=utf-8", - ".jpg": "image/jpeg", - ".js": "application/x-javascript", - ".png": "image/png", -} - -// Heuristic: b is text if it is valid UTF-8 and doesn't -// contain any unprintable ASCII or Unicode characters. -func isText(b []byte) bool { - for len(b) > 0 && utf8.FullRune(b) { - rune, size := utf8.DecodeRune(b); - if size == 1 && rune == utf8.RuneError { - // decoding error - return false; - } - if 0x80 <= rune && rune <= 0x9F { - return false; - } - if rune < ' ' { - switch rune { - case '\n', '\r', '\t': - // okay - default: - // binary garbage - return false; - } - } - b = b[size:len(b)]; - } - return true; -} - -func dirList(c *Conn, f *os.File) { - fmt.Fprintf(c, "<pre>\n"); - for { - dirs, err := f.Readdir(100); - if err != nil || len(dirs) == 0 { - break - } - for i, d := range dirs { - name := d.Name; - if d.IsDirectory() { - name += "/" - } - // TODO htmlescape - fmt.Fprintf(c, "<a href=\"%s\">%s</a>\n", name, name); - } - } - fmt.Fprintf(c, "</pre>\n"); -} - - -func serveFileInternal(c *Conn, r *Request, name string, redirect bool) { - const indexPage = "/index.html"; - - // redirect to strip off any index.html - n := len(name) - len(indexPage); - if n >= 0 && name[n:len(name)] == indexPage { - http.Redirect(c, name[0:n+1], StatusMovedPermanently); - return; - } - - f, err := os.Open(name, os.O_RDONLY, 0); - if err != nil { - // TODO expose actual error? - NotFound(c, r); - return; - } - defer f.Close(); - - d, err1 := f.Stat(); - if err1 != nil { - // TODO expose actual error? - NotFound(c, r); - return; - } - - if redirect { - // redirect to canonical path: / at end of directory url - // r.Url.Path always begins with / - url := r.Url.Path; - if d.IsDirectory() { - if url[len(url)-1] != '/' { - http.Redirect(c, url + "/", StatusMovedPermanently); - return; - } - } else { - if url[len(url)-1] == '/' { - http.Redirect(c, url[0:len(url)-1], StatusMovedPermanently); - return; - } - } - } - - // use contents of index.html for directory, if present - if d.IsDirectory() { - index := name + indexPage; - ff, err := os.Open(index, os.O_RDONLY, 0); - if err == nil { - defer ff.Close(); - dd, err := ff.Stat(); - if err == nil { - name = index; - d = dd; - f = ff; - } - } - } - - if d.IsDirectory() { - dirList(c, f); - return; - } - - // serve file - // use extension to find content type. - ext := path.Ext(name); - if ctype, ok := contentByExt[ext]; ok { - c.SetHeader("Content-Type", ctype); - } else { - // read first chunk to decide between utf-8 text and binary - var buf [1024]byte; - n, err := io.FullRead(f, &buf); - b := buf[0:n]; - if isText(b) { - c.SetHeader("Content-Type", "text-plain; charset=utf-8"); - } else { - c.SetHeader("Content-Type", "application/octet-stream"); // generic binary - } - c.Write(b); - } - io.Copy(f, c); -} - -// ServeFile replies to the request with the contents of the named file or directory. -func ServeFile(c *Conn, r *Request, name string) { - serveFileInternal(c, r, name, false); -} - -type fileHandler struct { - root string; - prefix string; -} - -// FileServer returns a handler that serves HTTP requests -// with the contents of the file system rooted at root. -// It strips prefix from the incoming requests before -// looking up the file name in the file system. -func FileServer(root, prefix string) Handler { - return &fileHandler{root, prefix}; -} - -func (f *fileHandler) ServeHTTP(c *Conn, r *Request) { - path := r.Url.Path; - if !strings.HasPrefix(path, f.prefix) { - NotFound(c, r); - return; - } - path = path[len(f.prefix):len(path)]; - serveFileInternal(c, r, f.root + "/" + path, true); -} - diff --git a/src/lib/http/request.go b/src/lib/http/request.go deleted file mode 100644 index 76dd6f30c1..0000000000 --- a/src/lib/http/request.go +++ /dev/null @@ -1,413 +0,0 @@ -// 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. - -// The http package implements parsing of HTTP requests and URLs -// and provides an extensible HTTP server. -// -// In the future it should also implement parsing of HTTP replies -// and provide methods to fetch URLs via HTTP. -package http - -import ( - "bufio"; - "fmt"; - "http"; - "io"; - "os"; - "strconv"; - "strings"; -) - -const ( - maxLineLength = 1024; // assumed < bufio.DefaultBufSize - maxValueLength = 1024; - maxHeaderLines = 1024; -) - -// HTTP request parsing errors. -type ProtocolError struct { - os.ErrorString -} -var ( - LineTooLong = &ProtocolError{"http header line too long"}; - ValueTooLong = &ProtocolError{"http header value too long"}; - HeaderTooLong = &ProtocolError{"http header too long"}; - BadContentLength = &ProtocolError{"invalid content length"}; - ShortEntityBody = &ProtocolError{"entity body too short"}; - BadHeader = &ProtocolError{"malformed http header"}; - BadRequest = &ProtocolError{"invalid http request"}; - BadHTTPVersion = &ProtocolError{"unsupported http version"}; -) - -// 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. - Proto string; // "HTTP/1.0" - ProtoMajor int; // 1 - ProtoMinor int; // 0 - - // A header mapping request lines to their values. - // If the header says - // - // Accept-Language: en-us - // accept-encoding: gzip, deflate - // Connection: keep-alive - // - // then - // - // Header = map[string]string{ - // "Accept-Encoding": "en-us", - // "Accept-Language": "gzip, deflate", - // "Connection": "keep-alive" - // } - // - // HTTP defines that header names are case-insensitive. - // 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; - - // The message body. - Body io.Reader; - - // Whether to close the connection after replying to this request. - Close bool; - - // The host on which the URL is sought. - // Per RFC 2616, this is either the value of the Host: header - // 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; -} - -// ProtoAtLeast returns whether the HTTP protocol used -// in the request is at least major.minor. -func (r *Request) ProtoAtLeast(major, minor int) bool { - return r.ProtoMajor > major || - r.ProtoMajor == major && r.ProtoMinor >= minor -} - -// 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.Reader) (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 -} - -// readLineBytes, but convert the bytes into a string. -func readLine(b *bufio.Reader) (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.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. - 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 -} - -var cmap = make(map[string]string) - -// 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 { - if t, ok := cmap[s]; ok { - return t; - } - - // 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. - a := io.StringBytes(s); - upper := true; - for i,v := range a { - if upper && 'a' <= v && v <= 'z' { - a[i] = v + 'A' - 'a'; - } - if !upper && 'A' <= v && v <= 'Z' { - a[i] = v + 'a' - 'A'; - } - upper = false; - if v == '-' { - upper = true; - } - } - t := string(a); - cmap[s] = t; - return t; -} - -// ReadRequest reads and parses a request from b. -func ReadRequest(b *bufio.Reader) (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.ProtoMajor, req.ProtoMinor, 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 = 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, HeaderTooLong - } - - 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 - } - } - - // 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, present := req.Header["Host"]; present && req.Url.Host == "" { - req.Host = v - } - - // RFC2616: Should treat - // Pragma: no-cache - // like - // Cache-Control: no-cache - if v, present := req.Header["Pragma"]; present && v == "no-cache" { - if cc, presentcc := req.Header["Cache-Control"]; !presentcc { - req.Header["Cache-Control"] = "no-cache" - } - } - - // Determine whether to hang up after sending the reply. - if req.ProtoMajor < 1 || (req.ProtoMajor == 1 && req.ProtoMinor < 1) { - req.Close = true - } else if v, present := req.Header["Connection"]; present { - // 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, present := req.Header["Referer"]; present { - req.Referer = v - } - if v, present := req.Header["User-Agent"]; present { - 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 - - // A message body exists when either Content-Length or Transfer-Encoding - // headers are present. TODO: Handle Transfer-Encoding. - if v, present := req.Header["Content-Length"]; present { - length, err := strconv.Btoui64(v, 10); - if err != nil { - return nil, BadContentLength - } - // TODO: limit the Content-Length. This is an easy DoS vector. - raw := make([]byte, length); - n, err := b.Read(raw); - if err != nil || uint64(n) < length { - return nil, ShortEntityBody - } - req.Body = io.NewByteReader(raw); - } - - return req, nil -} diff --git a/src/lib/http/server.go b/src/lib/http/server.go deleted file mode 100644 index c1de5de789..0000000000 --- a/src/lib/http/server.go +++ /dev/null @@ -1,575 +0,0 @@ -// 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 server. See RFC 2616. - -// TODO(rsc): -// logging -// cgi support -// post support - -package http - -import ( - "bufio"; - "fmt"; - "http"; - "io"; - "log"; - "net"; - "os"; - "path"; - "strconv"; - "strings"; -) - -// Errors introduced by the HTTP server. -var ( - ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush"); - ErrHijacked = os.NewError("Conn has been hijacked"); -) - -type Conn struct - -// Objects implemeting the Handler interface can be -// registered to serve a particular path or subtree -// in the HTTP server. -type Handler interface { - ServeHTTP(*Conn, *Request); -} - -// A Conn represents the server side of a single active HTTP connection. -type Conn struct { - RemoteAddr string; // network address of remote side - Req *Request; // current HTTP request - - rwc io.ReadWriteCloser; // i/o connection - buf *bufio.ReadWriter; // buffered rwc - handler Handler; // request handler - hijacked bool; // connection has been hijacked by handler - - // state for the current reply - closeAfterReply bool; // close connection after this reply - chunking bool; // using chunked transfer encoding for reply body - wroteHeader bool; // reply header has been written - header map[string] string; // reply header parameters - written int64; // number of bytes written in body - status int; // status code passed to WriteHeader -} - -// Create new connection from rwc. -func newConn(rwc io.ReadWriteCloser, raddr string, handler Handler) (c *Conn, err os.Error) { - c = new(Conn); - c.RemoteAddr = raddr; - c.handler = handler; - c.rwc = rwc; - br := bufio.NewReader(rwc); - bw := bufio.NewWriter(rwc); - c.buf = bufio.NewReadWriter(br, bw); - return c, nil -} - -func (c *Conn) SetHeader(hdr, val string) - -// Read next request from connection. -func (c *Conn) readRequest() (req *Request, err os.Error) { - if c.hijacked { - return nil, ErrHijacked - } - if req, err = ReadRequest(c.buf.Reader); err != nil { - return nil, err - } - - // Reset per-request connection state. - c.header = make(map[string] string); - c.wroteHeader = false; - c.Req = req; - - // Default output is HTML encoded in UTF-8. - c.SetHeader("Content-Type", "text/html; charset=utf-8"); - - if req.ProtoAtLeast(1, 1) { - // HTTP/1.1 or greater: use chunked transfer encoding - // to avoid closing the connection at EOF. - c.chunking = true; - c.SetHeader("Transfer-Encoding", "chunked"); - } else { - // HTTP version < 1.1: cannot do chunked transfer - // encoding, so signal EOF by closing connection. - // Could avoid closing the connection if there is - // a Content-Length: header in the response, - // but everyone who expects persistent connections - // does HTTP/1.1 now. - c.closeAfterReply = true; - c.chunking = false; - } - - return req, nil -} - -// SetHeader sets a header line in the eventual reply. -// For example, SetHeader("Content-Type", "text/html; charset=utf-8") -// will result in the header line -// -// Content-Type: text/html; charset=utf-8 -// -// being sent. UTF-8 encoded HTML is the default setting for -// Content-Type in this library, so users need not make that -// particular call. Calls to SetHeader after WriteHeader (or Write) -// are ignored. -func (c *Conn) SetHeader(hdr, val string) { - c.header[CanonicalHeaderKey(hdr)] = val; -} - -// WriteHeader sends an HTTP response header with status code. -// If WriteHeader is not called explicitly, the first call to Write -// will trigger an implicit WriteHeader(http.StatusOK). -// Thus explicit calls to WriteHeader are mainly used to -// send error codes. -func (c *Conn) WriteHeader(code int) { - if c.hijacked { - log.Stderr("http: Conn.WriteHeader on hijacked connection"); - return - } - if c.wroteHeader { - log.Stderr("http: multiple Conn.WriteHeader calls"); - return - } - c.wroteHeader = true; - c.status = code; - c.written = 0; - if !c.Req.ProtoAtLeast(1, 0) { - return - } - proto := "HTTP/1.0"; - if c.Req.ProtoAtLeast(1, 1) { - proto = "HTTP/1.1"; - } - codestring := strconv.Itoa(code); - text, ok := statusText[code]; - if !ok { - text = "status code " + codestring; - } - io.WriteString(c.buf, proto + " " + codestring + " " + text + "\r\n"); - for k,v := range c.header { - io.WriteString(c.buf, k + ": " + v + "\r\n"); - } - io.WriteString(c.buf, "\r\n"); -} - -// Write writes the data to the connection as part of an HTTP reply. -// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) -// before writing the data. -func (c *Conn) Write(data []byte) (n int, err os.Error) { - if c.hijacked { - log.Stderr("http: Conn.Write on hijacked connection"); - return 0, ErrHijacked - } - if !c.wroteHeader { - c.WriteHeader(StatusOK); - } - if len(data) == 0 { - return 0, nil - } - - c.written += int64(len(data)); // ignoring errors, for errorKludge - - // TODO(rsc): if chunking happened after the buffering, - // then there would be fewer chunk headers. - // On the other hand, it would make hijacking more difficult. - if c.chunking { - fmt.Fprintf(c.buf, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt - } - n, err = c.buf.Write(data); - if err == nil && c.chunking { - if n != len(data) { - err = io.ErrShortWrite; - } - if err == nil { - io.WriteString(c.buf, "\r\n"); - } - } - - return n, err; -} - -// If this is an error reply (4xx or 5xx) -// and the handler wrote some data explaining the error, -// some browsers (i.e., Chrome, Internet Explorer) -// will show their own error instead unless the error is -// long enough. The minimum lengths used in those -// browsers are in the 256-512 range. -// Pad to 1024 bytes. -func errorKludge(c *Conn, req *Request) { - const min = 1024; - - // Is this an error? - if kind := c.status/100; kind != 4 && kind != 5 { - return; - } - - // Did the handler supply any info? Enough? - if c.written == 0 || c.written >= min { - return; - } - - // Is it text? ("Content-Type" is always in the map) - if s := c.header["Content-Type"]; len(s) < 5 || s[0:5] != "text/" { - return; - } - - // Is it a broken browser? - var msg string; - switch agent := req.UserAgent; { - case strings.Index(agent, "MSIE") >= 0: - msg = "Internet Explorer"; - case strings.Index(agent, "Chrome/") >= 0: - msg = "Chrome"; - default: - return; - } - msg += " would ignore this error page if this text weren't here.\n"; - io.WriteString(c, "\n"); - for c.written < min { - io.WriteString(c, msg); - } -} - -func (c *Conn) flush() { - if !c.wroteHeader { - c.WriteHeader(StatusOK); - } - errorKludge(c, c.Req); - if c.chunking { - io.WriteString(c.buf, "0\r\n"); - // trailer key/value pairs, followed by blank line - io.WriteString(c.buf, "\r\n"); - } - c.buf.Flush(); -} - -// Close the connection. -func (c *Conn) close() { - if c.buf != nil { - c.buf.Flush(); - c.buf = nil; - } - if c.rwc != nil { - c.rwc.Close(); - c.rwc = nil; - } -} - -// Serve a new connection. -func (c *Conn) serve() { - for { - req, err := c.readRequest(); - if err != nil { - break - } - // HTTP cannot have multiple simultaneous active requests. - // Until the server replies to this request, it can't read another, - // so we might as well run the handler in this goroutine. - c.handler.ServeHTTP(c, req); - if c.hijacked { - return; - } - c.flush(); - if c.closeAfterReply { - break; - } - } - c.close(); -} - -// Hijack lets the caller take over the connection. -// After a call to c.Hijack(), the HTTP server library -// will not do anything else with the connection. -// It becomes the caller's responsibility to manage -// and close the connection. -func (c *Conn) Hijack() (rwc io.ReadWriteCloser, buf *bufio.ReadWriter, err os.Error) { - if c.hijacked { - return nil, nil, ErrHijacked; - } - c.hijacked = true; - rwc = c.rwc; - buf = c.buf; - c.rwc = nil; - c.buf = nil; - return; -} - -// The HandlerFunc type is an adapter to allow the use of -// ordinary functions as HTTP handlers. If f is a function -// with the appropriate signature, HandlerFunc(f) is a -// Handler object that calls f. -type HandlerFunc func(*Conn, *Request) - -// ServeHTTP calls f(c, req). -func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) { - f(c, req); -} - -// Helper handlers - -// NotFound replies to the request with an HTTP 404 not found error. -func NotFound(c *Conn, req *Request) { - c.SetHeader("Content-Type", "text/plain; charset=utf-8"); - c.WriteHeader(StatusNotFound); - io.WriteString(c, "404 page not found\n"); -} - -// NotFoundHandler returns a simple request handler -// that replies to each request with a ``404 page not found'' reply. -func NotFoundHandler() Handler { - return HandlerFunc(NotFound) -} - -// Redirect replies to the request with a redirect to url, -// which may be a path relative to the request path. -func Redirect(c *Conn, url string, code int) { - // RFC2616 recommends that a short note "SHOULD" be included in the - // response because older user agents may not understand 301/307. - note := "<a href=\"%v\">" + statusText[code] + "</a>.\n"; - if c.Req.Method == "POST" { - note = ""; - } - - u, err := ParseURL(url); - if err != nil { - goto finish - } - - // If url was relative, make absolute by - // combining with request path. - // The browser would probably do this for us, - // but doing it ourselves is more reliable. - - // NOTE(rsc): RFC 2616 says that the Location - // line must be an absolute URI, like - // "http://www.google.com/redirect/", - // not a path like "/redirect/". - // Unfortunately, we don't know what to - // put in the host name section to get the - // client to connect to us again, so we can't - // know the right absolute URI to send back. - // Because of this problem, no one pays attention - // to the RFC; they all send back just a new path. - // So do we. - oldpath := c.Req.Url.Path; - if oldpath == "" { // should not happen, but avoid a crash if it does - oldpath = "/" - } - if u.Scheme == "" { - // no leading http://server - if url == "" || url[0] != '/' { - // make relative path absolute - olddir, oldfile := path.Split(oldpath); - url = olddir + url; - } - - // clean up but preserve trailing slash - trailing := url[len(url) - 1] == '/'; - url = path.Clean(url); - if trailing && url[len(url) - 1] != '/' { - url += "/"; - } - } - -finish: - c.SetHeader("Location", url); - c.WriteHeader(code); - fmt.Fprintf(c, note, url); -} - -// Redirect to a fixed URL -type redirectHandler struct { - url string; - code int; -} -func (rh *redirectHandler) ServeHTTP(c *Conn, req *Request) { - Redirect(c, rh.url, rh.code); -} - -// RedirectHandler returns a request handler that redirects -// each request it receives to the given url using the given -// status code. -func RedirectHandler(url string, code int) Handler { - return &redirectHandler{ url, code } -} - -// ServeMux is an HTTP request multiplexer. -// It matches the URL of each incoming request against a list of registered -// patterns and calls the handler for the pattern that -// most closely matches the URL. -// -// Patterns named fixed paths, like "/favicon.ico", -// or subtrees, like "/images/" (note the trailing slash). -// Patterns must begin with /. -// Longer patterns take precedence over shorter ones, so that -// if there are handlers registered for both "/images/" -// and "/images/thumbnails/", the latter handler will be -// called for paths beginning "/images/thumbnails/" and the -// former will receiver requests for any other paths in the -// "/images/" subtree. -// -// In the future, the pattern syntax may be relaxed to allow -// an optional host-name at the beginning of the pattern, -// so that a handler might register for the two patterns -// "/codesearch" and "codesearch.google.com/" -// without taking over requests for http://www.google.com/. -// -// ServeMux also takes care of sanitizing the URL request path, -// redirecting any request containing . or .. elements to an -// equivalent .- and ..-free URL. -type ServeMux struct { - m map[string] Handler -} - -// NewServeMux allocates and returns a new ServeMux. -func NewServeMux() *ServeMux { - return &ServeMux{make(map[string] Handler)}; -} - -// DefaultServeMux is the default ServeMux used by Serve. -var DefaultServeMux = NewServeMux(); - -// Does path match pattern? -func pathMatch(pattern, path string) bool { - if len(pattern) == 0 { - // should not happen - return false - } - n := len(pattern); - if pattern[n-1] != '/' { - return pattern == path - } - return len(path) >= n && path[0:n] == pattern; -} - -// Return the canonical path for p, eliminating . and .. elements. -func cleanPath(p string) string { - if p == "" { - return "/"; - } - if p[0] != '/' { - p = "/" + p; - } - np := path.Clean(p); - // path.Clean removes trailing slash except for root; - // put the trailing slash back if necessary. - if p[len(p)-1] == '/' && np != "/" { - np += "/"; - } - return np; -} - -// ServeHTTP dispatches the request to the handler whose -// pattern most closely matches the request URL. -func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) { - // Clean path to canonical form and redirect. - if p := cleanPath(req.Url.Path); p != req.Url.Path { - c.SetHeader("Location", p); - c.WriteHeader(StatusMovedPermanently); - return; - } - - // Most-specific (longest) pattern wins. - var h Handler; - var n = 0; - for k, v := range mux.m { - if !pathMatch(k, req.Url.Path) { - continue; - } - if h == nil || len(k) > n { - n = len(k); - h = v; - } - } - if h == nil { - h = NotFoundHandler(); - } - h.ServeHTTP(c, req); -} - -// Handle registers the handler for the given pattern. -func (mux *ServeMux) Handle(pattern string, handler Handler) { - if pattern == "" || pattern[0] != '/' { - panicln("http: invalid pattern", pattern); - } - - mux.m[pattern] = handler; - - // Helpful behavior: - // If pattern is /tree/, insert permanent redirect for /tree. - n := len(pattern); - if n > 0 && pattern[n-1] == '/' { - mux.m[pattern[0:n-1]] = RedirectHandler(pattern, StatusMovedPermanently); - } -} - -// Handle registers the handler for the given pattern -// in the DefaultServeMux. -func Handle(pattern string, handler Handler) { - DefaultServeMux.Handle(pattern, handler); -} - -// Serve accepts incoming HTTP connections on the listener l, -// creating a new service thread for each. The service threads -// read requests and then call handler to reply to them. -// Handler is typically nil, in which case the DefaultServeMux is used. -func Serve(l net.Listener, handler Handler) os.Error { - if handler == nil { - handler = DefaultServeMux; - } - for { - rw, raddr, e := l.Accept(); - if e != nil { - return e - } - c, err := newConn(rw, raddr, handler); - if err != nil { - continue; - } - go c.serve(); - } - panic("not reached") -} - -// ListenAndServe listens on the TCP network address addr -// and then calls Serve with handler to handle requests -// on incoming connections. Handler is typically nil, -// in which case the DefaultServeMux is used. -// -// A trivial example server is: -// -// package main -// -// import ( -// "http"; -// "io"; -// ) -// -// // hello world, the web server -// func HelloServer(c *http.Conn, req *http.Request) { -// io.WriteString(c, "hello, world!\n"); -// } -// -// func main() { -// http.Handle("/hello", http.HandlerFunc(HelloServer)); -// err := http.ListenAndServe(":12345", nil); -// if err != nil { -// panic("ListenAndServe: ", err.String()) -// } -// } -func ListenAndServe(addr string, handler Handler) os.Error { - l, e := net.Listen("tcp", addr); - if e != nil { - return e - } - e = Serve(l, handler); - l.Close(); - return e -} - diff --git a/src/lib/http/status.go b/src/lib/http/status.go deleted file mode 100644 index 6d1c5ab28a..0000000000 --- a/src/lib/http/status.go +++ /dev/null @@ -1,101 +0,0 @@ -// 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 - -// HTTP status codes, defined in RFC 2616. -const ( - StatusContinue = 100; - StatusSwitchingProtocols = 101; - - StatusOK = 200; - StatusCreated = 201; - StatusAccepted = 202; - StatusNonAuthoritativeInfo = 203; - StatusNoContent = 204; - StatusResetContent = 205; - StatusPartialContent = 206; - - StatusMultipleChoices = 300; - StatusMovedPermanently = 301; - StatusFound = 302; - StatusSeeOther = 303; - StatusNotModified = 304; - StatusUseProxy = 305; - StatusTemporaryRedirect = 307; - - StatusBadRequest = 400; - StatusUnauthorized = 401; - StatusPaymentRequired = 402; - StatusForbidden = 403; - StatusNotFound = 404; - StatusMethodNotAllowed = 405; - StatusNotAcceptable = 406; - StatusProxyAuthRequired = 407; - StatusRequestTimeout = 408; - StatusConflict = 409; - StatusGone = 410; - StatusLengthRequired = 411; - StatusPreconditionFailed = 412; - StatusRequestEntityTooLarge = 413; - StatusRequestURITooLong = 414; - StatusUnsupportedMediaType = 415; - StatusRequestedRangeNotSatisfiable = 416; - StatusExpectationFailed = 417; - - StatusInternalServerError = 500; - StatusNotImplemented = 501; - StatusBadGateway = 502; - StatusServiceUnavailable = 503; - StatusGatewayTimeout = 504; - StatusHTTPVersionNotSupported = 505; -) - -var statusText = map[int]string { - StatusContinue: "Continue", - StatusSwitchingProtocols: "Switching Protocols", - - StatusOK: "OK", - StatusCreated: "Created", - StatusAccepted: "Accepted", - StatusNonAuthoritativeInfo: "Non-Authoritative Information", - StatusNoContent: "No Content", - StatusResetContent: "Reset Content", - StatusPartialContent: "Partial Content", - - StatusMultipleChoices: "Multiple Choices", - StatusMovedPermanently: "Moved Permanently", - StatusFound: "Found", - StatusSeeOther: "See Other", - StatusNotModified: "Not Modified", - StatusUseProxy: "Use Proxy", - StatusTemporaryRedirect: "Temporary Redirect", - - StatusBadRequest: "Bad Request", - StatusUnauthorized: "Unauthorized", - StatusPaymentRequired: "Payment Required", - StatusForbidden: "Forbidden", - StatusNotFound: "Not Found", - StatusMethodNotAllowed: "Method Not Allowed", - StatusNotAcceptable: "Not Acceptable", - StatusProxyAuthRequired: "Proxy Authentication Required", - StatusRequestTimeout: "Request Timeout", - StatusConflict: "Conflict", - StatusGone: "Gone", - StatusLengthRequired: "Length Required", - StatusPreconditionFailed: "Precondition Failed", - StatusRequestEntityTooLarge: "Request Entity Too Large", - StatusRequestURITooLong: "Request URI Too Long", - StatusUnsupportedMediaType: "Unsupported Media Type", - StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", - StatusExpectationFailed: "Expectation Failed", - - StatusInternalServerError: "Internal Server Error", - StatusNotImplemented: "Not Implemented", - StatusBadGateway: "Bad Gateway", - StatusServiceUnavailable: "Service Unavailable", - StatusGatewayTimeout: "Gateway Timeout", - StatusHTTPVersionNotSupported: "HTTP Version Not Supported", -} - diff --git a/src/lib/http/triv.go b/src/lib/http/triv.go deleted file mode 100644 index fc95017697..0000000000 --- a/src/lib/http/triv.go +++ /dev/null @@ -1,159 +0,0 @@ -// 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 main - -import ( - "bufio"; - "exvar"; - "flag"; - "fmt"; - "http"; - "io"; - "log"; - "net"; - "os"; - "strconv"; -) - - -// hello world, the web server -var helloRequests = exvar.NewInt("hello-requests"); -func HelloServer(c *http.Conn, req *http.Request) { - helloRequests.Add(1); - io.WriteString(c, "hello, world!\n"); -} - -// Simple counter server. POSTing to it will set the value. -type Counter struct { - n int; -} - -// This makes Counter satisfy the exvar.Var interface, so we can export -// it directly. -func (ctr *Counter) String() string { - return fmt.Sprintf("%d", ctr.n) -} - -func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) { - switch req.Method { - case "GET": - ctr.n++; - case "POST": - buf := new(io.ByteBuffer); - io.Copy(req.Body, buf); - body := string(buf.Data()); - if n, err := strconv.Atoi(body); err != nil { - fmt.Fprintf(c, "bad POST: %v\nbody: [%v]\n", err, body); - } else { - ctr.n = n; - fmt.Fprint(c, "counter reset\n"); - } - } - fmt.Fprintf(c, "counter = %d\n", ctr.n); -} - -// simple file server -var webroot = flag.String("root", "/home/rsc", "web root directory") -var pathVar = exvar.NewMap("file-requests"); -func FileServer(c *http.Conn, req *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8"); - pathVar.Add(req.Url.Path, 1); - path := *webroot + req.Url.Path; // TODO: insecure: use os.CleanName - f, err := os.Open(path, os.O_RDONLY, 0); - if err != nil { - c.WriteHeader(http.StatusNotFound); - fmt.Fprintf(c, "open %s: %v\n", path, err); - return; - } - n, err1 := io.Copy(f, c); - fmt.Fprintf(c, "[%d bytes]\n", n); - f.Close(); -} - -// simple flag server -var booleanflag = flag.Bool("boolean", true, "another flag for testing") -func FlagServer(c *http.Conn, req *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8"); - fmt.Fprint(c, "Flags:\n"); - flag.VisitAll(func (f *flag.Flag) { - if f.Value.String() != f.DefValue { - fmt.Fprintf(c, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue); - } else { - fmt.Fprintf(c, "%s = %s\n", f.Name, f.Value.String()); - } - }); -} - -// simple argument server -func ArgServer(c *http.Conn, req *http.Request) { - for i, s := range os.Args { - fmt.Fprint(c, s, " "); - } -} - -// a channel (just for the fun of it) -type Chan chan int - -func ChanCreate() Chan { - c := make(Chan); - go func(c Chan) { - for x := 0;; x++ { - c <- x - } - }(c); - return c; -} - -func (ch Chan) ServeHTTP(c *http.Conn, req *http.Request) { - io.WriteString(c, fmt.Sprintf("channel send #%d\n", <-ch)); -} - -// exec a program, redirecting output -func DateServer(c *http.Conn, req *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8"); - r, w, err := os.Pipe(); - if err != nil { - fmt.Fprintf(c, "pipe: %s\n", err); - return; - } - pid, err := os.ForkExec("/bin/date", []string{"date"}, os.Environ(), "", []*os.File{nil, w, w}); - defer r.Close(); - w.Close(); - if err != nil { - fmt.Fprintf(c, "fork/exec: %s\n", err); - return; - } - io.Copy(r, c); - wait, err := os.Wait(pid, 0); - if err != nil { - fmt.Fprintf(c, "wait: %s\n", err); - return; - } - if !wait.Exited() || wait.ExitStatus() != 0 { - fmt.Fprintf(c, "date: %v\n", wait); - return; - } -} - -func main() { - flag.Parse(); - - // The counter is published as a variable directly. - ctr := new(Counter); - http.Handle("/counter", ctr); - exvar.Publish("counter", ctr); - - http.Handle("/go/", http.HandlerFunc(FileServer)); - http.Handle("/flags", http.HandlerFunc(FlagServer)); - http.Handle("/args", http.HandlerFunc(ArgServer)); - http.Handle("/go/hello", http.HandlerFunc(HelloServer)); - http.Handle("/chan", ChanCreate()); - http.Handle("/date", http.HandlerFunc(DateServer)); - err := http.ListenAndServe(":12345", nil); - if err != nil { - log.Crash("ListenAndServe: ", err) - } -} - diff --git a/src/lib/http/url.go b/src/lib/http/url.go deleted file mode 100644 index 0325b04eed..0000000000 --- a/src/lib/http/url.go +++ /dev/null @@ -1,303 +0,0 @@ -// 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. - -// Parse URLs (actually URIs, but that seems overly pedantic). -// RFC 2396 - -package http - -import ( - "os"; - "strings" -) - -// Errors introduced by ParseURL. -type BadURL struct { - os.ErrorString -} - -func ishex(c byte) bool { - switch { - case '0' <= c && c <= '9': - return true; - case 'a' <= c && c <= 'f': - return true; - case 'A' <= c && c <= 'F': - return true; - } - return false -} - -func unhex(c byte) byte { - switch { - case '0' <= c && c <= '9': - return c - '0'; - case 'a' <= c && c <= 'f': - return c - 'a' + 10; - case 'A' <= c && c <= 'F': - return c - 'A' + 10; - } - return 0 -} - -// Return true if the specified character should be escaped when appearing in a -// URL string. -// -// TODO: for now, this is a hack; it only flags a few common characters that have -// special meaning in URLs. That will get the job done in the common cases. -func shouldEscape(c byte) bool { - switch c { - case ' ', '?', '&', '=', '#', '+', '%': - return true; - } - return false; -} - -// URLUnescape unescapes a URL-encoded string, -// converting %AB into the byte 0xAB and '+' into ' ' (space). -// It returns a BadURL error if any % is not followed -// by two hexadecimal digits. -func URLUnescape(s string) (string, os.Error) { - // Count %, check that they're well-formed. - n := 0; - anyPlusses := false; - for i := 0; i < len(s); { - switch s[i] { - case '%': - n++; - if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { - return "", BadURL{"invalid hexadecimal escape"} - } - i += 3; - case '+': - anyPlusses = true; - i++; - default: - i++ - } - } - - if n == 0 && !anyPlusses { - return s, nil - } - - t := make([]byte, len(s)-2*n); - j := 0; - for i := 0; i < len(s); { - switch s[i] { - case '%': - t[j] = unhex(s[i+1]) << 4 | unhex(s[i+2]); - j++; - i += 3; - case '+': - t[j] = ' '; - j++; - i++; - default: - t[j] = s[i]; - j++; - i++; - } - } - return string(t), nil; -} - -// URLEscape converts a string into URL-encoded form. -func URLEscape(s string) string { - spaceCount, hexCount := 0, 0; - for i := 0; i < len(s); i++ { - c := s[i]; - if (shouldEscape(c)) { - if (c == ' ') { - spaceCount++; - } else { - hexCount++; - } - } - } - - if spaceCount == 0 && hexCount == 0 { - return s; - } - - t := make([]byte, len(s)+2*hexCount); - j := 0; - for i := 0; i < len(s); i++ { - c := s[i]; - if !shouldEscape(c) { - t[j] = s[i]; - j++; - } else if (c == ' ') { - t[j] = '+'; - j++; - } else { - t[j] = '%'; - t[j+1] = "0123456789abcdef"[c>>4]; - t[j+2] = "0123456789abcdef"[c&15]; - j += 3; - } - } - return string(t); -} - -// A URL represents a parsed URL (technically, a URI reference). -// The general form represented is: -// scheme://[userinfo@]host/path[?query][#fragment] -// The Raw, RawPath, and RawQuery fields are in "wire format" (special -// characters must be hex-escaped if not meant to have special meaning). -// All other fields are logical values; '+' or '%' represent themselves. -// -// Note, the reason for using wire format for the query is that it needs -// to be split into key/value pairs before decoding. -type URL struct { - Raw string; // the original string - Scheme string; // scheme - RawPath string; // //[userinfo@]host/path[?query][#fragment] - Authority string; // [userinfo@]host - Userinfo string; // userinfo - Host string; // host - Path string; // /path - RawQuery string; // query - Fragment string; // fragment -} - -// Maybe rawurl is of the form scheme:path. -// (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*) -// If so, return scheme, path; else return "", rawurl. -func getscheme(rawurl string) (scheme, path string, err os.Error) { - for i := 0; i < len(rawurl); i++ { - c := rawurl[i]; - switch { - case 'a' <= c && c <= 'z' ||'A' <= c && c <= 'Z': - // do nothing - case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.': - if i == 0 { - return "", rawurl, nil - } - case c == ':': - if i == 0 { - return "", "", BadURL{"missing protocol scheme"} - } - return rawurl[0:i], rawurl[i+1:len(rawurl)], nil - } - } - return "", rawurl, nil -} - -// Maybe s is of the form t c u. -// If so, return t, c u (or t, u if cutc == true). -// If not, return s, "". -func split(s string, c byte, cutc bool) (string, string) { - for i := 0; i < len(s); i++ { - if s[i] == c { - if cutc { - return s[0:i], s[i+1:len(s)] - } - return s[0:i], s[i:len(s)] - } - } - return s, "" -} - -// BUG(rsc): ParseURL should canonicalize the path, -// removing unnecessary . and .. elements. - -// ParseURL parses rawurl into a URL structure. -// The string rawurl is assumed not to have a #fragment suffix. -// (Web browsers strip #fragment before sending the URL to a web server.) -func ParseURL(rawurl string) (url *URL, err os.Error) { - if rawurl == "" { - return nil, BadURL{"empty url"} - } - url = new(URL); - url.Raw = rawurl; - - // split off possible leading "http:", "mailto:", etc. - var path string; - if url.Scheme, path, err = getscheme(rawurl); err != nil { - return nil, err - } - url.RawPath = path; - - // RFC 2396: a relative URI (no scheme) has a ?query, - // but absolute URIs only have query if path begins with / - if url.Scheme == "" || len(path) > 0 && path[0] == '/' { - path, url.RawQuery = split(path, '?', true); - } - - // Maybe path is //authority/path - if len(path) > 2 && path[0:2] == "//" { - url.Authority, path = split(path[2:len(path)], '/', false); - } - - // If there's no @, split's default is wrong. Check explicitly. - if strings.Index(url.Authority, "@") < 0 { - url.Host = url.Authority; - } else { - url.Userinfo, url.Host = split(url.Authority, '@', true); - } - - // What's left is the path. - // TODO: Canonicalize (remove . and ..)? - if url.Path, err = URLUnescape(path); err != nil { - return nil, err - } - - // Remove escapes from the Authority and Userinfo fields, and verify - // that Scheme and Host contain no escapes (that would be illegal). - if url.Authority, err = URLUnescape(url.Authority); err != nil { - return nil, err - } - if url.Userinfo, err = URLUnescape(url.Userinfo); err != nil { - return nil, err - } - if (strings.Index(url.Scheme, "%") >= 0) { - return nil, BadURL{"hexadecimal escape in scheme"} - } - if (strings.Index(url.Host, "%") >= 0) { - return nil, BadURL{"hexadecimal escape in host"} - } - - return url, nil -} - -// ParseURLReference is like ParseURL but allows a trailing #fragment. -func ParseURLReference(rawurlref string) (url *URL, err os.Error) { - // Cut off #frag. - rawurl, frag := split(rawurlref, '#', true); - if url, err = ParseURL(rawurl); err != nil { - return nil, err - } - if url.Fragment, err = URLUnescape(frag); err != nil { - return nil, err - } - return url, nil -} - -// String reassembles url into a valid URL string. -// -// There are redundant fields stored in the URL structure: -// the String method consults Scheme, Path, Host, Userinfo, -// RawQuery, and Fragment, but not Raw, RawPath or Authority. -func (url *URL) String() string { - result := ""; - if url.Scheme != "" { - result += url.Scheme + ":"; - } - if url.Host != "" || url.Userinfo != "" { - result += "//"; - if url.Userinfo != "" { - result += URLEscape(url.Userinfo) + "@"; - } - result += url.Host; - } - result += URLEscape(url.Path); - if url.RawQuery != "" { - result += "?" + url.RawQuery; - } - if url.Fragment != "" { - result += "#" + URLEscape(url.Fragment); - } - return result; -} diff --git a/src/lib/http/url_test.go b/src/lib/http/url_test.go deleted file mode 100644 index 8d8fabad5f..0000000000 --- a/src/lib/http/url_test.go +++ /dev/null @@ -1,348 +0,0 @@ -// 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"; - "os"; - "reflect"; - "testing"; -) - -// TODO(rsc): -// test URLUnescape -// test URLEscape -// test ParseURL - -type URLTest struct { - in string; - out *URL; - roundtrip string; // expected result of reserializing the URL; empty means same as "in". -} - -var urltests = []URLTest { - // no path - URLTest{ - "http://www.google.com", - &URL{ - "http://www.google.com", - "http", "//www.google.com", - "www.google.com", "", "www.google.com", - "", "", "" - }, - "" - }, - // path - URLTest{ - "http://www.google.com/", - &URL{ - "http://www.google.com/", - "http", "//www.google.com/", - "www.google.com", "", "www.google.com", - "/", "", "" - }, - "" - }, - // path with hex escaping... note that space roundtrips to + - URLTest{ - "http://www.google.com/file%20one%26two", - &URL{ - "http://www.google.com/file%20one%26two", - "http", "//www.google.com/file%20one%26two", - "www.google.com", "", "www.google.com", - "/file one&two", "", "" - }, - "http://www.google.com/file+one%26two" - }, - // user - URLTest{ - "ftp://webmaster@www.google.com/", - &URL{ - "ftp://webmaster@www.google.com/", - "ftp", "//webmaster@www.google.com/", - "webmaster@www.google.com", "webmaster", "www.google.com", - "/", "", "" - }, - "" - }, - // escape sequence in username - URLTest{ - "ftp://john%20doe@www.google.com/", - &URL{ - "ftp://john%20doe@www.google.com/", - "ftp", "//john%20doe@www.google.com/", - "john doe@www.google.com", "john doe", "www.google.com", - "/", "", "" - }, - "ftp://john+doe@www.google.com/" - }, - // query - URLTest{ - "http://www.google.com/?q=go+language", - &URL{ - "http://www.google.com/?q=go+language", - "http", "//www.google.com/?q=go+language", - "www.google.com", "", "www.google.com", - "/", "q=go+language", "" - }, - "" - }, - // query with hex escaping: NOT parsed - URLTest{ - "http://www.google.com/?q=go%20language", - &URL{ - "http://www.google.com/?q=go%20language", - "http", "//www.google.com/?q=go%20language", - "www.google.com", "", "www.google.com", - "/", "q=go%20language", "" - }, - "" - }, - // path without /, so no query parsing - URLTest{ - "http:www.google.com/?q=go+language", - &URL{ - "http:www.google.com/?q=go+language", - "http", "www.google.com/?q=go+language", - "", "", "", - "www.google.com/?q=go language", "", "" - }, - "http:www.google.com/%3fq%3dgo+language" - }, - // non-authority - URLTest{ - "mailto:/webmaster@golang.org", - &URL{ - "mailto:/webmaster@golang.org", - "mailto", "/webmaster@golang.org", - "", "", "", - "/webmaster@golang.org", "", "" - }, - "" - }, - // non-authority - URLTest{ - "mailto:webmaster@golang.org", - &URL{ - "mailto:webmaster@golang.org", - "mailto", "webmaster@golang.org", - "", "", "", - "webmaster@golang.org", "", "" - }, - "" - }, -} - -var urlnofragtests = []URLTest { - URLTest{ - "http://www.google.com/?q=go+language#foo", - &URL{ - "http://www.google.com/?q=go+language#foo", - "http", "//www.google.com/?q=go+language#foo", - "www.google.com", "", "www.google.com", - "/", "q=go+language#foo", "" - }, - "" - }, -} - -var urlfragtests = []URLTest { - URLTest{ - "http://www.google.com/?q=go+language#foo", - &URL{ - "http://www.google.com/?q=go+language", - "http", "//www.google.com/?q=go+language", - "www.google.com", "", "www.google.com", - "/", "q=go+language", "foo" - }, - "" - }, - URLTest{ - "http://www.google.com/?q=go+language#foo%26bar", - &URL{ - "http://www.google.com/?q=go+language", - "http", "//www.google.com/?q=go+language", - "www.google.com", "", "www.google.com", - "/", "q=go+language", "foo&bar" - }, - "" - }, -} - -// more useful string for debugging than fmt's struct printer -func ufmt(u *URL) string { - return fmt.Sprintf("%q, %q, %q, %q, %q, %q, %q, %q, %q", - u.Raw, u.Scheme, u.RawPath, u.Authority, u.Userinfo, - u.Host, u.Path, u.RawQuery, u.Fragment); -} - -func DoTest(t *testing.T, parse func(string) (*URL, os.Error), name string, tests []URLTest) { - for i, tt := range tests { - u, err := parse(tt.in); - if err != nil { - t.Errorf("%s(%q) returned error %s", name, tt.in, err); - continue; - } - if !reflect.DeepEqual(u, tt.out) { - t.Errorf("%s(%q):\n\thave %v\n\twant %v\n", - name, tt.in, ufmt(u), ufmt(tt.out)); - } - } -} - -func TestParseURL(t *testing.T) { - DoTest(t, ParseURL, "ParseURL", urltests); - DoTest(t, ParseURL, "ParseURL", urlnofragtests); -} - -func TestParseURLReference(t *testing.T) { - DoTest(t, ParseURLReference, "ParseURLReference", urltests); - DoTest(t, ParseURLReference, "ParseURLReference", urlfragtests); -} - -func DoTestString(t *testing.T, parse func(string) (*URL, os.Error), name string, tests []URLTest) { - for i, tt := range tests { - u, err := parse(tt.in); - if err != nil { - t.Errorf("%s(%q) returned error %s", name, tt.in, err); - continue; - } - s := u.String(); - expected := tt.in; - if len(tt.roundtrip) > 0 { - expected = tt.roundtrip; - } - if s != expected { - t.Errorf("%s(%q).String() == %q (expected %q)", name, tt.in, s, expected); - } - } -} - -func TestURLString(t *testing.T) { - DoTestString(t, ParseURL, "ParseURL", urltests); - DoTestString(t, ParseURL, "ParseURL", urlfragtests); - DoTestString(t, ParseURL, "ParseURL", urlnofragtests); - DoTestString(t, ParseURLReference, "ParseURLReference", urltests); - DoTestString(t, ParseURLReference, "ParseURLReference", urlfragtests); - DoTestString(t, ParseURLReference, "ParseURLReference", urlnofragtests); -} - -type URLEscapeTest struct { - in string; - out string; - err os.Error; -} - -var unescapeTests = []URLEscapeTest { - URLEscapeTest{ - "", - "", - nil - }, - URLEscapeTest{ - "abc", - "abc", - nil - }, - URLEscapeTest{ - "1%41", - "1A", - nil - }, - URLEscapeTest{ - "1%41%42%43", - "1ABC", - nil - }, - URLEscapeTest{ - "%4a", - "J", - nil - }, - URLEscapeTest{ - "%6F", - "o", - nil - }, - URLEscapeTest{ - "%", // not enough characters after % - "", - BadURL{"invalid hexadecimal escape"} - }, - URLEscapeTest{ - "%a", // not enough characters after % - "", - BadURL{"invalid hexadecimal escape"} - }, - URLEscapeTest{ - "%1", // not enough characters after % - "", - BadURL{"invalid hexadecimal escape"} - }, - URLEscapeTest{ - "123%45%6", // not enough characters after % - "", - BadURL{"invalid hexadecimal escape"} - }, - URLEscapeTest{ - "%zz", // invalid hex digits - "", - BadURL{"invalid hexadecimal escape"} - }, -} - -func TestURLUnescape(t *testing.T) { - for i, tt := range unescapeTests { - actual, err := URLUnescape(tt.in); - if actual != tt.out || (err != nil) != (tt.err != nil) { - t.Errorf("URLUnescape(%q) = %q, %s; want %q, %s", tt.in, actual, err, tt.out, tt.err); - } - } -} - -var escapeTests = []URLEscapeTest { - URLEscapeTest{ - "", - "", - nil - }, - URLEscapeTest{ - "abc", - "abc", - nil - }, - URLEscapeTest{ - "one two", - "one+two", - nil - }, - URLEscapeTest{ - "10%", - "10%25", - nil - }, - URLEscapeTest{ - " ?&=#+%!", - "+%3f%26%3d%23%2b%25!", - nil - }, -} - -func TestURLEscape(t *testing.T) { - for i, tt := range escapeTests { - actual := URLEscape(tt.in); - if tt.out != actual { - t.Errorf("URLEscape(%q) = %q, want %q", tt.in, actual, tt.out); - } - - // for bonus points, verify that escape:unescape is an identity. - roundtrip, err := URLUnescape(actual); - if roundtrip != tt.in || err != nil { - t.Errorf("URLUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]"); - } - } -} - |
