]> Cypherpunks repositories - gostls13.git/commitdiff
url: new package
authorRob Pike <r@golang.org>
Wed, 17 Aug 2011 03:36:02 +0000 (13:36 +1000)
committerRob Pike <r@golang.org>
Wed, 17 Aug 2011 03:36:02 +0000 (13:36 +1000)
This is just moving the URL code from package http into its own package,
which has been planned for a while.
Besides clarity, this also breaks a nascent dependency cycle the new template
package was about to introduce.

Add a gofix module, url, and use it to generate changes outside http and url.

Sadness about the churn, gladness about some of the naming improvements.

R=dsymonds, bradfitz, rsc, gustavo, r
CC=golang-dev
https://golang.org/cl/4893043

28 files changed:
misc/dashboard/builder/http.go
src/cmd/godoc/main.go
src/cmd/gofix/Makefile
src/cmd/gofix/url.go [new file with mode: 0644]
src/cmd/gofix/url_test.go [new file with mode: 0644]
src/pkg/Makefile
src/pkg/exp/template/funcs.go
src/pkg/http/Makefile
src/pkg/http/cgi/child.go
src/pkg/http/cgi/host.go
src/pkg/http/client.go
src/pkg/http/client_test.go
src/pkg/http/fs_test.go
src/pkg/http/readrequest_test.go
src/pkg/http/request.go
src/pkg/http/request_test.go
src/pkg/http/requestwrite_test.go
src/pkg/http/reverseproxy.go
src/pkg/http/reverseproxy_test.go
src/pkg/http/serve_test.go
src/pkg/http/server.go
src/pkg/http/transport.go
src/pkg/http/transport_test.go
src/pkg/url/Makefile [new file with mode: 0644]
src/pkg/url/url.go [moved from src/pkg/http/url.go with 75% similarity]
src/pkg/url/url_test.go [moved from src/pkg/http/url_test.go with 87% similarity]
src/pkg/websocket/client.go
src/pkg/websocket/websocket_test.go

index 98400c51a81972cbef8b2c512b1dd1bbd527bb8f..abef8faa48eb66c48b18fa3de6802176ddcece5b 100644 (file)
@@ -12,6 +12,7 @@ import (
        "log"
        "os"
        "strconv"
+       "url"
 )
 
 type param map[string]string
@@ -26,7 +27,7 @@ func dash(meth, cmd string, resp interface{}, args param) os.Error {
                log.Println("dash", cmd, args)
        }
        cmd = "http://" + *dashboard + "/" + cmd
-       vals := make(http.Values)
+       vals := make(url.Values)
        for k, v := range args {
                vals.Add(k, v)
        }
index e4c3023969446477be6207f7e37e129152c1a9cd..89b12b9acbb6a79e544a6096e63d426884c9b334 100644 (file)
@@ -44,6 +44,7 @@ import (
        "runtime"
        "strings"
        "time"
+       "url"
 )
 
 const defaultAddr = ":6060" // default webserver address
@@ -160,7 +161,7 @@ func loggingHandler(h http.Handler) http.Handler {
 }
 
 func remoteSearch(query string) (res *http.Response, err os.Error) {
-       search := "/search?f=text&q=" + http.URLEscape(query)
+       search := "/search?f=text&q=" + url.QueryEscape(query)
 
        // list of addresses to try
        var addrs []string
index 7ce21e8aab5163350e06e2bacf86acd14d53db3a..22033d7f811d9c63e29297912a946804f88f1e2b 100644 (file)
@@ -23,6 +23,7 @@ GOFILES=\
        sortslice.go\
        stringssplit.go\
        typecheck.go\
+       url.go\
 
 include ../../Make.cmd
 
diff --git a/src/cmd/gofix/url.go b/src/cmd/gofix/url.go
new file mode 100644 (file)
index 0000000..047fb19
--- /dev/null
@@ -0,0 +1,116 @@
+// Copyright 2011 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 (
+       "fmt"
+       "os"
+       "go/ast"
+)
+
+var _ fmt.Stringer
+var _ os.Error
+
+var urlFix = fix{
+       "url",
+       url,
+       `Move the URL pieces of package http into a new package, url.
+
+http://codereview.appspot.com/4893043
+`,
+}
+
+func init() {
+       register(urlFix)
+}
+
+var urlRenames = []struct{ in, out string }{
+       {"ParseURL", "Parse"},
+       {"ParseURLReference", "ParseWithReference"},
+       {"ParseQuery", "ParseQuery"},
+       {"Values", "Values"},
+       {"URLEscape", "QueryEscape"},
+       {"URLUnescape", "QueryUnescape"},
+       {"URLError", "Error"},
+       {"URLEscapeError", "EscapeError"},
+}
+
+func url(f *ast.File) bool {
+       if imports(f, "url") || !imports(f, "http") {
+               return false
+       }
+
+       fixed := false
+
+       // Update URL code.
+       urlWalk := func(n interface{}) {
+               // Is it an identifier?
+               if ident, ok := n.(*ast.Ident); ok && ident.Name == "url" {
+                       ident.Name = "url_"
+                       return
+               }
+               // Find declared identifiers called url that might be confused.
+               // TODO: Why does gofix not walk the Names in a ValueSpec?
+               // TODO: Just a bug; fix later as it will have consequences.
+               if valSpec, ok := n.(*ast.ValueSpec); ok {
+                       for _, ident := range valSpec.Names {
+                               if ident.Name == "url" {
+                                       ident.Name = "url_"
+                               }
+                       }
+               }
+               // Parameter and result names.
+               if fn, ok := n.(*ast.FuncType); ok {
+                       fixed = urlDoFields(fn.Params) || fixed
+                       fixed = urlDoFields(fn.Results) || fixed
+               }
+       }
+
+       // Fix up URL code and add import, at most once.
+       fix := func() {
+               if fixed {
+                       return
+               }
+               walk(f, urlWalk)
+               addImport(f, "url")
+               fixed = true
+       }
+
+       walk(f, func(n interface{}) {
+               // Rename functions and methods.
+               if expr, ok := n.(ast.Expr); ok {
+                       for _, s := range urlRenames {
+                               if isPkgDot(expr, "http", s.in) {
+                                       fix()
+                                       expr.(*ast.SelectorExpr).X.(*ast.Ident).Name = "url"
+                                       expr.(*ast.SelectorExpr).Sel.Name = s.out
+                                       return
+                               }
+                       }
+               }
+       })
+
+       // Remove the http import if no longer needed.
+       if fixed && !usesImport(f, "http") {
+               deleteImport(f, "http")
+       }
+
+       return fixed
+}
+
+func urlDoFields(list *ast.FieldList) (fixed bool) {
+       if list == nil {
+               return
+       }
+       for _, field := range list.List {
+               for _, ident := range field.Names {
+                       if ident.Name == "url" {
+                               fixed = true
+                               ident.Name = "url_"
+                       }
+               }
+       }
+       return
+}
diff --git a/src/cmd/gofix/url_test.go b/src/cmd/gofix/url_test.go
new file mode 100644 (file)
index 0000000..1a7095a
--- /dev/null
@@ -0,0 +1,147 @@
+// Copyright 2011 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
+
+func init() {
+       addTestCases(urlTests)
+}
+
+var urlTests = []testCase{
+       {
+               Name: "url.0",
+               In: `package main
+
+import (
+       "http"
+)
+
+func f() {
+       http.ParseURL(a)
+       http.ParseURLReference(a)
+       http.ParseQuery(a)
+       m := http.Values{a: b}
+       http.URLEscape(a)
+       http.URLUnescape(a)
+       var x http.URLError
+       var y http.URLEscapeError
+}
+`,
+               Out: `package main
+
+import "url"
+
+func f() {
+       url.Parse(a)
+       url.ParseWithReference(a)
+       url.ParseQuery(a)
+       m := url.Values{a: b}
+       url.QueryEscape(a)
+       url.QueryUnescape(a)
+       var x url.Error
+       var y url.EscapeError
+}
+`,
+       },
+       {
+               Name: "url.1",
+               In: `package main
+
+import (
+       "http"
+)
+
+func f() {
+       http.ParseURL(a)
+       var x http.Request
+}
+`,
+               Out: `package main
+
+import (
+       "http"
+       "url"
+)
+
+func f() {
+       url.Parse(a)
+       var x http.Request
+}
+`,
+       },
+       {
+               Name: "url.2",
+               In: `package main
+
+import (
+       "http"
+)
+
+func f() {
+       http.ParseURL(a)
+       var url = 23
+       url, x := 45, y
+}
+
+func g(url string) string {
+       return url
+}
+
+func h() (url string) {
+       return url
+}
+`,
+               Out: `package main
+
+import "url"
+
+func f() {
+       url.Parse(a)
+       var url_ = 23
+       url_, x := 45, y
+}
+
+func g(url_ string) string {
+       return url_
+}
+
+func h() (url_ string) {
+       return url_
+}
+`,
+       },
+       {
+               Name: "url.3",
+               In: `package main
+
+import "http"
+
+type U struct{ url string }
+
+func f() {
+       var u U
+       u.url = "x"
+}
+
+func (url *T) m() string {
+       return url
+}
+`,
+               Out: `package main
+
+import "http"
+
+type U struct{ url string }
+
+func f() {
+       var u U
+       u.url = "x"
+}
+
+func (url *T) m() string {
+       return url
+}
+`,
+       },
+}
index ec9a070bd1f3eec08088df8ba3f5e106e8fe2dd1..388e2a1d319be57626ebf1c2060f9ffc6a83c250 100644 (file)
@@ -164,6 +164,7 @@ DIRS=\
        time\
        try\
        unicode\
