Browse Source

bump(github.com/gorilla/mux): b08c5fcf14d01cb0c20ddf70157f54d4a2c3a38a

Ben Johnson 12 years ago
parent
commit
34b99ee343

+ 27 - 0
third_party/github.com/gorilla/mux/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 6 - 0
third_party/github.com/gorilla/mux/README.md

@@ -0,0 +1,6 @@
+mux
+===
+
+gorilla/mux is a powerful URL router and dispatcher.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux

+ 21 - 0
third_party/github.com/gorilla/mux/bench_test.go

@@ -0,0 +1,21 @@
+// Copyright 2012 The Gorilla 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 mux
+
+import (
+	"net/http"
+	"testing"
+)
+
+func BenchmarkMux(b *testing.B) {
+	router := new(Router)
+	handler := func(w http.ResponseWriter, r *http.Request) {}
+	router.HandleFunc("/v1/{v1}", handler)
+
+	request, _ := http.NewRequest("GET", "/v1/anything", nil)
+	for i := 0; i < b.N; i++ {
+		router.ServeHTTP(nil, request)
+	}
+}

+ 199 - 0
third_party/github.com/gorilla/mux/doc.go

@@ -0,0 +1,199 @@
+// Copyright 2012 The Gorilla 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 gorilla/mux implements a request router and dispatcher.
+
+The name mux stands for "HTTP request multiplexer". Like the standard
+http.ServeMux, mux.Router matches incoming requests against a list of
+registered routes and calls a handler for the route that matches the URL
+or other conditions. The main features are:
+
+	* Requests can be matched based on URL host, path, path prefix, schemes,
+	  header and query values, HTTP methods or using custom matchers.
+	* URL hosts and paths can have variables with an optional regular
+	  expression.
+	* Registered URLs can be built, or "reversed", which helps maintaining
+	  references to resources.
+	* Routes can be used as subrouters: nested routes are only tested if the
+	  parent route matches. This is useful to define groups of routes that
+	  share common conditions like a host, a path prefix or other repeated
+	  attributes. As a bonus, this optimizes request matching.
+	* It implements the http.Handler interface so it is compatible with the
+	  standard http.ServeMux.
+
+Let's start registering a couple of URL paths and handlers:
+
+	func main() {
+		r := mux.NewRouter()
+		r.HandleFunc("/", HomeHandler)
+		r.HandleFunc("/products", ProductsHandler)
+		r.HandleFunc("/articles", ArticlesHandler)
+		http.Handle("/", r)
+	}
+
+Here we register three routes mapping URL paths to handlers. This is
+equivalent to how http.HandleFunc() works: if an incoming request URL matches
+one of the paths, the corresponding handler is called passing
+(http.ResponseWriter, *http.Request) as parameters.
+
+Paths can have variables. They are defined using the format {name} or
+{name:pattern}. If a regular expression pattern is not defined, the matched
+variable will be anything until the next slash. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/products/{key}", ProductHandler)
+	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+
+The names are used to create a map of route variables which can be retrieved
+calling mux.Vars():
+
+	vars := mux.Vars(request)
+	category := vars["category"]
+
+And this is all you need to know about the basic usage. More advanced options
+are explained below.
+
+Routes can also be restricted to a domain or subdomain. Just define a host
+pattern to be matched. They can also have variables:
+
+	r := mux.NewRouter()
+	// Only matches if domain is "www.domain.com".
+	r.Host("www.domain.com")
+	// Matches a dynamic subdomain.
+	r.Host("{subdomain:[a-z]+}.domain.com")
+
+There are several other matchers that can be added. To match path prefixes:
+
+	r.PathPrefix("/products/")
+
+...or HTTP methods:
+
+	r.Methods("GET", "POST")
+
+...or URL schemes:
+
+	r.Schemes("https")
+
+...or header values:
+
+	r.Headers("X-Requested-With", "XMLHttpRequest")
+
+...or query values:
+
+	r.Queries("key", "value")
+
+...or to use a custom matcher function:
+
+	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
+		return r.ProtoMajor == 0
+    })
+
+...and finally, it is possible to combine several matchers in a single route:
+
+	r.HandleFunc("/products", ProductsHandler).
+	  Host("www.domain.com").
+	  Methods("GET").
+	  Schemes("http")
+
+Setting the same matching conditions again and again can be boring, so we have
+a way to group several routes that share the same requirements.
+We call it "subrouting".
+
+For example, let's say we have several URLs that should only match when the
+host is "www.domain.com". Create a route for that host and get a "subrouter"
+from it:
+
+	r := mux.NewRouter()
+	s := r.Host("www.domain.com").Subrouter()
+
+Then register routes in the subrouter:
+
+	s.HandleFunc("/products/", ProductsHandler)
+	s.HandleFunc("/products/{key}", ProductHandler)
+	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+
+The three URL paths we registered above will only be tested if the domain is
+"www.domain.com", because the subrouter is tested first. This is not
+only convenient, but also optimizes request matching. You can create
+subrouters combining any attribute matchers accepted by a route.
+
+Subrouters can be used to create domain or path "namespaces": you define
+subrouters in a central place and then parts of the app can register its
+paths relatively to a given subrouter.
+
+There's one more thing about subroutes. When a subrouter has a path prefix,
+the inner routes use it as base for their paths:
+
+	r := mux.NewRouter()
+	s := r.PathPrefix("/products").Subrouter()
+	// "/products/"
+	s.HandleFunc("/", ProductsHandler)
+	// "/products/{key}/"
+	s.HandleFunc("/{key}/", ProductHandler)
+	// "/products/{key}/details"
+	s.HandleFunc("/{key}/details"), ProductDetailsHandler)
+
+Now let's see how to build registered URLs.
+
+Routes can be named. All routes that define a name can have their URLs built,
+or "reversed". We define a name calling Name() on a route. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+	  Name("article")
+
+To build a URL, get the route and call the URL() method, passing a sequence of
+key/value pairs for the route variables. For the previous route, we would do:
+
+	url, err := r.Get("article").URL("category", "technology", "id", "42")
+
+...and the result will be a url.URL with the following path:
+
+	"/articles/technology/42"
+
+This also works for host variables:
+
+	r := mux.NewRouter()
+	r.Host("{subdomain}.domain.com").
+	  Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// url.String() will be "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+									 "category", "technology",
+									 "id", "42")
+
+All variables defined in the route are required, and their values must
+conform to the corresponding patterns. These requirements guarantee that a
+generated URL will always match a registered route -- the only exception is
+for explicitly defined "build-only" routes which never match.
+
+There's also a way to build only the URL host or path for a route:
+use the methods URLHost() or URLPath() instead. For the previous route,
+we would do:
+
+	// "http://news.domain.com/"
+	host, err := r.Get("article").URLHost("subdomain", "news")
+
+	// "/articles/technology/42"
+	path, err := r.Get("article").URLPath("category", "technology", "id", "42")
+
+And if you use subrouters, host and path defined separately can be built
+as well:
+
+	r := mux.NewRouter()
+	s := r.Host("{subdomain}.domain.com").Subrouter()
+	s.Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+									 "category", "technology",
+									 "id", "42")
+*/
+package mux

