aboutsummaryrefslogtreecommitdiff
path: root/src/lib/http
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2009-06-09 09:53:44 -0700
committerRob Pike <r@golang.org>2009-06-09 09:53:44 -0700
commitd90e7cbac65c5792ce312ee82fbe03a5dfc98c6f (patch)
tree7032a11d0cac2ae4d3e90f7a189b575b5a50f848 /src/lib/http
parentbf5c0c957c3c3ea9add6cfd51b90c463cb4814b5 (diff)
downloadgo-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/Makefile85
-rw-r--r--src/lib/http/fs.go184
-rw-r--r--src/lib/http/request.go413
-rw-r--r--src/lib/http/server.go575
-rw-r--r--src/lib/http/status.go101
-rw-r--r--src/lib/http/triv.go159
-rw-r--r--src/lib/http/url.go303
-rw-r--r--src/lib/http/url_test.go348
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]");
- }
- }
-}
-