+       url\
        utf16\
        utf8\
        websocket\
index 6de46aa451a4ebef620fe8391bbbb6d3db5e0489..feb1fd82c72fa794eb3b6684e993b1492aac2386 100644 (file)
@@ -7,12 +7,12 @@ package template
 import (
        "bytes"
        "fmt"
-       "http"
        "io"
        "os"
        "reflect"
        "strings"
        "unicode"
+       "url"
        "utf8"
 )
 
@@ -364,5 +364,5 @@ func URLQueryEscaper(args ...interface{}) string {
        if !ok {
                s = fmt.Sprint(args...)
        }
-       return http.URLEscape(s)
+       return url.QueryEscape(s)
 }
index b8bc093d49c714b9dec2c6dd8358171658511ffa..df4ab95101b39adce1bd32dfc4fc7880f0ed24f4 100644 (file)
@@ -22,6 +22,5 @@ GOFILES=\
        status.go\
        transfer.go\
        transport.go\
-       url.go\
 
 include ../../Make.pkg
index 8b74d705483625d2f76460cea8bfb49736ff1ec6..8d0eca8d55bfafa8e740fe11d62c515c4201b7c4 100644 (file)
@@ -18,6 +18,7 @@ import (
        "os"
        "strconv"
        "strings"
+       "url"
 )
 
 // Request returns the HTTP request as represented in the current