+ 339 - 0
third_party/github.com/gorilla/mux/mux.go

@@ -0,0 +1,339 @@
+// Copyright 2012 The Gorilla 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 mux
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/gorilla/context"
+)
+
+// NewRouter returns a new router instance.
+func NewRouter() *Router {
+	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
+}
+
+// Router registers routes to be matched and dispatches a handler.
+//
+// It implements the http.Handler interface, so it can be registered to serve
+// requests:
+//
+//     var router = mux.NewRouter()
+//
+//     func main() {
+//         http.Handle("/", router)
+//     }
+//
+// Or, for Google App Engine, register it in a init() function:
+//
+//     func init() {
+//         http.Handle("/", router)
+//     }
+//
+// This will send all incoming requests to the router.
+type Router struct {
+	// Configurable Handler to be used when no route matches.
+	NotFoundHandler http.Handler
+	// Parent route, if this is a subrouter.
+	parent parentRoute
+	// Routes to be matched, in order.
+	routes []*Route
+	// Routes by name for URL building.
+	namedRoutes map[string]*Route
+	// See Router.StrictSlash(). This defines the flag for new routes.
+	strictSlash bool
+	// If true, do not clear the request context after handling the request
+	KeepContext bool
+}
+
+// Match matches registered routes against the request.
+func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
+	for _, route := range r.routes {
+		if route.Match(req, match) {
+			return true
+		}
+	}
+	return false
+}
+
+// ServeHTTP dispatches the handler registered in the matched route.
+//
+// When there is a match, the route variables can be retrieved calling
+// mux.Vars(request).
+func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// Clean path to canonical form and redirect.
+	if p := cleanPath(req.URL.Path); p != req.URL.Path {
+		w.Header().Set("Location", p)
+		w.WriteHeader(http.StatusMovedPermanently)
+		return
+	}
+	var match RouteMatch
+	var handler http.Handler
+	if r.Match(req, &match) {
+		handler = match.Handler
+		setVars(req, match.Vars)
+		setCurrentRoute(req, match.Route)
+	}
+	if handler == nil {
+		if r.NotFoundHandler == nil {
+			r.NotFoundHandler = http.NotFoundHandler()
+		}
+		handler = r.NotFoundHandler
+	}
+	if !r.KeepContext {
+		defer context.Clear(req)
+	}
+	handler.ServeHTTP(w, req)
+}
+
+// Get returns a route registered with the given name.
+func (r *Router) Get(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// GetRoute returns a route registered with the given name. This method
+// was renamed to Get() and remains here for backwards compatibility.
+func (r *Router) GetRoute(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// StrictSlash defines the slash behavior for new routes.
+//
+// When true, if the route path is "/path/", accessing "/path" will redirect
+// to the former and vice versa.
+//
+// Special case: when a route sets a path prefix, strict slash is
+// automatically set to false for that route because the redirect behavior
+// can't be determined for prefixes.
+func (r *Router) StrictSlash(value bool) *Router {
+	r.strictSlash = value
+	return r
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Router) getNamedRoutes() map[string]*Route {
+	if r.namedRoutes == nil {
+		if r.parent != nil {
+			r.namedRoutes = r.parent.getNamedRoutes()
+		} else {
+			r.namedRoutes = make(map[string]*Route)
+		}
+	}
+	return r.namedRoutes
+}
+
+// getRegexpGroup returns regexp definitions from the parent route, if any.
+func (r *Router) getRegexpGroup() *routeRegexpGroup {
+	if r.parent != nil {
+		return r.parent.getRegexpGroup()
+	}
+	return nil
+}
+
+// ----------------------------------------------------------------------------
+// Route factories
+// ----------------------------------------------------------------------------
+
+// NewRoute registers an empty route.
+func (r *Router) NewRoute() *Route {
+	route := &Route{parent: r, strictSlash: r.strictSlash}
+	r.routes = append(r.routes, route)
+	return route
+}
+
+// Handle registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.Handler().
+func (r *Router) Handle(path string, handler http.Handler) *Route {
+	return r.NewRoute().Path(path).Handler(handler)
+}
+
+// HandleFunc registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.HandlerFunc().
+func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
+	*http.Request)) *Route {
+	return r.NewRoute().Path(path).HandlerFunc(f)
+}
+
+// Headers registers a new route with a matcher for request header values.
+// See Route.Headers().
+func (r *Router) Headers(pairs ...string) *Route {
+	return r.NewRoute().Headers(pairs...)
+}
+
+// Host registers a new route with a matcher for the URL host.
+// See Route.Host().
+func (r *Router) Host(tpl string) *Route {
+	return r.NewRoute().Host(tpl)
+}
+
+// MatcherFunc registers a new route with a custom matcher function.
+// See Route.MatcherFunc().
+func (r *Router) MatcherFunc(f MatcherFunc) *Route {
+	return r.NewRoute().MatcherFunc(f)
+}
+
+// Methods registers a new route with a matcher for HTTP methods.
+// See Route.Methods().
+func (r *Router) Methods(methods ...string) *Route {
+	return r.NewRoute().Methods(methods...)
+}
+
+// Path registers a new route with a matcher for the URL path.
+// See Route.Path().
+func (r *Router) Path(tpl string) *Route {
+	return r.NewRoute().Path(tpl)
+}
+
+// PathPrefix registers a new route with a matcher for the URL path prefix.
+// See Route.PathPrefix().
+func (r *Router) PathPrefix(tpl string) *Route {
+	return r.NewRoute().PathPrefix(tpl)
+}
+
+// Queries registers a new route with a matcher for URL query values.
+// See Route.Queries().
+func (r *Router) Queries(pairs ...string) *Route {
+	return r.NewRoute().Queries(pairs...)
+}
+
+// Schemes registers a new route with a matcher for URL schemes.
+// See Route.Schemes().
+func (r *Router) Schemes(schemes ...string) *Route {
+	return r.NewRoute().Schemes(schemes...)
+}
+
+// ----------------------------------------------------------------------------
+// Context
+// ----------------------------------------------------------------------------
+
+// RouteMatch stores information about a matched route.
+type RouteMatch struct {
+	Route   *Route
+	Handler http.Handler
+	Vars    map[string]string
+}
+
+type contextKey int
+
+const (
+	varsKey contextKey = iota
+	routeKey
+)
+
+// Vars returns the route variables for the current request, if any.
+func Vars(r *http.Request) map[string]string {
+	if rv := context.Get(r, varsKey); rv != nil {
+		return rv.(map[string]string)
+	}
+	return nil
+}
+
+// CurrentRoute returns the matched route for the current request, if any.
+func CurrentRoute(r *http.Request) *Route {
+	if rv := context.Get(r, routeKey); rv != nil {
+		return rv.(*Route)
+	}
+	return nil
+}
+
+func setVars(r *http.Request, val interface{}) {
+	context.Set(r, varsKey, val)
+}
+
+func setCurrentRoute(r *http.Request, val interface{}) {
+	context.Set(r, routeKey, val)
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+// cleanPath returns the canonical path for p, eliminating . and .. elements.
+// Borrowed from the net/http package.
+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
+}
+
+// uniqueVars returns an error if two slices contain duplicated strings.
+func uniqueVars(s1, s2 []string) error {
+	for _, v1 := range s1 {
+		for _, v2 := range s2 {
+			if v1 == v2 {
+				return fmt.Errorf("mux: duplicated route variable %q", v2)
+			}
+		}
+	}
+	return nil
+}
+
+// mapFromPairs converts variadic string parameters to a string map.
+func mapFromPairs(pairs ...string) (map[string]string, error) {
+	length := len(pairs)
+	if length%2 != 0 {
+		return nil, fmt.Errorf(
+			"mux: number of parameters must be multiple of 2, got %v", pairs)
+	}
+	m := make(map[string]string, length/2)
+	for i := 0; i < length; i += 2 {
+		m[pairs[i]] = pairs[i+1]
+	}
+	return m, nil
+}
+
+// matchInArray returns true if the given string value is in the array.
+func matchInArray(arr []string, value string) bool {
+	for _, v := range arr {
+		if v == value {
+			return true
+		}
+	}
+	return false
+}
+
+// matchMap returns true if the given key/value pairs exist in a given map.
+func matchMap(toCheck map[string]string, toMatch map[string][]string,
+	canonicalKey bool) bool {
+	for k, v := range toCheck {
+		// Check if key exists.
+		if canonicalKey {
+			k = http.CanonicalHeaderKey(k)
+		}
+		if values := toMatch[k]; values == nil {
+			return false
+		} else if v != "" {
+			// If value was defined as an empty string we only check that the
+			// key exists. Otherwise we also check for equality.
+			valueExists := false
+			for _, value := range values {
+				if v == value {
+					valueExists = true
+					break
+				}
+			}
+			if !valueExists {
+				return false
+			}
+		}
+	}
+	return true
+}