@@ -93,7 +94,7 @@ func RequestFromMap(params map[string]string) (*http.Request, os.Error) {
                // Hostname is provided, so we can reasonably construct a URL,
                // even if we have to assume 'http' for the scheme.
                r.RawURL = "http://" + r.Host + params["REQUEST_URI"]
-               url, err := http.ParseURL(r.RawURL)
+               url, err := url.Parse(r.RawURL)
                if err != nil {
                        return nil, os.NewError("cgi: failed to parse host and REQUEST_URI into a URL: " + r.RawURL)
                }
@@ -103,7 +104,7 @@ func RequestFromMap(params map[string]string) (*http.Request, os.Error) {
        // failed to parse
        if r.URL == nil {
                r.RawURL = params["REQUEST_URI"]
-               url, err := http.ParseURL(r.RawURL)
+               url, err := url.Parse(r.RawURL)
                if err != nil {
                        return nil, os.NewError("cgi: failed to parse REQUEST_URI into a URL: " + r.RawURL)
                }
index 93825b3919707f74f79549cdf3f3b5e7db27ce9a..f7de89f9974ecae9a2ef4faa07e5e8a171e3dd3a 100644 (file)
@@ -276,7 +276,7 @@ func (h *Handler) printf(format string, v ...interface{}) {
 }
 
 func (h *Handler) handleInternalRedirect(rw http.ResponseWriter, req *http.Request, path string) {
-       url, err := req.URL.ParseURL(path)
+       url, err := req.URL.Parse(path)
        if err != nil {
                rw.WriteHeader(http.StatusInternalServerError)
                h.printf("cgi: error resolving local URI path %q: %v", path, err)
index 6ea7dee03f68661d9181b1d2f16280dc3e208f9b..44b3443fc40023ac1d236f68b441d802a9b52cb6 100644 (file)
@@ -12,6 +12,7 @@ import (
        "io"
        "os"
        "strings"
+       "url"
 )
 
 // A Client is an HTTP client. Its zero value (DefaultClient) is a usable client
@@ -158,7 +159,7 @@ func (c *Client) Get(url string) (r *Response, err os.Error) {
 func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err os.Error) {
        // TODO: if/when we add cookie support, the redirected request shouldn't
        // necessarily supply the same cookies as the original.
-       var base *URL
+       var base *url.URL
        redirectChecker := c.CheckRedirect
        if redirectChecker == nil {
                redirectChecker = defaultCheckRedirect
@@ -166,13 +167,13 @@ func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err os.Error)
        var via []*Request
 
        req := ireq
-       url := "" // next relative or absolute URL to fetch (after first request)
+       urlStr := "" // next relative or absolute URL to fetch (after first request)
        for redirect := 0; ; redirect++ {
                if redirect != 0 {
                        req = new(Request)
                        req.Method = ireq.Method
                        req.Header = make(Header)
-                       req.URL, err = base.ParseURL(url)
+                       req.URL, err = base.Parse(urlStr)
                        if err != nil {
                                break
                        }
@@ -190,13 +191,13 @@ func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err os.Error)
                        }
                }
 
-               url = req.URL.String()
+               urlStr = req.URL.String()
                if r, err = send(req, c.Transport); err != nil {
                        break
                }
                if shouldRedirect(r.StatusCode) {
                        r.Body.Close()
-                       if url = r.Header.Get("Location"); url == "" {
+                       if urlStr = r.Header.Get("Location"); urlStr == "" {
                                err = os.NewError(fmt.Sprintf("%d response missing Location header", r.StatusCode))
                                break
                        }
@@ -208,7 +209,7 @@ func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err os.Error)
        }
 
        method := ireq.Method
-       err = &URLError{method[0:1] + strings.ToLower(method[1:]), url, err}
+       err = &url.Error{method[0:1] + strings.ToLower(method[1:]), urlStr, err}
        return
 }
 
@@ -246,7 +247,7 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response,
 // Caller should close r.Body when done reading from it.
 //
 // PostForm is a wrapper around DefaultClient.PostForm
-func PostForm(url string, data Values) (r *Response, err os.Error) {
+func PostForm(url string, data url.Values) (r *Response, err os.Error) {
        return DefaultClient.PostForm(url, data)
 }
 
@@ -254,7 +255,7 @@ func PostForm(url string, data Values) (r *Response, err os.Error) {
 // with data's keys and values urlencoded as the request body.
 //
 // Caller should close r.Body when done reading from it.
-func (c *Client) PostForm(url string, data Values) (r *Response, err os.Error) {
+func (c *Client) PostForm(url string, data url.Values) (r *Response, err os.Error) {
        return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
 }
 
index 3b85585353f622a1502405e90fac1bfbc7b738d9..f22cce50b89717ef02f01210d688376c29521c60 100644 (file)
@@ -17,6 +17,7 @@ import (
        "strconv"
        "strings"
        "testing"
+       "url"
 )
 
 var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
@@ -109,18 +110,18 @@ func TestPostFormRequestFormat(t *testing.T) {
        tr := &recordingTransport{}
        client := &Client{Transport: tr}
 
-       url := "http://dummy.faketld/"
-       form := make(Values)
+       urlStr := "http://dummy.faketld/"
+       form := make(url.Values)
        form.Set("foo", "bar")
        form.Add("foo", "bar2")
        form.Set("bar", "baz")
-       client.PostForm(url, form) // Note: doesn't hit network
+       client.PostForm(urlStr, form) // Note: doesn't hit network
 
        if tr.req.Method != "POST" {
                t.Errorf("got method %q, want %q", tr.req.Method, "POST")
        }
-       if tr.req.URL.String() != url {
-               t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
+       if tr.req.URL.String() != urlStr {
+               t.Errorf("got URL %q, want %q", tr.req.URL.String(), urlStr)
        }
        if tr.req.Header == nil {
                t.Fatalf("expected non-nil request Header")
@@ -281,7 +282,7 @@ func TestClientWrites(t *testing.T) {
        }
 
        writes = 0
-       _, err = c.PostForm(ts.URL, Values{"foo": {"bar"}})
+       _, err = c.PostForm(ts.URL, url.Values{"foo": {"bar"}})
        if err != nil {
                t.Fatal(err)
        }
index 823770ec4f38795bc2019f24dc612cdaa8d6c4e0..bb6d0158b7b32d07717651daf4c6b87cac337198 100644 (file)
@@ -13,6 +13,7 @@ import (
        "path/filepath"
        "strings"
        "testing"
+       "url"
 )
 
 const (
@@ -49,7 +50,7 @@ func TestServeFile(t *testing.T) {
        // set up the Request (re-used for all tests)
        var req Request
        req.Header = make(Header)
-       if req.URL, err = ParseURL(ts.URL); err != nil {
+       if req.URL, err = url.Parse(ts.URL); err != nil {
                t.Fatal("ParseURL:", err)
        }
        req.Method = "GET"
index 79f8de70d3c8b61051c25efc596f504f4e0964e0..f6dc99e2e085d290417fe826863694b3bfcc11ba 100644 (file)
@@ -10,6 +10,7 @@ import (
        "fmt"
        "io"
        "testing"
+       "url"
 )
 
 type reqTest struct {
@@ -40,7 +41,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        RawURL: "http://www.techcrunch.com/",
-                       URL: &URL{
+                       URL: &url.URL{
                                Raw:          "http://www.techcrunch.com/",
                                Scheme:       "http",
                                RawPath:      "/",
@@ -67,7 +68,7 @@ var reqTests = []reqTest{
                        Close:         false,
                        ContentLength: 7,
                        Host:          "www.techcrunch.com",
-                       Form:          Values{},
+                       Form:          url.Values{},
                },
 
                "abcdef\n",
@@ -83,7 +84,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        RawURL: "/",
-                       URL: &URL{
+                       URL: &url.URL{
                                Raw:     "/",
                                Path:    "/",
                                RawPath: "/",
@@ -94,7 +95,7 @@ var reqTests = []reqTest{
                        Close:         false,
                        ContentLength: 0,
                        Host:          "foo.com",
-                       Form:          Values{},
+                       Form:          url.Values{},
                },
 
                noBody,
@@ -110,7 +111,7 @@ var reqTests = []reqTest{
                &Request{
                        Method: "GET",
                        RawURL: "//user@host/is/actually/a/path/",
-                       URL: &URL{
+                       URL: &url.URL{
                                Raw:          "//user@host/is/actually/a/path/",
                                Scheme:       "",
                                RawPath:      "//user@host/is/actually/a/path/",
@@ -128,7 +129,7 @@ var reqTests = []reqTest{
                        Close:         false,
                        ContentLength: 0,
                        Host:          "test",
-                       Form:          Values{},
+                       Form:          url.Values{},
                },
 
                noBody,
index 7aae8b4235adb95386c8bbab05f5ac40f8ebdf0a..9126920810372203b96ee55428720849042b2a95 100644 (file)
@@ -22,6 +22,7 @@ import (
        "os"
        "strconv"
        "strings"
+       "url"
 )
 
 const (
@@ -72,9 +73,9 @@ var reqWriteExcludeHeader = map[string]bool{
 
 // A Request represents a parsed HTTP request header.
 type Request struct {
-       Method string // GET, POST, PUT, etc.
-       RawURL string // The raw URL given in the request.
-       URL    *URL   // Parsed URL.
+       Method string   // GET, POST, PUT, etc.
+       RawURL string   // The raw URL given in the request.
+       URL    *url.URL // Parsed URL.
 
        // The protocol version for incoming requests.
        // Outgoing requests always use HTTP/1.1.
@@ -124,7 +125,7 @@ type Request struct {
        Host string
 
        // The parsed form. Only available after ParseForm is called.
-       Form Values
+       Form url.Values
 
        // The parsed multipart form, including file uploads.
        // Only available after ParseMultipartForm is called.
@@ -289,22 +290,22 @@ func (req *Request) write(w io.Writer, usingProxy bool) os.Error {
                host = req.URL.Host
        }
 
-       uri := req.RawURL
-       if uri == "" {
-               uri = valueOrDefault(urlEscape(req.URL.Path, encodePath), "/")
+       urlStr := req.RawURL
+       if urlStr == "" {
+               urlStr = valueOrDefault(req.URL.EncodedPath(), "/")
                if req.URL.RawQuery != "" {
-                       uri += "?" + req.URL.RawQuery
+                       urlStr += "?" + req.URL.RawQuery
                }
                if usingProxy {
-                       if uri == "" || uri[0] != '/' {
-                               uri = "/" + uri
+                       if urlStr == "" || urlStr[0] != '/' {
+                               urlStr = "/" + urlStr
                        }
-                       uri = req.URL.Scheme + "://" + host + uri
+                       urlStr = req.URL.Scheme + "://" + host + urlStr
                }
        }
 
        bw := bufio.NewWriter(w)
-       fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), uri)
+       fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), urlStr)
 
        // Header lines
        fmt.Fprintf(bw, "Host: %s\r\n", host)
@@ -481,8 +482,8 @@ func (cr *chunkedReader) Read(b []uint8) (n int, err os.Error) {
 }
 
 // NewRequest returns a new Request given a method, URL, and optional body.
-func NewRequest(method, url string, body io.Reader) (*Request, os.Error) {
-       u, err := ParseURL(url)
+func NewRequest(method, urlStr string, body io.Reader) (*Request, os.Error) {
+       u, err := url.Parse(urlStr)
        if err != nil {
                return nil, err
        }
@@ -547,7 +548,7 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
                return nil, &badStringError{"malformed HTTP version", req.Proto}
        }
 
-       if req.URL, err = ParseRequestURL(req.RawURL); err != nil {
+       if req.URL, err = url.ParseRequest(req.RawURL); err != nil {
                return nil, err
        }
 
@@ -607,77 +608,6 @@ func ReadRequest(b *bufio.Reader) (req *Request, err os.Error) {
        return req, nil
 }
 
-// Values maps a string key to a list of values.
-// It is typically used for query parameters and form values.
-// Unlike in the Header map, the keys in a Values map
-// are case-sensitive.
-type Values map[string][]string
-
-// Get gets the first value associated with the given key.
-// If there are no values associated with the key, Get returns
-// the empty string. To access multiple values, use the map
-// directly.
-func (v Values) Get(key string) string {
-       if v == nil {
-               return ""
-       }
-       vs, ok := v[key]
-       if !ok || len(vs) == 0 {
-               return ""
-       }
-       return vs[0]
-}
-
-// Set sets the key to value. It replaces any existing
-// values.
-func (v Values) Set(key, value string) {
-       v[key] = []string{value}
-}
-
-// Add adds the key to value. It appends to any existing
-// values associated with key.
-func (v Values) Add(key, value string) {
-       v[key] = append(v[key], value)
-}
-
-// Del deletes the values associated with key.
-func (v Values) Del(key string) {
-       v[key] = nil, false
-}
-
-// ParseQuery parses the URL-encoded query string and returns
-// a map listing the values specified for each key.
-// ParseQuery always returns a non-nil map containing all the
-// valid query parameters found; err describes the first decoding error
-// encountered, if any.
-func ParseQuery(query string) (m Values, err os.Error) {
-       m = make(Values)
-       err = parseQuery(m, query)
-       return
-}
-
-func parseQuery(m Values, query string) (err os.Error) {
-       for _, kv := range strings.Split(query, "&") {
-               if len(kv) == 0 {
-                       continue
-               }
-               kvPair := strings.SplitN(kv, "=", 2)
-
-               var key, value string
-               var e os.Error
-               key, e = URLUnescape(kvPair[0])
-               if e == nil && len(kvPair) > 1 {
-                       value, e = URLUnescape(kvPair[1])
-               }
-               if e != nil {
-                       err = e
-                       continue
-               }
-               m[key] = append(m[key], value)
-       }
-       return err
-}
-
 // ParseForm parses the raw query.
 // For POST requests, it also parses the request body as a form.
 // ParseMultipartForm calls ParseForm automatically.
@@ -687,9 +617,10 @@ func (r *Request) ParseForm() (err os.Error) {
                return
        }
 
-       r.Form = make(Values)
        if r.URL != nil {
-               err = parseQuery(r.Form, r.URL.RawQuery)
+               r.Form, err = url.ParseQuery(r.URL.RawQuery)
+       } else {
+               r.Form = make(url.Values) // TODO: remove when nil maps work.
        }
        if r.Method == "POST" {
                if r.Body == nil {
@@ -709,10 +640,17 @@ func (r *Request) ParseForm() (err os.Error) {
                        if int64(len(b)) > maxFormSize {
                                return os.NewError("http: POST too large")
                        }
-                       e = parseQuery(r.Form, string(b))
+                       var newValues url.Values
+                       newValues, e = url.ParseQuery(string(b))
                        if err == nil {
                                err = e
                        }
+                       // Copy values into r.Form. TODO: make this smoother.
+                       for k, vs := range newValues {
+                               for _, value := range vs {
+                                       r.Form.Add(k, value)
+                               }
+                       }
                case "multipart/form-data":
                        // handled by ParseMultipartForm
                default:
index b5482db38b33824ccf43b7deee64cf6cf1186333..869cd57b696858cb7daa4583674955beb6adcb78 100644 (file)
@@ -17,6 +17,7 @@ import (
        "regexp"
        "strings"
        "testing"
+       "url"
 )
 
 type stringMultimap map[string][]string
@@ -43,7 +44,7 @@ var parseTests = []parseTest{
 
 func TestParseForm(t *testing.T) {
        for i, test := range parseTests {
-               form, err := ParseQuery(test.query)
+               form, err := url.ParseQuery(test.query)
                if err != nil {
                        t.Errorf("test %d: Unexpected error: %v", i, err)
                        continue
@@ -72,7 +73,7 @@ func TestParseForm(t *testing.T) {
 
 func TestQuery(t *testing.T) {
        req := &Request{Method: "GET"}
-       req.URL, _ = ParseURL("http://www.google.com/search?q=foo&q=bar")
+       req.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar")
        if q := req.FormValue("q"); q != "foo" {
                t.Errorf(`req.FormValue("q") = %q, want "foo"`, q)
        }
@@ -80,7 +81,7 @@ func TestQuery(t *testing.T) {
 
 func TestPostQuery(t *testing.T) {
        req := &Request{Method: "POST"}
-       req.URL, _ = ParseURL("http://www.google.com/search?q=foo&q=bar&both=x")
+       req.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar&both=x")
        req.Header = Header{
                "Content-Type": {"application/x-www-form-urlencoded; boo!"},
        }
index 0052c0cfc552abc73f491ce63fd6fe21d9b0376a..458f0bd7f4b731f18c54507e266e14872c463773 100644 (file)
@@ -12,6 +12,7 @@ import (
        "os"
        "strings"
        "testing"
+       "url"
 )
 
 type reqWriteTest struct {
@@ -27,7 +28,7 @@ var reqWriteTests = []reqWriteTest{
                Request{
                        Method: "GET",
                        RawURL: "http://www.techcrunch.com/",
-                       URL: &URL{
+                       URL: &url.URL{
                                Raw:          "http://www.techcrunch.com/",
                                Scheme:       "http",
                                RawPath:      "http://www.techcrunch.com/",
@@ -82,7 +83,7 @@ var reqWriteTests = []reqWriteTest{
        {
                Request{
                        Method: "GET",
-                       URL: &URL{
+                       URL: &url.URL{
                                Scheme: "http",
                                Host:   "www.google.com",
                                Path:   "/search",
@@ -111,7 +112,7 @@ var reqWriteTests = []reqWriteTest{
        {
                Request{
                        Method: "POST",
-                       URL: &URL{
+                       URL: &url.URL{
                                Scheme: "http",
                                Host:   "www.google.com",
                                Path:   "/search",
@@ -144,7 +145,7 @@ var reqWriteTests = []reqWriteTest{
        {
                Request{
                        Method: "POST",
-                       URL: &URL{
+                       URL: &url.URL{
                                Scheme: "http",
                                Host:   "www.google.com",
                                Path:   "/search",
index 015f87f246a4dd8e750752b449d71266ff0e0886..3f8bfdc80c23d8278e5b6ef52f731390f8e801ab 100644 (file)
@@ -14,6 +14,7 @@ import (
        "strings"
        "sync"
        "time"
+       "url"
 )
 
 // ReverseProxy is an HTTP Handler that takes an incoming request and
@@ -53,7 +54,7 @@ func singleJoiningSlash(a, b string) string {
 // URLs to the scheme, host, and base path provided in target. If the
 // target's path is "/base" and the incoming request was for "/dir",
 // the target request will be for /base/dir.
-func NewSingleHostReverseProxy(target *URL) *ReverseProxy {
+func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
        director := func(req *Request) {
                req.URL.Scheme = target.Scheme
                req.URL.Host = target.Host
index b2dd24633aca51be83461f7b87ecade9e796519f..8078c8d10df3b40dcd6cfdc92618c8a7fbe29cb7 100644 (file)
@@ -11,6 +11,7 @@ import (
        "http/httptest"
        "io/ioutil"
        "testing"
+       "url"
 )
 
 func TestReverseProxy(t *testing.T) {
@@ -32,7 +33,7 @@ func TestReverseProxy(t *testing.T) {
                w.Write([]byte(backendResponse))
        }))
        defer backend.Close()
-       backendURL, err := ParseURL(backend.URL)
+       backendURL, err := url.Parse(backend.URL)
        if err != nil {
                t.Fatal(err)
        }
index 2725c3b428babab120aa45e276068b770c3e68d3..ac04033459636a0cd51b01b56b73a9c37a19ce55 100644 (file)
@@ -22,6 +22,7 @@ import (
        "syscall"
        "testing"
        "time"
+       "url"
 )
 
 type dummyAddr string
@@ -183,7 +184,7 @@ func TestHostHandlers(t *testing.T) {
        for _, vt := range vtests {
                var r *Response
                var req Request
-               if req.URL, err = ParseURL(vt.url); err != nil {
+               if req.URL, err = url.Parse(vt.url); err != nil {
                        t.Errorf("cannot parse url: %v", err)
                        continue
                }
index 1955b67e65957b03b0e3f3cf7335c3e2cb6a753e..b634e27d6df35cf723d58659234c1900968ff345 100644 (file)
@@ -25,6 +25,7 @@ import (
        "strings"
        "sync"
        "time"
+       "url"
 )
 
 // Errors introduced by the HTTP server.
@@ -716,8 +717,8 @@ func StripPrefix(prefix string, h Handler) Handler {
 
 // Redirect replies to the request with a redirect to url,
 // which may be a path relative to the request path.
-func Redirect(w ResponseWriter, r *Request, url string, code int) {
-       if u, err := ParseURL(url); err == nil {
+func Redirect(w ResponseWriter, r *Request, urlStr string, code int) {
+       if u, err := url.Parse(urlStr); err == nil {
                // If url was relative, make absolute by
                // combining with request path.
                // The browser would probably do this for us,
@@ -740,35 +741,35 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) {
                }
                if u.Scheme == "" {
                        // no leading http://server
-                       if url == "" || url[0] != '/' {
+                       if urlStr == "" || urlStr[0] != '/' {
                                // make relative path absolute
                                olddir, _ := path.Split(oldpath)
-                               url = olddir + url
+                               urlStr = olddir + urlStr
                        }
 
                        var query string
-                       if i := strings.Index(url, "?"); i != -1 {
-                               url, query = url[:i], url[i:]
+                       if i := strings.Index(urlStr, "?"); i != -1 {
+                               urlStr, query = urlStr[:i], urlStr[i:]
                        }
 
                        // clean up but preserve trailing slash
-                       trailing := url[len(url)-1] == '/'
-                       url = path.Clean(url)
-                       if trailing && url[len(url)-1] != '/' {
-                               url += "/"
+                       trailing := urlStr[len(urlStr)-1] == '/'
+                       urlStr = path.Clean(urlStr)
+                       if trailing && urlStr[len(urlStr)-1] != '/' {
+                               urlStr += "/"
                        }
-                       url += query
+                       urlStr += query
                }
        }
 
-       w.Header().Set("Location", url)
+       w.Header().Set("Location", urlStr)
        w.WriteHeader(code)
 
        // RFC2616 recommends that a short note "SHOULD" be included in the
        // response because older user agents may not understand 301/307.
        // Shouldn't send the response for POST or HEAD; that leaves GET.
        if r.Method == "GET" {
-               note := "<a href=\"" + htmlEscape(url) + "\">" + statusText[code] + "</a>.\n"
+               note := "<a href=\"" + htmlEscape(urlStr) + "\">" + statusText[code] + "</a>.\n"
                fmt.Fprintln(w, note)
        }
 }
index d03aadfd34462c26a64f17bd6203d3ede18f42e1..4302ffab1e39f20941ebde369d01811a1e75a681 100644 (file)
@@ -17,6 +17,7 @@ import (
        "os"
        "strings"
        "sync"
+       "url"
 )
 
 // DefaultTransport is the default implementation of Transport and is
@@ -46,7 +47,7 @@ type Transport struct {
        // Request. If the function returns a non-nil error, the
        // request is aborted with the provided error.
        // If Proxy is nil or returns a nil *URL, no proxy is used.
-       Proxy func(*Request) (*URL, os.Error)
+       Proxy func(*Request) (*url.URL, os.Error)
 
        // Dial specifies the dial function for creating TCP
        // connections.
@@ -66,7 +67,7 @@ type Transport struct {
 // given request, as indicated by the environment variables
 // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy).
 // Either URL or an error is returned.
-func ProxyFromEnvironment(req *Request) (*URL, os.Error) {
+func ProxyFromEnvironment(req *Request) (*url.URL, os.Error) {
        proxy := getenvEitherCase("HTTP_PROXY")
        if proxy == "" {
                return nil, nil
@@ -74,12 +75,12 @@ func ProxyFromEnvironment(req *Request) (*URL, os.Error) {
        if !useProxy(canonicalAddr(req.URL)) {
                return nil, nil
        }
-       proxyURL, err := ParseRequestURL(proxy)
+       proxyURL, err := url.ParseRequest(proxy)
        if err != nil {
                return nil, os.NewError("invalid proxy address")
        }
        if proxyURL.Host == "" {
-               proxyURL, err = ParseRequestURL("http://" + proxy)
+               proxyURL, err = url.ParseRequest("http://" + proxy)
                if err != nil {
                        return nil, os.NewError("invalid proxy address")
                }
@@ -89,16 +90,16 @@ func ProxyFromEnvironment(req *Request) (*URL, os.Error) {
 
 // ProxyURL returns a proxy function (for use in a Transport)
 // that always returns the same URL.
-func ProxyURL(url *URL) func(*Request) (*URL, os.Error) {
-       return func(*Request) (*URL, os.Error) {
-               return url, nil
+func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, os.Error) {
+       return func(*Request) (*url.URL, os.Error) {
+               return fixedURL, nil
        }
 }
 
 // RoundTrip implements the RoundTripper interface.
 func (t *Transport) RoundTrip(req *Request) (resp *Response, err os.Error) {
        if req.URL == nil {
-               if req.URL, err = ParseURL(req.RawURL); err != nil {
+               if req.URL, err = url.Parse(req.RawURL); err != nil {
                        return
                }
        }
@@ -413,9 +414,9 @@ func useProxy(addr string) bool {
 // Note: no support to https to the proxy yet.
 //
 type connectMethod struct {
-       proxyURL     *URL   // "" for no proxy, else full proxy URL
-       targetScheme string // "http" or "https"
-       targetAddr   string // Not used if proxy + http targetScheme (4th example in table)
+       proxyURL     *url.URL // nil for no proxy, else full proxy URL
+       targetScheme string   // "http" or "https"
+       targetAddr   string   // Not used if proxy + http targetScheme (4th example in table)
 }
 
 func (ck *connectMethod) String() string {
@@ -642,7 +643,7 @@ var portMap = map[string]string{
 }
 
 // canonicalAddr returns url.Host but always with a ":port" suffix
-func canonicalAddr(url *URL) string {
+func canonicalAddr(url *url.URL) string {
        addr := url.Host
        if !hasPort(addr) {
                return addr + ":" + portMap[url.Scheme]
index 20895da8695e667a4253ba956d1b57a1f5ad768a..eafde7f8995a12d895b0261cc618f42af9fb46da 100644 (file)
@@ -20,6 +20,7 @@ import (
        "strings"
        "testing"
        "time"
+       "url"
 )
 
 // TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close
@@ -77,7 +78,7 @@ func TestTransportConnectionCloseOnResponse(t *testing.T) {
                fetch := func(n int) string {
                        req := new(Request)
                        var err os.Error
-                       req.URL, err = ParseURL(ts.URL + fmt.Sprintf("?close=%v", connectionClose))
+                       req.URL, err = url.Parse(ts.URL + fmt.Sprintf("?close=%v", connectionClose))
                        if err != nil {
                                t.Fatalf("URL parse error: %v", err)
                        }
@@ -119,7 +120,7 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) {
                fetch := func(n int) string {
                        req := new(Request)
                        var err os.Error
-                       req.URL, err = ParseURL(ts.URL)
+                       req.URL, err = url.Parse(ts.URL)
                        if err != nil {
                                t.Fatalf("URL parse error: %v", err)
                        }
@@ -552,7 +553,7 @@ func TestTransportProxy(t *testing.T) {
        }))
        defer proxy.Close()
 
-       pu, err := ParseURL(proxy.URL)
+       pu, err := url.Parse(proxy.URL)
        if err != nil {
                t.Fatal(err)
        }
diff --git a/src/pkg/url/Makefile b/src/pkg/url/Makefile
new file mode 100644 (file)
index 0000000..b9267bd
--- /dev/null
@@ -0,0 +1,11 @@
+# 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.
+
+include ../../Make.inc
+
+TARG=url
+GOFILES=\
+       url.go\
+
+include ../../Make.pkg
similarity index 75%
rename from src/pkg/http/url.go
rename to src/pkg/url/url.go
index b38585ac200e35a93721782101f24dc79fe6a7f4..d07b016118fb387bea9974b80ba28fb768414097 100644 (file)
@@ -2,10 +2,9 @@
 // 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 3986
-
-package http
+// Package URL parses URLs and implements query escaping.
+// See RFC 3986.
+package url
 
 import (
        "os"
@@ -13,14 +12,14 @@ import (
        "strings"
 )
 
-// URLError reports an error and the operation and URL that caused it.
-type URLError struct {
+// Error reports an error and the operation and URL that caused it.
+type Error struct {
        Op    string
        URL   string
        Error os.Error
 }
 
-func (e *URLError) String() string { return e.Op + " " + e.URL + ": " + e.Error.String() }
+func (e *Error) String() string { return e.Op + " " + e.URL + ": " + e.Error.String() }
 
 func ishex(c byte) bool {
        switch {
@@ -56,9 +55,9 @@ const (
        encodeOpaque
 )
 
-type URLEscapeError string
+type EscapeError string
 
-func (e URLEscapeError) String() string {
+func (e EscapeError) String() string {
        return "invalid URL escape " + strconv.Quote(string(e))
 }
 
@@ -113,19 +112,16 @@ func shouldEscape(c byte, mode encoding) bool {
        return true
 }
 
-// URLUnescape unescapes a string in ``URL encoded'' form,
-// converting %AB into the byte 0xAB and '+' into ' ' (space).
-// It returns an error if any % is not followed
-// by two hexadecimal digits.
-// Despite the name, this encoding applies only to individual
-// components of the query portion of the URL.
-func URLUnescape(s string) (string, os.Error) {
-       return urlUnescape(s, encodeQueryComponent)
+// QueryUnescape does the inverse transformation of QueryEscape, converting
+// %AB into the byte 0xAB and '+' into ' ' (space). It returns an error if
+// any % is not followed by two hexadecimal digits.
+func QueryUnescape(s string) (string, os.Error) {
+       return unescape(s, encodeQueryComponent)
 }
 
-// urlUnescape is like URLUnescape but mode specifies
-// which section of the URL is being unescaped.
-func urlUnescape(s string, mode encoding) (string, os.Error) {
+// unescape unescapes a string; the mode specifies
+// which section of the URL string is being unescaped.
+func unescape(s string, mode encoding) (string, os.Error) {
        // Count %, check that they're well-formed.
        n := 0
        hasPlus := false
@@ -138,7 +134,7 @@ func urlUnescape(s string, mode encoding) (string, os.Error) {
                                if len(s) > 3 {
                                        s = s[0:3]
                                }
-                               return "", URLEscapeError(s)
+                               return "", EscapeError(s)
                        }
                        i += 3
                case '+':
@@ -178,14 +174,13 @@ func urlUnescape(s string, mode encoding) (string, os.Error) {
        return string(t), nil
 }
 
-// URLEscape converts a string into ``URL encoded'' form.
-// Despite the name, this encoding applies only to individual
-// components of the query portion of the URL.
-func URLEscape(s string) string {
-       return urlEscape(s, encodeQueryComponent)
+// QueryEscape escapes the string so it can be safely placed
+// inside a URL query.
+func QueryEscape(s string) string {
+       return escape(s, encodeQueryComponent)
 }
 
-func urlEscape(s string, mode encoding) string {
+func escape(s string, mode encoding) string {
        spaceCount, hexCount := 0, 0
        for i := 0; i < len(s); i++ {
                c := s[i]
@@ -233,10 +228,10 @@ func urlEscape(s string, mode encoding) string {
 // security risk in almost every case where it has been used.''
 func UnescapeUserinfo(rawUserinfo string) (user, password string, err os.Error) {
        u, p := split(rawUserinfo, ':', true)
-       if user, err = urlUnescape(u, encodeUserPassword); err != nil {
+       if user, err = unescape(u, encodeUserPassword); err != nil {
                return "", "", err
        }
-       if password, err = urlUnescape(p, encodeUserPassword); err != nil {
+       if password, err = unescape(p, encodeUserPassword); err != nil {
                return "", "", err
        }
        return
@@ -252,9 +247,9 @@ func UnescapeUserinfo(rawUserinfo string) (user, password string, err os.Error)
 // information in clear text (such as URI) has proven to be a
 // security risk in almost every case where it has been used.''
 func EscapeUserinfo(user, password string) string {
-       raw := urlEscape(user, encodeUserPassword)
+       raw := escape(user, encodeUserPassword)
        if password != "" {
-               raw += ":" + urlEscape(password, encodeUserPassword)
+               raw += ":" + escape(password, encodeUserPassword)
        }
        return raw
 }
@@ -324,28 +319,28 @@ func split(s string, c byte, cutc bool) (string, string) {
        return s, ""
 }
 
-// ParseURL parses rawurl into a URL structure.
+// Parse 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.)
 // The rawurl may be relative or absolute.
-func ParseURL(rawurl string) (url *URL, err os.Error) {
-       return parseURL(rawurl, false)
+func Parse(rawurl string) (url *URL, err os.Error) {
+       return parse(rawurl, false)
 }
 
-// ParseRequestURL parses rawurl into a URL structure.  It assumes that
+// ParseRequest parses rawurl into a URL structure.  It assumes that
 // rawurl was received from an HTTP request, so the rawurl is interpreted
 // only as an absolute URI or an absolute path.
 // The string rawurl is assumed not to have a #fragment suffix.
 // (Web browsers strip #fragment before sending the URL to a web server.)
-func ParseRequestURL(rawurl string) (url *URL, err os.Error) {
-       return parseURL(rawurl, true)
+func ParseRequest(rawurl string) (url *URL, err os.Error) {
+       return parse(rawurl, true)
 }
 
-// parseURL parses a URL from a string in one of two contexts.  If
+// parse parses a URL from a string in one of two contexts.  If
 // viaRequest is true, the URL is assumed to have arrived via an HTTP request,
 // in which case only absolute URLs or path-absolute relative URLs are allowed.
 // If viaRequest is false, all forms of relative URLs are allowed.
-func parseURL(rawurl string, viaRequest bool) (url *URL, err os.Error) {
+func parse(rawurl string, viaRequest bool) (url *URL, err os.Error) {
        var (
                leadingSlash bool
                path         string
@@ -372,7 +367,7 @@ func parseURL(rawurl string, viaRequest bool) (url *URL, err os.Error) {
                // This is the case that handles mailto:name@example.com.
                url.RawPath = path
 
-               if url.Path, err = urlUnescape(path, encodeOpaque); err != nil {
+               if url.Path, err = unescape(path, encodeOpaque); err != nil {
                        goto Error
                }
                url.OpaquePath = true
@@ -417,30 +412,30 @@ func parseURL(rawurl string, viaRequest bool) (url *URL, err os.Error) {
                }
                url.Host = rawHost
 
-               if url.Path, err = urlUnescape(path, encodePath); err != nil {
+               if url.Path, err = unescape(path, encodePath); err != nil {
                        goto Error
                }
        }
        return url, nil
 
 Error:
-       return nil, &URLError{"parse", rawurl, err}
+       return nil, &Error{"parse", rawurl, err}
 
 }
 
-// ParseURLReference is like ParseURL but allows a trailing #fragment.
-func ParseURLReference(rawurlref string) (url *URL, err os.Error) {
+// ParseWithReference is like Parse but allows a trailing #fragment.
+func ParseWithReference(rawurlref string) (url *URL, err os.Error) {
        // Cut off #frag.
        rawurl, frag := split(rawurlref, '#', false)
-       if url, err = ParseURL(rawurl); err != nil {
+       if url, err = Parse(rawurl); err != nil {
                return nil, err
        }
        url.Raw += frag
        url.RawPath += frag
        if len(frag) > 1 {
                frag = frag[1:]
-               if url.Fragment, err = urlUnescape(frag, encodeFragment); err != nil {
-                       return nil, &URLError{"parse", rawurl, err}
+               if url.Fragment, err = unescape(frag, encodeFragment); err != nil {
+                       return nil, &Error{"parse", rawurl, err}
                }
        }
        return url, nil
@@ -474,19 +469,90 @@ func (url *URL) String() string {
                        result += "%2f"
                        path = path[1:]
                }
-               result += urlEscape(path, encodeOpaque)
+               result += escape(path, encodeOpaque)
        } else {
-               result += urlEscape(url.Path, encodePath)
+               result += escape(url.Path, encodePath)
        }
        if url.RawQuery != "" {
                result += "?" + url.RawQuery
        }
        if url.Fragment != "" {
-               result += "#" + urlEscape(url.Fragment, encodeFragment)
+               result += "#" + escape(url.Fragment, encodeFragment)
        }
        return result
 }
 
+// Values maps a string key to a list of values.
+// It is typically used for query parameters and form values.
+// Unlike in the http.Header map, the keys in a Values map
+// are case-sensitive.
+type Values map[string][]string
+
+// Get gets the first value associated with the given key.
+// If there are no values associated with the key, Get returns
+// the empty string. To access multiple values, use the map
+// directly.
+func (v Values) Get(key string) string {
+       if v == nil {
+               return ""
+       }
+       vs, ok := v[key]
+       if !ok || len(vs) == 0 {
+               return ""
+       }
+       return vs[0]
+}
+
+// Set sets the key to value. It replaces any existing
+// values.
+func (v Values) Set(key, value string) {
+       v[key] = []string{value}
+}
+
+// Add adds the key to value. It appends to any existing
+// values associated with key.
+func (v Values) Add(key, value string) {
+       v[key] = append(v[key], value)
+}
+
+// Del deletes the values associated with key.
+func (v Values) Del(key string) {
+       v[key] = nil, false
+}
+
+// ParseQuery parses the URL-encoded query string and returns
+// a map listing the values specified for each key.
+// ParseQuery always returns a non-nil map containing all the
+// valid query parameters found; err describes the first decoding error
+// encountered, if any.
+func ParseQuery(query string) (m Values, err os.Error) {
+       m = make(Values)
+       err = parseQuery(m, query)
+       return
+}
+
+func parseQuery(m Values, query string) (err os.Error) {
+       for _, kv := range strings.Split(query, "&") {
+               if len(kv) == 0 {
+                       continue
+               }
+               kvPair := strings.SplitN(kv, "=", 2)
+
+               var key, value string
+               var e os.Error
+               key, e = QueryUnescape(kvPair[0])
+               if e == nil && len(kvPair) > 1 {
+                       value, e = QueryUnescape(kvPair[1])
+               }
+               if e != nil {
+                       err = e
+                       continue
+               }
+               m[key] = append(m[key], value)
+       }
+       return err
+}
+
 // Encode encodes the values into ``URL encoded'' form.
 // e.g. "foo=bar&bar=baz"
 func (v Values) Encode() string {
@@ -495,9 +561,9 @@ func (v Values) Encode() string {
        }
        parts := make([]string, 0, len(v)) // will be large enough for most uses
        for k, vs := range v {
-               prefix := URLEscape(k) + "="
+               prefix := QueryEscape(k) + "="
                for _, v := range vs {
-                       parts = append(parts, prefix+URLEscape(v))
+                       parts = append(parts, prefix+QueryEscape(v))
                }
        }
        return strings.Join(parts, "&")
@@ -538,11 +604,11 @@ func (url *URL) IsAbs() bool {
        return url.Scheme != ""
 }
 
-// ParseURL parses a URL in the context of a base URL.  The URL in ref
-// may be relative or absolute.  ParseURL returns nil, err on parse
+// Parse parses a URL in the context of a base URL.  The URL in ref
+// may be relative or absolute.  Parse returns nil, err on parse
 // failure, otherwise its return value is the same as ResolveReference.
-func (base *URL) ParseURL(ref string) (*URL, os.Error) {
-       refurl, err := ParseURL(ref)
+func (base *URL) Parse(ref string) (*URL, os.Error) {
+       refurl, err := Parse(ref)
        if err != nil {
                return nil, err
        }
@@ -604,3 +670,8 @@ func (u *URL) Query() Values {
        v, _ := ParseQuery(u.RawQuery)
        return v
 }
+
+// EncodedPath returns the URL's path in "URL path encoded" form.
+func (u *URL) EncodedPath() string {
+       return escape(u.Path, encodePath)
+}
similarity index 87%
rename from src/pkg/http/url_test.go
rename to src/pkg/url/url_test.go
index eaec5872aedf8ca317295f26c21df6aecb20f424..af394d4fb4a0094dbdbbe426c3b8e10f9d8f091d 100644 (file)
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package http
+package url
 
 import (
        "fmt"
@@ -12,9 +12,9 @@ import (
 )
 
 // TODO(rsc):
-//     test URLUnescape
-//     test URLEscape
-//     test ParseURL
+//     test Unescape
+//     test Escape
+//     test Parse
 
 type URLTest struct {
        in        string
@@ -218,7 +218,7 @@ var urltests = []URLTest{
        },
        // Three leading slashes isn't an authority, but doesn't return an error.
        // (We can't return an error, as this code is also used via
-       // ServeHTTP -> ReadRequest -> ParseURL, which is arguably a
+       // ServeHTTP -> ReadRequest -> Parse, which is arguably a
        // different URL parsing context, but currently shares the
        // same codepath)
        {
@@ -325,14 +325,14 @@ func DoTest(t *testing.T, parse func(string) (*URL, os.Error), name string, test
        }
 }
 
-func TestParseURL(t *testing.T) {
-       DoTest(t, ParseURL, "ParseURL", urltests)
-       DoTest(t, ParseURL, "ParseURL", urlnofragtests)
+func TestParse(t *testing.T) {
+       DoTest(t, Parse, "Parse", urltests)
+       DoTest(t, Parse, "Parse", urlnofragtests)
 }
 
-func TestParseURLReference(t *testing.T) {
-       DoTest(t, ParseURLReference, "ParseURLReference", urltests)
-       DoTest(t, ParseURLReference, "ParseURLReference", urlfragtests)
+func TestParseWithReference(t *testing.T) {
+       DoTest(t, ParseWithReference, "ParseWithReference", urltests)
+       DoTest(t, ParseWithReference, "ParseWithReference", urlfragtests)
 }
 
 const pathThatLooksSchemeRelative = "//not.a.user@not.a.host/just/a/path"
@@ -351,16 +351,16 @@ var parseRequestUrlTests = []struct {
        {"../dir/", false},
 }
 
-func TestParseRequestURL(t *testing.T) {
+func TestParseRequest(t *testing.T) {
        for _, test := range parseRequestUrlTests {
-               _, err := ParseRequestURL(test.url)
+               _, err := ParseRequest(test.url)
                valid := err == nil
                if valid != test.expectedValid {
                        t.Errorf("Expected valid=%v for %q; got %v", test.expectedValid, test.url, valid)
                }
        }
 
-       url, err := ParseRequestURL(pathThatLooksSchemeRelative)
+       url, err := ParseRequest(pathThatLooksSchemeRelative)
        if err != nil {
                t.Fatalf("Unexpected error %v", err)
        }
@@ -388,19 +388,19 @@ func DoTestString(t *testing.T, parse func(string) (*URL, os.Error), name string
 }
 
 func TestURLString(t *testing.T) {
-       DoTestString(t, ParseURL, "ParseURL", urltests)
-       DoTestString(t, ParseURL, "ParseURL", urlnofragtests)
-       DoTestString(t, ParseURLReference, "ParseURLReference", urltests)
-       DoTestString(t, ParseURLReference, "ParseURLReference", urlfragtests)
+       DoTestString(t, Parse, "Parse", urltests)
+       DoTestString(t, Parse, "Parse", urlnofragtests)
+       DoTestString(t, ParseWithReference, "ParseWithReference", urltests)
+       DoTestString(t, ParseWithReference, "ParseWithReference", urlfragtests)
 }
 
-type URLEscapeTest struct {
+type EscapeTest struct {
        in  string
        out string
        err os.Error
 }
 
-var unescapeTests = []URLEscapeTest{
+var unescapeTests = []EscapeTest{
        {
                "",
                "",
@@ -434,40 +434,40 @@ var unescapeTests = []URLEscapeTest{
        {
                "%", // not enough characters after %
                "",
-               URLEscapeError("%"),
+               EscapeError("%"),
        },
        {
                "%a", // not enough characters after %
                "",
-               URLEscapeError("%a"),
+               EscapeError("%a"),
        },
        {
                "%1", // not enough characters after %
                "",
-               URLEscapeError("%1"),
+               EscapeError("%1"),
        },
        {
                "123%45%6", // not enough characters after %
                "",
-               URLEscapeError("%6"),
+               EscapeError("%6"),
        },
        {
                "%zzzzz", // invalid hex digits
                "",
-               URLEscapeError("%zz"),
+               EscapeError("%zz"),
        },
 }
 
-func TestURLUnescape(t *testing.T) {
+func TestUnescape(t *testing.T) {
        for _, tt := range unescapeTests {
-               actual, err := URLUnescape(tt.in)
+               actual, err := QueryUnescape(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)
+                       t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", tt.in, actual, err, tt.out, tt.err)
                }
        }
 }
 
-var escapeTests = []URLEscapeTest{
+var escapeTests = []EscapeTest{
        {
                "",
                "",
@@ -495,17 +495,17 @@ var escapeTests = []URLEscapeTest{
        },
 }
 
-func TestURLEscape(t *testing.T) {
+func TestEscape(t *testing.T) {
        for _, tt := range escapeTests {
-               actual := URLEscape(tt.in)
+               actual := QueryEscape(tt.in)
                if tt.out != actual {
-                       t.Errorf("URLEscape(%q) = %q, want %q", tt.in, actual, tt.out)
+                       t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out)
                }
 
                // for bonus points, verify that escape:unescape is an identity.
-               roundtrip, err := URLUnescape(actual)
+               roundtrip, err := QueryUnescape(actual)
                if roundtrip != tt.in || err != nil {
-                       t.Errorf("URLUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
+                       t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
                }
        }
 }
@@ -629,16 +629,16 @@ var resolveReferenceTests = []struct {
 }
 
 func TestResolveReference(t *testing.T) {
-       mustParseURL := func(url string) *URL {
-               u, err := ParseURLReference(url)
+       mustParse := func(url string) *URL {
+               u, err := ParseWithReference(url)
                if err != nil {
                        t.Fatalf("Expected URL to parse: %q, got error: %v", url, err)
                }
                return u
        }
        for _, test := range resolveReferenceTests {
-               base := mustParseURL(test.base)
-               rel := mustParseURL(test.rel)
+               base := mustParse(test.base)
+               rel := mustParse(test.rel)
                url := base.ResolveReference(rel)
                urlStr := url.String()
                if urlStr != test.expected {
@@ -647,33 +647,33 @@ func TestResolveReference(t *testing.T) {
        }
 
        // Test that new instances are returned.
-       base := mustParseURL("http://foo.com/")
-       abs := base.ResolveReference(mustParseURL("."))
+       base := mustParse("http://foo.com/")
+       abs := base.ResolveReference(mustParse("."))
        if base == abs {
                t.Errorf("Expected no-op reference to return new URL instance.")
        }
-       barRef := mustParseURL("http://bar.com/")
+       barRef := mustParse("http://bar.com/")
        abs = base.ResolveReference(barRef)
        if abs == barRef {
                t.Errorf("Expected resolution of absolute reference to return new URL instance.")
        }
 
        // Test the convenience wrapper too
-       base = mustParseURL("http://foo.com/path/one/")
-       abs, _ = base.ParseURL("../two")
+       base = mustParse("http://foo.com/path/one/")
+       abs, _ = base.Parse("../two")
        expected := "http://foo.com/path/two"
        if abs.String() != expected {
-               t.Errorf("ParseURL wrapper got %q; expected %q", abs.String(), expected)
+               t.Errorf("Parse wrapper got %q; expected %q", abs.String(), expected)
        }
-       _, err := base.ParseURL("")
+       _, err := base.Parse("")
        if err == nil {
-               t.Errorf("Expected an error from ParseURL wrapper parsing an empty string.")
+               t.Errorf("Expected an error from Parse wrapper parsing an empty string.")
        }
 
 }
 
 func TestQueryValues(t *testing.T) {
-       u, _ := ParseURL("http://x.com?foo=bar&bar=1&bar=2")
+       u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2")
        v := u.Query()
        if len(v) != 2 {
                t.Errorf("got %d keys in Query values, want 2", len(v))
index f24c463608c191e2374e0f0d47e3a1cf04caaf5b..74bede4249f52980b9ab3194856069920d50e51a 100644 (file)
@@ -15,6 +15,7 @@ import (
        "os"
        "rand"
        "strings"
+       "url"
 )
 
 type ProtocolError struct {
@@ -99,10 +100,10 @@ A trivial example client:
                // use msg[0:n]
        }
 */
-func Dial(url, protocol, origin string) (ws *Conn, err os.Error) {
+func Dial(url_, protocol, origin string) (ws *Conn, err os.Error) {
        var client net.Conn
 
-       parsedUrl, err := http.ParseURL(url)
+       parsedUrl, err := url.Parse(url_)
        if err != nil {
                goto Error
        }
@@ -121,14 +122,14 @@ func Dial(url, protocol, origin string) (ws *Conn, err os.Error) {
                goto Error
        }
 
-       ws, err = newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url, protocol, client, handshake)
+       ws, err = newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url_, protocol, client, handshake)
        if err != nil {
                goto Error
        }
        return
 
 Error:
-       return nil, &DialError{url, protocol, origin, err}
+       return nil, &DialError{url_, protocol, origin, err}
 }
 
 /*
index 84788b416edd1b0cd9fa0cdb8ced9f261c6fbc3f..71c3c8514b7c6cddf9b6241f0a4f6cbae52edbda 100644 (file)
@@ -15,6 +15,7 @@ import (
        "net"
        "sync"
        "testing"
+       "url"
 )
 
 var serverAddr string
@@ -155,9 +156,9 @@ func TestHTTP(t *testing.T) {
                t.Error("Get: unexpected success")
                return
        }
-       urlerr, ok := err.(*http.URLError)
+       urlerr, ok := err.(*url.Error)
        if !ok {
-               t.Errorf("Get: not URLError %#v", err)
+               t.Errorf("Get: not url.Error %#v", err)
                return
        }
        if urlerr.Error != io.ErrUnexpectedEOF {