+ 755 - 0
third_party/github.com/gorilla/mux/mux_test.go

@@ -0,0 +1,755 @@
+// Copyright 2012 The Gorilla 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 mux
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/gorilla/context"
+)
+
+type routeTest struct {
+	title       string            // title of the test
+	route       *Route            // the route being tested
+	request     *http.Request     // a request to test the route
+	vars        map[string]string // the expected vars of the match
+	host        string            // the expected host of the match
+	path        string            // the expected path of the match
+	shouldMatch bool              // whether the request is expected to match the route at all
+}
+
+func TestHost(t *testing.T) {
+	// newRequestHost a new request with a method, url, and host header
+	newRequestHost := func(method, url, host string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		req.Host = host
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Host route match",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with port, match",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with port, wrong port in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route, match with host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		// BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
+		{
+			title:       "Host route with port, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with pattern, match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with pattern, wrong host in request URL",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with multiple patterns, match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with multiple patterns, wrong host in request URL",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Path route, match",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route, wrong path in request in request URL",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Path route with pattern, match",
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route with pattern, URL in request does not match",
+			route:       new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Path route with multiple patterns, match",
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route with multiple patterns, URL in request does not match",
+			route:       new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestPathPrefix(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "PathPrefix route, match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: false,
+		},
+		{
+			title:       "PathPrefix route with pattern, match",
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route with pattern, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: false,
+		},
+		{
+			title:       "PathPrefix route with multiple patterns, match",
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route with multiple patterns, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:     newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:        map[string]string{"v1": "111", "v2": "222"},
+			host:        "",
+			path:        "/111/222",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestHostPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Host and Path route, match",
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route, wrong host in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host and Path route with pattern, match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route with pattern, URL in request does not match",
+			route:       new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "bbb", "v2": "222"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host and Path route with multiple patterns, match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host and Path route with multiple patterns, URL in request does not match",
+			route:       new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:        "aaa.bbb.ccc",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestHeaders(t *testing.T) {
+	// newRequestHeaders creates a new request with a method, url, and headers
+	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		for k, v := range headers {
+			req.Header.Add(k, v)
+		}
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Headers route, match",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Headers route, bad header values",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+
+}
+
+func TestMethods(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Methods route, match GET",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, match POST",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("POST", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, bad method",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("PUT", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestQueries(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Queries route, match",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route, bad query",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=dong"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestSchemes(t *testing.T) {
+	tests := []routeTest{
+		// Schemes
+		{
+			title:       "Schemes route, match https",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "https://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, match ftp",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "ftp://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, bad scheme",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestMatcherFunc(t *testing.T) {
+	m := func(r *http.Request, m *RouteMatch) bool {
+		if r.URL.Host == "aaa.bbb.ccc" {
+			return true
+		}
+		return false
+	}
+
+	tests := []routeTest{
+		{
+			title:       "MatchFunc route, match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "MatchFunc route, non-match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestSubRouter(t *testing.T) {
+	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
+	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
+
+	tests := []routeTest{
+		{
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
+			request:     newRequest("GET", "http://aaa.google.com/bbb"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:        "aaa.google.com",
+			path:        "/bbb",
+			shouldMatch: true,
+		},
+		{
+			route:       subrouter1.Path("/{v2:[a-z]+}"),
+			request:     newRequest("GET", "http://111.google.com/111"),
+			vars:        map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:        "aaa.google.com",
+			path:        "/bbb",
+			shouldMatch: false,
+		},
+		{
+			route:       subrouter2.Path("/baz/{v2}"),
+			request:     newRequest("GET", "http://localhost/foo/bar/baz/ding"),
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
+			host:        "",
+			path:        "/foo/bar/baz/ding",
+			shouldMatch: true,
+		},
+		{
+			route:       subrouter2.Path("/baz/{v2}"),
+			request:     newRequest("GET", "http://localhost/foo/bar"),
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
+			host:        "",
+			path:        "/foo/bar/baz/ding",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+	}
+}
+
+func TestNamedRoutes(t *testing.T) {
+	r1 := NewRouter()
+	r1.NewRoute().Name("a")
+	r1.NewRoute().Name("b")
+	r1.NewRoute().Name("c")
+
+	r2 := r1.NewRoute().Subrouter()
+	r2.NewRoute().Name("d")
+	r2.NewRoute().Name("e")
+	r2.NewRoute().Name("f")
+
+	r3 := r2.NewRoute().Subrouter()
+	r3.NewRoute().Name("g")
+	r3.NewRoute().Name("h")
+	r3.NewRoute().Name("i")
+
+	if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
+		t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
+	} else if r1.Get("i") == nil {
+		t.Errorf("Subroute name not registered")
+	}
+}
+
+func TestStrictSlash(t *testing.T) {
+	var r *Router
+	var req *http.Request
+	var route *Route
+	var match *RouteMatch
+	var matched bool
+
+	// StrictSlash should be ignored for path prefix.
+	// So we register a route ending in slash but it doesn't attempt to add
+	// the slash for a path not ending in slash.
+	r = NewRouter()
+	r.StrictSlash(true)
+	route = r.NewRoute().PathPrefix("/static/")
+	req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil)
+	match = new(RouteMatch)
+	matched = r.Match(req, match)
+	if !matched {
+		t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route))
+	}
+	if match.Handler != nil {
+		t.Errorf("Should not redirect")
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+func getRouteTemplate(route *Route) string {
+	host, path := "none", "none"
+	if route.regexp != nil {
+		if route.regexp.host != nil {
+			host = route.regexp.host.template
+		}
+		if route.regexp.path != nil {
+			path = route.regexp.path.template
+		}
+	}
+	return fmt.Sprintf("Host: %v, Path: %v", host, path)
+}
+
+func testRoute(t *testing.T, test routeTest) {
+	request := test.request
+	route := test.route
+	vars := test.vars
+	shouldMatch := test.shouldMatch
+	host := test.host
+	path := test.path
+	url := test.host + test.path
+
+	var match RouteMatch
+	ok := route.Match(request, &match)
+	if ok != shouldMatch {
+		msg := "Should match"
+		if !shouldMatch {
+			msg = "Should not match"
+		}
+		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
+		return
+	}
+	if shouldMatch {
+		if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
+			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
+			return
+		}
+		if host != "" {
+			u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
+			if host != u.Host {
+				t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
+				return
+			}
+		}
+		if path != "" {
+			u, _ := route.URLPath(mapToPairs(match.Vars)...)
+			if path != u.Path {
+				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+		if url != "" {
+			u, _ := route.URL(mapToPairs(match.Vars)...)
+			if url != u.Host+u.Path {
+				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+	}
+}
+
+// Tests that the context is cleared or not cleared properly depending on
+// the configuration of the router
+func TestKeepContext(t *testing.T) {
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
+
+	r := NewRouter()
+	r.HandleFunc("/", func1).Name("func1")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	res := new(http.ResponseWriter)
+	r.ServeHTTP(*res, req)
+
+	if _, ok := context.GetOk(req, "t"); ok {
+		t.Error("Context should have been cleared at end of request")
+	}
+
+	r.KeepContext = true
+
+	req, _ = http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	r.ServeHTTP(*res, req)
+	if _, ok := context.GetOk(req, "t"); !ok {
+		t.Error("Context should NOT have been cleared at end of request")
+	}
+
+}
+
+// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
+func TestSubrouterHeader(t *testing.T) {
+	expected := "func1 response"
+	func1 := func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, expected)
+	}
+	func2 := func(http.ResponseWriter, *http.Request) {}
+
+	r := NewRouter()
+	s := r.Headers("SomeSpecialHeader", "").Subrouter()
+	s.HandleFunc("/", func1).Name("func1")
+	r.HandleFunc("/", func2).Name("func2")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	req.Header.Add("SomeSpecialHeader", "foo")
+	match := new(RouteMatch)
+	matched := r.Match(req, match)
+	if !matched {
+		t.Errorf("Should match request")
+	}
+	if match.Route.GetName() != "func1" {
+		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
+	}
+	resp := NewRecorder()
+	match.Handler.ServeHTTP(resp, req)
+	if resp.Body.String() != expected {
+		t.Errorf("Expecting %q", expected)
+	}
+}
+
+// mapToPairs converts a string map to a slice of string pairs
+func mapToPairs(m map[string]string) []string {
+	var i int
+	p := make([]string, len(m)*2)
+	for k, v := range m {
+		p[i] = k
+		p[i+1] = v
+		i += 2
+	}
+	return p
+}
+
+// stringMapEqual checks the equality of two string maps
+func stringMapEqual(m1, m2 map[string]string) bool {
+	nil1 := m1 == nil
+	nil2 := m2 == nil
+	if nil1 != nil2 || len(m1) != len(m2) {
+		return false
+	}
+	for k, v := range m1 {
+		if v != m2[k] {
+			return false
+		}
+	}
+	return true
+}
+
+// newRequest is a helper function to create a new request with a method and url
+func newRequest(method, url string) *http.Request {
+	req, err := http.NewRequest(method, url, nil)
+	if err != nil {
+		panic(err)
+	}
+	return req
+}

+ 758 - 0
third_party/github.com/gorilla/mux/old_test.go

@@ -0,0 +1,758 @@
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
+
+// Copyright 2011 Gorilla 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 mux
+
+import (
+	"bytes"
+	"net/http"
+	"testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// 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.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+	Code      int           // the HTTP response code from WriteHeader
+	HeaderMap http.Header   // the HTTP response headers
+	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+	Flushed   bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+	return &ResponseRecorder{
+		HeaderMap: make(http.Header),
+		Body:      new(bytes.Buffer),
+	}
+}
+
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
+const DefaultRemoteAddr = "1.2.3.4"
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+	return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+	if rw.Body != nil {
+		rw.Body.Write(buf)
+	}
+	if rw.Code == 0 {
+		rw.Code = http.StatusOK
+	}
+	return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+	rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+	rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+func TestRouteMatchers(t *testing.T) {
+	var scheme, host, path, query, method string
+	var headers map[string]string
+	var resultVars map[bool]map[string]string
+
+	router := NewRouter()
+	router.NewRoute().Host("{var1}.google.com").
+		Path("/{var2:[a-z]+}/{var3:[0-9]+}").
+		Queries("foo", "bar").
+		Methods("GET").
+		Schemes("https").
+		Headers("x-requested-with", "XMLHttpRequest")
+	router.NewRoute().Host("www.{var4}.com").
+		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
+		Queries("baz", "ding").
+		Methods("POST").
+		Schemes("http").
+		Headers("Content-Type", "application/json")
+
+	reset := func() {
+		// Everything match.
+		scheme = "https"
+		host = "www.google.com"
+		path = "/product/42"
+		query = "?foo=bar"
+		method = "GET"
+		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
+		resultVars = map[bool]map[string]string{
+			true:  map[string]string{"var1": "www", "var2": "product", "var3": "42"},
+			false: map[string]string{},
+		}
+	}
+
+	reset2 := func() {
+		// Everything match.
+		scheme = "http"
+		host = "www.google.com"
+		path = "/foo/product/42/path/that/is/ignored"
+		query = "?baz=ding"
+		method = "POST"
+		headers = map[string]string{"Content-Type": "application/json"}
+		resultVars = map[bool]map[string]string{
+			true:  map[string]string{"var4": "google", "var5": "product", "var6": "42"},
+			false: map[string]string{},
+		}
+	}
+
+	match := func(shouldMatch bool) {
+		url := scheme + "://" + host + path + query
+		request, _ := http.NewRequest(method, url, nil)
+		for key, value := range headers {
+			request.Header.Add(key, value)
+		}
+
+		var routeMatch RouteMatch
+		matched := router.Match(request, &routeMatch)
+		if matched != shouldMatch {
+			// Need better messages. :)
+			if matched {
+				t.Errorf("Should match.")
+			} else {
+				t.Errorf("Should not match.")
+			}
+		}
+
+		if matched {
+			currentRoute := routeMatch.Route
+			if currentRoute == nil {
+				t.Errorf("Expected a current route.")
+			}
+			vars := routeMatch.Vars
+			expectedVars := resultVars[shouldMatch]
+			if len(vars) != len(expectedVars) {
+				t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+			}
+			for name, value := range vars {
+				if expectedVars[name] != value {
+					t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+				}
+			}
+		}
+	}
+
+	// 1st route --------------------------------------------------------------
+
+	// Everything match.
+	reset()
+	match(true)
+
+	// Scheme doesn't match.
+	reset()
+	scheme = "http"
+	match(false)
+
+	// Host doesn't match.
+	reset()
+	host = "www.mygoogle.com"
+	match(false)
+
+	// Path doesn't match.
+	reset()
+	path = "/product/notdigits"
+	match(false)
+
+	// Query doesn't match.
+	reset()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset()
+	method = "POST"
+	match(false)
+
+	// Header doesn't match.
+	reset()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset()
+	match(true)
+
+	// 2nd route --------------------------------------------------------------
+
+	// Everything match.
+	reset2()
+	match(true)
+
+	// Scheme doesn't match.
+	reset2()
+	scheme = "https"
+	match(false)
+
+	// Host doesn't match.
+	reset2()
+	host = "sub.google.com"
+	match(false)
+
+	// Path doesn't match.
+	reset2()
+	path = "/bar/product/42"
+	match(false)
+
+	// Query doesn't match.
+	reset2()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset2()
+	method = "GET"
+	match(false)
+
+	// Header doesn't match.
+	reset2()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset2()
+	match(true)
+}
+
+type headerMatcherTest struct {
+	matcher headerMatcher
+	headers map[string]string
+	result  bool
+}
+
+var headerMatcherTests = []headerMatcherTest{
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
+		headers: map[string]string{"X-Requested-With": "anything"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{},
+		result:  false,
+	},
+}
+
+type hostMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var hostMatcherTests = []hostMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://abc.def.ghi/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://a.b.c/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  false,
+	},
+}
+
+type methodMatcherTest struct {
+	matcher methodMatcher
+	method  string
+	result  bool
+}
+
+var methodMatcherTests = []methodMatcherTest{
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "GET",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "POST",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "PUT",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "DELETE",
+		result:  false,
+	},
+}
+
+type pathMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var pathMatcherTests = []pathMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/123/456/789",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/1/2/3",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  false,
+	},
+}
+
+type queryMatcherTest struct {
+	matcher queryMatcher
+	url     string
+	result  bool
+}
+
+var queryMatcherTests = []queryMatcherTest{
+	{
+		matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  true,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}),
+		url:     "http://localhost:8080/?foo=anything&baz=anything",
+		result:  true,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  false,
+	},
+	{
+		matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}),
+		url:     "http://localhost:8080/?foo=bar&baz=ding",
+		result:  false,
+	},
+}
+
+type schemeMatcherTest struct {
+	matcher schemeMatcher
+	url     string
+	result  bool
+}
+
+var schemeMatcherTests = []schemeMatcherTest{
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "http://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "https://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"https"}),
+		url:     "http://localhost:8080/",
+		result:  false,
+	},
+	{
+		matcher: schemeMatcher([]string{"http"}),
+		url:     "https://localhost:8080/",
+		result:  false,
+	},
+}
+
+type urlBuildingTest struct {
+	route *Route
+	vars  []string
+	url   string
+}
+
+var urlBuildingTests = []urlBuildingTest{
+	{
+		route: new(Route).Host("foo.domain.com"),
+		vars:  []string{},
+		url:   "http://foo.domain.com",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com"),
+		vars:  []string{"subdomain", "bar"},
+		url:   "http://bar.domain.com",
+	},
+	{
+		route: new(Route).Host("foo.domain.com").Path("/articles"),
+		vars:  []string{},
+		url:   "http://foo.domain.com/articles",
+	},
+	{
+		route: new(Route).Path("/articles"),
+		vars:  []string{},
+		url:   "/articles",
+	},
+	{
+		route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"category", "technology", "id", "42"},
+		url:   "/articles/technology/42",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"subdomain", "foo", "category", "technology", "id", "42"},
+		url:   "http://foo.domain.com/articles/technology/42",
+	},
+}
+
+func TestHeaderMatcher(t *testing.T) {
+	for _, v := range headerMatcherTests {
+		request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+		for key, value := range v.headers {
+			request.Header.Add(key, value)
+		}
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, request.Header)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
+			}
+		}
+	}
+}
+
+func TestHostMatcher(t *testing.T) {
+	for _, v := range hostMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestMethodMatcher(t *testing.T) {
+	for _, v := range methodMatcherTests {
+		request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.method)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.method)
+			}
+		}
+	}
+}
+
+func TestPathMatcher(t *testing.T) {
+	for _, v := range pathMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestQueryMatcher(t *testing.T) {
+	for _, v := range queryMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+	}
+}
+
+func TestSchemeMatcher(t *testing.T) {
+	for _, v := range queryMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+	}
+}
+
+func TestUrlBuilding(t *testing.T) {
+
+	for _, v := range urlBuildingTests {
+		u, _ := v.route.URL(v.vars...)
+		url := u.String()
+		if url != v.url {
+			t.Errorf("expected %v, got %v", v.url, url)
+			/*
+				reversePath := ""
+				reverseHost := ""
+				if v.route.pathTemplate != nil {
+						reversePath = v.route.pathTemplate.Reverse
+				}
+				if v.route.hostTemplate != nil {
+						reverseHost = v.route.hostTemplate.Reverse
+				}
+
+				t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
+			*/
+		}
+	}
+
+	ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
+	}
+
+	router := NewRouter()
+	router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
+
+	url, _ := router.Get("article").URL("category", "technology", "id", "42")
+	expected := "/articles/technology/42"
+	if url.String() != expected {
+		t.Errorf("Expected %v, got %v", expected, url.String())
+	}
+}
+
+func TestMatchedRouteName(t *testing.T) {
+	routeName := "stock"
+	router := NewRouter()
+	route := router.NewRoute().Path("/products/").Name(routeName)
+
+	url := "http://www.domain.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	retName := rv.Route.GetName()
+	if retName != routeName {
+		t.Errorf("Expected %q, got %q.", routeName, retName)
+	}
+}
+
+func TestSubRouting(t *testing.T) {
+	// Example from docs.
+	router := NewRouter()
+	subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
+	route := subrouter.NewRoute().Path("/products/").Name("products")
+
+	url := "http://www.domain.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	u, _ := router.Get("products").URL()
+	builtUrl := u.String()
+	// Yay, subroute aware of the domain when building!
+	if builtUrl != url {
+		t.Errorf("Expected %q, got %q.", url, builtUrl)
+	}
+}
+
+func TestVariableNames(t *testing.T) {
+	route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
+	if route.err == nil {
+		t.Errorf("Expected error for duplicated variable names")
+	}
+}
+
+func TestRedirectSlash(t *testing.T) {
+	var route *Route
+	var routeMatch RouteMatch
+	r := NewRouter()
+
+	r.StrictSlash(false)
+	route = r.NewRoute()
+	if route.strictSlash != false {
+		t.Errorf("Expected false redirectSlash.")
+	}
+
+	r.StrictSlash(true)
+	route = r.NewRoute()
+	if route.strictSlash != true {
+		t.Errorf("Expected true redirectSlash.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}/")
+	request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars := routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp := NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
+		t.Errorf("Expected redirect header.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}")
+	request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars = routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp = NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
+		t.Errorf("Expected redirect header.")
+	}
+}
+
+// Test for the new regexp library, still not available in stable Go.
+func TestNewRegexp(t *testing.T) {
+	var p *routeRegexp
+	var matches []string
+
+	tests := map[string]map[string][]string{
+		"/{foo:a{2}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  nil,
+			"/aaaa": nil,
+		},
+		"/{foo:a{2,}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": {"aaaa"},
+		},
+		"/{foo:a{2,3}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": nil,
+		},
+		"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
+			"/a":       nil,
+			"/ab":      nil,
+			"/abc":     nil,
+			"/abcd":    nil,
+			"/abc/ab":  {"abc", "ab"},
+			"/abc/abc": nil,
+			"/abcd/ab": nil,
+		},
+		`/{foo:\w{3,}}/{bar:\d{2,}}`: {
+			"/a":        nil,
+			"/ab":       nil,
+			"/abc":      nil,
+			"/abc/1":    nil,
+			"/abc/12":   {"abc", "12"},
+			"/abcd/12":  {"abcd", "12"},
+			"/abcd/123": {"abcd", "123"},
+		},
+	}
+
+	for pattern, paths := range tests {
+		p, _ = newRouteRegexp(pattern, false, false, false)
+		for path, result := range paths {
+			matches = p.regexp.FindStringSubmatch(path)
+			if result == nil {
+				if matches != nil {
+					t.Errorf("%v should not match %v.", pattern, path)
+				}
+			} else {
+				if len(matches) != len(result)+1 {
+					t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
+				} else {
+					for k, v := range result {
+						if matches[k+1] != v {
+							t.Errorf("Expected %v, got %v.", v, matches[k+1])
+						}
+					}
+				}
+			}
+		}
+	}
+}

+ 247 - 0
third_party/github.com/gorilla/mux/regexp.go

@@ -0,0 +1,247 @@
+// Copyright 2012 The Gorilla 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 mux
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+// newRouteRegexp parses a route template and returns a routeRegexp,
+// used to match a host or path.
+//
+// It will extract named variables, assemble a regexp to be matched, create
+// a "reverse" template to build URLs and compile regexps to validate variable
+// values used in URL building.
+//
+// Previously we accepted only Python-like identifiers for variable
+// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
+// name and pattern can't be empty, and names can't contain a colon.
+func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) {
+	// Check if it is well-formed.
+	idxs, errBraces := braceIndices(tpl)
+	if errBraces != nil {
+		return nil, errBraces
+	}
+	// Backup the original.
+	template := tpl
+	// Now let's parse it.
+	defaultPattern := "[^/]+"
+	if matchHost {
+		defaultPattern = "[^.]+"
+		matchPrefix, strictSlash = false, false
+	}
+	if matchPrefix {
+		strictSlash = false
+	}
+	// Set a flag for strictSlash.
+	endSlash := false
+	if strictSlash && strings.HasSuffix(tpl, "/") {
+		tpl = tpl[:len(tpl)-1]
+		endSlash = true
+	}
+	varsN := make([]string, len(idxs)/2)
+	varsR := make([]*regexp.Regexp, len(idxs)/2)
+	pattern := bytes.NewBufferString("^")
+	reverse := bytes.NewBufferString("")
+	var end int
+	var err error
+	for i := 0; i < len(idxs); i += 2 {
+		// Set all values we are interested in.
+		raw := tpl[end:idxs[i]]
+		end = idxs[i+1]
+		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
+		name := parts[0]
+		patt := defaultPattern
+		if len(parts) == 2 {
+			patt = parts[1]
+		}
+		// Name or pattern can't be empty.
+		if name == "" || patt == "" {
+			return nil, fmt.Errorf("mux: missing name or pattern in %q",
+				tpl[idxs[i]:end])
+		}
+		// Build the regexp pattern.
+		fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
+		// Build the reverse template.
+		fmt.Fprintf(reverse, "%s%%s", raw)
+		// Append variable name and compiled pattern.
+		varsN[i/2] = name
+		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
+		if err != nil {
+			return nil, err
+		}
+	}
+	// Add the remaining.
+	raw := tpl[end:]
+	pattern.WriteString(regexp.QuoteMeta(raw))
+	if strictSlash {
+		pattern.WriteString("[/]?")
+	}
+	if !matchPrefix {
+		pattern.WriteByte('$')
+	}
+	reverse.WriteString(raw)
+	if endSlash {
+		reverse.WriteByte('/')
+	}
+	// Compile full regexp.
+	reg, errCompile := regexp.Compile(pattern.String())
+	if errCompile != nil {
+		return nil, errCompile
+	}
+	// Done!
+	return &routeRegexp{
+		template:  template,
+		matchHost: matchHost,
+		regexp:    reg,
+		reverse:   reverse.String(),
+		varsN:     varsN,
+		varsR:     varsR,
+	}, nil
+}
+
+// routeRegexp stores a regexp to match a host or path and information to
+// collect and validate route variables.
+type routeRegexp struct {
+	// The unmodified template.
+	template string
+	// True for host match, false for path match.
+	matchHost bool
+	// Expanded regexp.
+	regexp *regexp.Regexp
+	// Reverse template.
+	reverse string
+	// Variable names.
+	varsN []string
+	// Variable regexps (validators).
+	varsR []*regexp.Regexp
+}
+
+// Match matches the regexp against the URL host or path.
+func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
+	if !r.matchHost {
+		return r.regexp.MatchString(req.URL.Path)
+	}
+	return r.regexp.MatchString(getHost(req))
+}
+
+// url builds a URL part using the given values.
+func (r *routeRegexp) url(pairs ...string) (string, error) {
+	values, err := mapFromPairs(pairs...)
+	if err != nil {
+		return "", err
+	}
+	urlValues := make([]interface{}, len(r.varsN))
+	for k, v := range r.varsN {
+		value, ok := values[v]
+		if !ok {
+			return "", fmt.Errorf("mux: missing route variable %q", v)
+		}
+		urlValues[k] = value
+	}
+	rv := fmt.Sprintf(r.reverse, urlValues...)
+	if !r.regexp.MatchString(rv) {
+		// The URL is checked against the full regexp, instead of checking
+		// individual variables. This is faster but to provide a good error
+		// message, we check individual regexps if the URL doesn't match.
+		for k, v := range r.varsN {
+			if !r.varsR[k].MatchString(values[v]) {
+				return "", fmt.Errorf(
+					"mux: variable %q doesn't match, expected %q", values[v],
+					r.varsR[k].String())
+			}
+		}
+	}
+	return rv, nil
+}
+
+// braceIndices returns the first level curly brace indices from a string.
+// It returns an error in case of unbalanced braces.
+func braceIndices(s string) ([]int, error) {
+	var level, idx int
+	idxs := make([]int, 0)
+	for i := 0; i < len(s); i++ {
+		switch s[i] {
+		case '{':
+			if level++; level == 1 {
+				idx = i
+			}
+		case '}':
+			if level--; level == 0 {
+				idxs = append(idxs, idx, i+1)
+			} else if level < 0 {
+				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+			}
+		}
+	}
+	if level != 0 {
+		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+	}
+	return idxs, nil
+}
+
+// ----------------------------------------------------------------------------
+// routeRegexpGroup
+// ----------------------------------------------------------------------------
+
+// routeRegexpGroup groups the route matchers that carry variables.
+type routeRegexpGroup struct {
+	host *routeRegexp
+	path *routeRegexp
+}
+
+// setMatch extracts the variables from the URL once a route matches.
+func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
+	// Store host variables.
+	if v.host != nil {
+		hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
+		if hostVars != nil {
+			for k, v := range v.host.varsN {
+				m.Vars[v] = hostVars[k+1]
+			}
+		}
+	}
+	// Store path variables.
+	if v.path != nil {
+		pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
+		if pathVars != nil {
+			for k, v := range v.path.varsN {
+				m.Vars[v] = pathVars[k+1]
+			}
+			// Check if we should redirect.
+			if r.strictSlash {
+				p1 := strings.HasSuffix(req.URL.Path, "/")
+				p2 := strings.HasSuffix(v.path.template, "/")
+				if p1 != p2 {
+					u, _ := url.Parse(req.URL.String())
+					if p1 {
+						u.Path = u.Path[:len(u.Path)-1]
+					} else {
+						u.Path += "/"
+					}
+					m.Handler = http.RedirectHandler(u.String(), 301)
+				}
+			}
+		}
+	}
+}
+
+// getHost tries its best to return the request host.
+func getHost(r *http.Request) string {
+	if !r.URL.IsAbs() {
+		host := r.Host
+		// Slice off any port information.
+		if i := strings.Index(host, ":"); i != -1 {
+			host = host[:i]
+		}
+		return host
+	}
+	return r.URL.Host
+}

+ 499 - 0
third_party/github.com/gorilla/mux/route.go

@@ -0,0 +1,499 @@
+// Copyright 2012 The Gorilla 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 mux
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+// Route stores information to match a request and build URLs.
+type Route struct {
+	// Parent where the route was registered (a Router).
+	parent parentRoute
+	// Request handler for the route.
+	handler http.Handler
+	// List of matchers.
+	matchers []matcher
+	// Manager for the variables from host and path.
+	regexp *routeRegexpGroup
+	// If true, when the path pattern is "/path/", accessing "/path" will
+	// redirect to the former and vice versa.
+	strictSlash bool
+	// If true, this route never matches: it is only used to build URLs.
+	buildOnly bool
+	// The name used to build URLs.
+	name string
+	// Error resulted from building a route.
+	err error
+}
+
+// Match matches the route against the request.
+func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
+	if r.buildOnly || r.err != nil {
+		return false
+	}
+	// Match everything.
+	for _, m := range r.matchers {
+		if matched := m.Match(req, match); !matched {
+			return false
+		}
+	}
+	// Yay, we have a match. Let's collect some info about it.
+	if match.Route == nil {
+		match.Route = r
+	}
+	if match.Handler == nil {
+		match.Handler = r.handler
+	}
+	if match.Vars == nil {
+		match.Vars = make(map[string]string)
+	}
+	// Set variables.
+	if r.regexp != nil {
+		r.regexp.setMatch(req, match, r)
+	}
+	return true
+}
+
+// ----------------------------------------------------------------------------
+// Route attributes
+// ----------------------------------------------------------------------------
+
+// GetError returns an error resulted from building the route, if any.
+func (r *Route) GetError() error {
+	return r.err
+}
+
+// BuildOnly sets the route to never match: it is only used to build URLs.
+func (r *Route) BuildOnly() *Route {
+	r.buildOnly = true
+	return r
+}
+
+// Handler --------------------------------------------------------------------
+
+// Handler sets a handler for the route.
+func (r *Route) Handler(handler http.Handler) *Route {
+	if r.err == nil {
+		r.handler = handler
+	}
+	return r
+}
+
+// HandlerFunc sets a handler function for the route.
+func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
+	return r.Handler(http.HandlerFunc(f))
+}
+
+// GetHandler returns the handler for the route, if any.
+func (r *Route) GetHandler() http.Handler {
+	return r.handler
+}
+
+// Name -----------------------------------------------------------------------
+
+// Name sets the name for the route, used to build URLs.
+// If the name was registered already it will be overwritten.
+func (r *Route) Name(name string) *Route {
+	if r.name != "" {
+		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
+			r.name, name)
+	}
+	if r.err == nil {
+		r.name = name
+		r.getNamedRoutes()[name] = r
+	}
+	return r
+}
+
+// GetName returns the name for the route, if any.
+func (r *Route) GetName() string {
+	return r.name
+}
+
+// ----------------------------------------------------------------------------
+// Matchers
+// ----------------------------------------------------------------------------
+
+// matcher types try to match a request.
+type matcher interface {
+	Match(*http.Request, *RouteMatch) bool
+}
+
+// addMatcher adds a matcher to the route.
+func (r *Route) addMatcher(m matcher) *Route {
+	if r.err == nil {
+		r.matchers = append(r.matchers, m)
+	}
+	return r
+}
+
+// addRegexpMatcher adds a host or path matcher and builder to a route.
+func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error {
+	if r.err != nil {
+		return r.err
+	}
+	r.regexp = r.getRegexpGroup()
+	if !matchHost {
+		if len(tpl) == 0 || tpl[0] != '/' {
+			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
+		}
+		if r.regexp.path != nil {
+			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
+		}
+	}
+	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash)
+	if err != nil {
+		return err
+	}
+	if matchHost {
+		if r.regexp.path != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
+				return err
+			}
+		}
+		r.regexp.host = rr
+	} else {
+		if r.regexp.host != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
+				return err
+			}
+		}
+		r.regexp.path = rr
+	}
+	r.addMatcher(rr)
+	return nil
+}
+
+// Headers --------------------------------------------------------------------
+
+// headerMatcher matches the request against header values.
+type headerMatcher map[string]string
+
+func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMap(m, r.Header, true)
+}
+
+// Headers adds a matcher for request header values.
+// It accepts a sequence of key/value pairs to be matched. For example:
+//
+//     r := mux.NewRouter()
+//     r.Headers("Content-Type", "application/json",
+//               "X-Requested-With", "XMLHttpRequest")
+//
+// The above route will only match if both request header values match.
+//
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) Headers(pairs ...string) *Route {
+	if r.err == nil {
+		var headers map[string]string
+		headers, r.err = mapFromPairs(pairs...)
+		return r.addMatcher(headerMatcher(headers))
+	}
+	return r
+}
+
+// Host -----------------------------------------------------------------------
+
+// Host adds a matcher for the URL host.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next dot.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Host("www.domain.com")
+//     r.Host("{subdomain}.domain.com")
+//     r.Host("{subdomain:[a-z]+}.domain.com")
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Host(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, true, false)
+	return r
+}
+
+// MatcherFunc ----------------------------------------------------------------
+
+// MatcherFunc is the function signature used by custom matchers.
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
+	return m(r, match)
+}
+
+// MatcherFunc adds a custom function to be used as request matcher.
+func (r *Route) MatcherFunc(f MatcherFunc) *Route {
+	return r.addMatcher(f)
+}
+
+// Methods --------------------------------------------------------------------
+
+// methodMatcher matches the request against HTTP methods.
+type methodMatcher []string
+
+func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.Method)
+}
+
+// Methods adds a matcher for HTTP methods.
+// It accepts a sequence of one or more methods to be matched, e.g.:
+// "GET", "POST", "PUT".
+func (r *Route) Methods(methods ...string) *Route {
+	for k, v := range methods {
+		methods[k] = strings.ToUpper(v)
+	}
+	return r.addMatcher(methodMatcher(methods))
+}
+
+// Path -----------------------------------------------------------------------
+
+// Path adds a matcher for the URL path.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Path("/products/").Handler(ProductsHandler)
+//     r.Path("/products/{key}").Handler(ProductsHandler)
+//     r.Path("/articles/{category}/{id:[0-9]+}").
+//       Handler(ArticleHandler)
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Path(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, false, false)
+	return r
+}
+
+// PathPrefix -----------------------------------------------------------------
+
+// PathPrefix adds a matcher for the URL path prefix.
+func (r *Route) PathPrefix(tpl string) *Route {
+	r.strictSlash = false
+	r.err = r.addRegexpMatcher(tpl, false, true)
+	return r
+}
+
+// Query ----------------------------------------------------------------------
+
+// queryMatcher matches the request against URL queries.
+type queryMatcher map[string]string
+
+func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMap(m, r.URL.Query(), false)
+}
+
+// Queries adds a matcher for URL query values.
+// It accepts a sequence of key/value pairs. For example:
+//
+//     r := mux.NewRouter()
+//     r.Queries("foo", "bar", "baz", "ding")
+//
+// The above route will only match if the URL contains the defined queries
+// values, e.g.: ?foo=bar&baz=ding.
+//
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) Queries(pairs ...string) *Route {
+	if r.err == nil {
+		var queries map[string]string
+		queries, r.err = mapFromPairs(pairs...)
+		return r.addMatcher(queryMatcher(queries))
+	}
+	return r
+}
+
+// Schemes --------------------------------------------------------------------
+
+// schemeMatcher matches the request against URL schemes.
+type schemeMatcher []string
+
+func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.URL.Scheme)
+}
+
+// Schemes adds a matcher for URL schemes.
+// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
+func (r *Route) Schemes(schemes ...string) *Route {
+	for k, v := range schemes {
+		schemes[k] = strings.ToLower(v)
+	}
+	return r.addMatcher(schemeMatcher(schemes))
+}
+
+// Subrouter ------------------------------------------------------------------
+
+// Subrouter creates a subrouter for the route.
+//
+// It will test the inner routes only if the parent route matched. For example:
+//
+//     r := mux.NewRouter()
+//     s := r.Host("www.domain.com").Subrouter()
+//     s.HandleFunc("/products/", ProductsHandler)
+//     s.HandleFunc("/products/{key}", ProductHandler)
+//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+//
+// Here, the routes registered in the subrouter won't be tested if the host
+// doesn't match.
+func (r *Route) Subrouter() *Router {
+	router := &Router{parent: r, strictSlash: r.strictSlash}
+	r.addMatcher(router)
+	return router
+}
+
+// ----------------------------------------------------------------------------
+// URL building
+// ----------------------------------------------------------------------------
+
+// URL builds a URL for the route.
+//
+// It accepts a sequence of key/value pairs for the route variables. For
+// example, given this route:
+//
+//     r := mux.NewRouter()
+//     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+// ...a URL for it can be built using:
+//
+//     url, err := r.Get("article").URL("category", "technology", "id", "42")
+//
+// ...which will return an url.URL with the following path:
+//
+//     "/articles/technology/42"
+//
+// This also works for host variables:
+//
+//     r := mux.NewRouter()
+//     r.Host("{subdomain}.domain.com").
+//       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+//     // url.String() will be "http://news.domain.com/articles/technology/42"
+//     url, err := r.Get("article").URL("subdomain", "news",
+//                                      "category", "technology",
+//                                      "id", "42")
+//
+// All variables defined in the route are required, and their values must
+// conform to the corresponding patterns.
+func (r *Route) URL(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil {
+		return nil, errors.New("mux: route doesn't have a host or path")
+	}
+	var scheme, host, path string
+	var err error
+	if r.regexp.host != nil {
+		// Set a default scheme.
+		scheme = "http"
+		if host, err = r.regexp.host.url(pairs...); err != nil {
+			return nil, err
+		}
+	}
+	if r.regexp.path != nil {
+		if path, err = r.regexp.path.url(pairs...); err != nil {
+			return nil, err
+		}
+	}
+	return &url.URL{
+		Scheme: scheme,
+		Host:   host,
+		Path:   path,
+	}, nil
+}
+
+// URLHost builds the host part of the URL for a route. See Route.URL().
+//
+// The route must have a host defined.
+func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.host == nil {
+		return nil, errors.New("mux: route doesn't have a host")
+	}
+	host, err := r.regexp.host.url(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Scheme: "http",
+		Host:   host,
+	}, nil
+}
+
+// URLPath builds the path part of the URL for a route. See Route.URL().
+//
+// The route must have a path defined.
+func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.path == nil {
+		return nil, errors.New("mux: route doesn't have a path")
+	}
+	path, err := r.regexp.path.url(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Path: path,
+	}, nil
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// parentRoute allows routes to know about parent host and path definitions.
+type parentRoute interface {
+	getNamedRoutes() map[string]*Route
+	getRegexpGroup() *routeRegexpGroup
+}
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Route) getNamedRoutes() map[string]*Route {
+	if r.parent == nil {
+		// During tests router is not always set.
+		r.parent = NewRouter()
+	}
+	return r.parent.getNamedRoutes()
+}
+
+// getRegexpGroup returns regexp definitions from this route.
+func (r *Route) getRegexpGroup() *routeRegexpGroup {
+	if r.regexp == nil {
+		if r.parent == nil {
+			// During tests router is not always set.
+			r.parent = NewRouter()
+		}
+		regexp := r.parent.getRegexpGroup()
+		if regexp == nil {
+			r.regexp = new(routeRegexpGroup)
+		} else {
+			// Copy.
+			r.regexp = &routeRegexpGroup{
+				host: regexp.host,
+				path: regexp.path,
+			}
+		}
+	}
+	return r.regexp
+}