[
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: go\ngo:\n  - 1.7.x\n  - 1.8.x\n  - 1.9.x\n  - 1.10.x\n  - 1.11.x\n  - 1.12.x\n  - 1.13.x\n  - master\nmatrix:\n  allow_failures:\n    - go: master\n  fast_finish: true\nbefore_install:\n  - go get github.com/mattn/goveralls\nscript:\n  - go test -v -covermode=count -coverprofile=coverage.out\n  - go vet ./...\n  - test -z \"$(gofmt -d -s . | tee /dev/stderr)\"\n  - $HOME/gopath/bin/goveralls  -coverprofile=coverage.out -service=travis-ci\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2013, Julien Schmidt\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# HttpRouter [![Coverage Status](https://coveralls.io/repos/github/julienschmidt/httprouter/badge.svg?branch=master)](https://coveralls.io/github/julienschmidt/httprouter?branch=master) [![Docs](https://godoc.org/github.com/julienschmidt/httprouter?status.svg)](http://pkg.go.dev/github.com/julienschmidt/httprouter)\n\nHttpRouter is a lightweight high performance HTTP request router (also called *multiplexer* or just *mux* for short) for [Go](https://golang.org/).\n\nIn contrast to the [default mux](https://golang.org/pkg/net/http/#ServeMux) of Go's `net/http` package, this router supports variables in the routing pattern and matches against the request method. It also scales better.\n\nThe router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.\n\n## Features\n\n**Only explicit matches:** With other routers, like [`http.ServeMux`](https://golang.org/pkg/net/http/#ServeMux), a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like *longest match* or *first registered, first matched*. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO and improves the user experience.\n\n**Stop caring about trailing slashes:** Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can [turn off this behavior](https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash).\n\n**Path auto-correction:** Besides detecting the missing or additional trailing slash at no extra cost, the router can also fix wrong cases and remove superfluous path elements (like `../` or `//`). Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? HttpRouter can help him by making a case-insensitive look-up and redirecting him to the correct URL.\n\n**Parameters in your routing pattern:** Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap.\n\n**Zero Garbage:** The matching and dispatching process generates zero bytes of garbage. The only heap allocations that are made are building the slice of the key-value pairs for path parameters, and building new context and request objects (the latter only in the standard `Handler`/`HandlerFunc` API). In the 3-argument API, if the request path contains no parameters not a single heap allocation is necessary.\n\n**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). See below for technical details of the implementation.\n\n**No more server crashes:** You can set a [Panic handler](https://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics occurring during handling a HTTP request. The router then recovers and lets the `PanicHandler` log what happened and deliver a nice error page.\n\n**Perfect for APIs:** The router design encourages to build sensible, hierarchical RESTful APIs. Moreover it has built-in native support for [OPTIONS requests](http://zacstewart.com/2012/04/14/http-options-method.html) and `405 Method Not Allowed` replies.\n\nOf course you can also set **custom [`NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and  [`MethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](https://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles).\n\n## Usage\n\nThis is just a quick introduction, view the [Docs](http://pkg.go.dev/github.com/julienschmidt/httprouter) for details.\n\n    $ go get github.com/julienschmidt/httprouter\n\nand use it, like in this trivial example:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"net/http\"\n    \"log\"\n\n    \"github.com/julienschmidt/httprouter\"\n)\n\nfunc Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n    fmt.Fprint(w, \"Welcome!\\n\")\n}\n\nfunc Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n    fmt.Fprintf(w, \"hello, %s!\\n\", ps.ByName(\"name\"))\n}\n\nfunc main() {\n    router := httprouter.New()\n    router.GET(\"/\", Index)\n    router.GET(\"/hello/:name\", Hello)\n\n    log.Fatal(http.ListenAndServe(\":8080\", router))\n}\n```\n\n### Named parameters\n\nAs you can see, `:name` is a *named parameter*. The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s. You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method: `:name` can be retrieved by `ByName(\"name\")`.\n\nWhen using a `http.Handler` (using `router.Handler` or `http.HandlerFunc`) instead of HttpRouter's handle API using a 3rd function parameter, the named parameters are stored in the `request.Context`. See more below under [Why doesn't this work with http.Handler?](#why-doesnt-this-work-with-httphandler).\n\nNamed parameters only match a single path segment:\n\n```\nPattern: /user/:user\n\n /user/gordon              match\n /user/you                 match\n /user/gordon/profile      no match\n /user/                    no match\n```\n\n**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other.\n\n### Catch-All parameters\n\nThe second type are *catch-all* parameters and have the form `*name`. Like the name suggests, they match everything. Therefore they must always be at the **end** of the pattern:\n\n```\nPattern: /src/*filepath\n\n /src/                     match\n /src/somefile.go          match\n /src/subdir/somefile.go   match\n```\n\n## How does it work?\n\nThe router relies on a tree structure which makes heavy use of *common prefixes*, it is basically a *compact* [*prefix tree*](https://en.wikipedia.org/wiki/Trie) (or just [*Radix tree*](https://en.wikipedia.org/wiki/Radix_tree)). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree for the `GET` request method could look like:\n\n```\nPriority   Path             Handle\n9          \\                *<1>\n3          ├s               nil\n2          |├earch\\         *<2>\n1          |└upport\\        *<3>\n2          ├blog\\           *<4>\n1          |    └:post      nil\n1          |         └\\     *<5>\n2          ├about-us\\       *<6>\n1          |        └team\\  *<7>\n1          └contact\\        *<8>\n```\n\nEvery `*<num>` represents the memory address of a handler function (a pointer). If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g `\\blog\\:post\\`, where `:post` is just a placeholder ([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a tree structure also allows us to use dynamic parts like the `:post` parameter, since we actually match against the routing patterns instead of just comparing hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), this works very well and efficient.\n\nSince URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, it also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree.\n\nFor even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..). This helps in two ways:\n\n1. Nodes which are part of the most routing paths are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.\n2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The following scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.\n\n```\n├------------\n├---------\n├-----\n├----\n├--\n├--\n└-\n```\n\n## Why doesn't this work with `http.Handler`?\n\n**It does!** The router itself implements the `http.Handler` interface. Moreover the router provides convenient [adapters for `http.Handler`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [`http.HandlerFunc`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s which allows them to be used as a [`httprouter.Handle`](https://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route.\n\nNamed parameters can be accessed `request.Context`:\n\n```go\nfunc Hello(w http.ResponseWriter, r *http.Request) {\n    params := httprouter.ParamsFromContext(r.Context())\n\n    fmt.Fprintf(w, \"hello, %s!\\n\", params.ByName(\"name\"))\n}\n```\n\nAlternatively, one can also use `params := r.Context().Value(httprouter.ParamsKey)` instead of the helper function.\n\nJust try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.\n\n## Automatic OPTIONS responses and CORS\n\nOne might wish to modify automatic responses to OPTIONS requests, e.g. to support [CORS preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/preflight_request) or to set other headers.\nThis can be achieved using the [`Router.GlobalOPTIONS`](https://godoc.org/github.com/julienschmidt/httprouter#Router.GlobalOPTIONS) handler:\n\n```go\nrouter.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    if r.Header.Get(\"Access-Control-Request-Method\") != \"\" {\n        // Set CORS headers\n        header := w.Header()\n        header.Set(\"Access-Control-Allow-Methods\", header.Get(\"Allow\"))\n        header.Set(\"Access-Control-Allow-Origin\", \"*\")\n    }\n\n    // Adjust status code to 204\n    w.WriteHeader(http.StatusNoContent)\n})\n```\n\n## Where can I find Middleware *X*?\n\nThis package just provides a very efficient request router with a few extra features. The router is just a [`http.Handler`](https://golang.org/pkg/net/http/#Handler), you can chain any http.Handler compatible middleware before the router, for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers). Or you could [just write your own](https://justinas.org/writing-http-middleware-in-go/), it's very easy!\n\nAlternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter).\n\n### Multi-domain / Sub-domains\n\nHere is a quick example: Does your server serve multiple domains / hosts?\nYou want to use sub-domains?\nDefine a router per host!\n\n```go\n// We need an object that implements the http.Handler interface.\n// Therefore we need a type for which we implement the ServeHTTP method.\n// We just use a map here, in which we map host names (with port) to http.Handlers\ntype HostSwitch map[string]http.Handler\n\n// Implement the ServeHTTP method on our new type\nfunc (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t// Check if a http.Handler is registered for the given host.\n\t// If yes, use it to handle the request.\n\tif handler := hs[r.Host]; handler != nil {\n\t\thandler.ServeHTTP(w, r)\n\t} else {\n\t\t// Handle host names for which no handler is registered\n\t\thttp.Error(w, \"Forbidden\", 403) // Or Redirect?\n\t}\n}\n\nfunc main() {\n\t// Initialize a router as usual\n\trouter := httprouter.New()\n\trouter.GET(\"/\", Index)\n\trouter.GET(\"/hello/:name\", Hello)\n\n\t// Make a new HostSwitch and insert the router (our http handler)\n\t// for example.com and port 12345\n\ths := make(HostSwitch)\n\ths[\"example.com:12345\"] = router\n\n\t// Use the HostSwitch to listen and serve on port 12345\n\tlog.Fatal(http.ListenAndServe(\":12345\", hs))\n}\n```\n\n### Basic Authentication\n\nAnother quick example: Basic Authentication (RFC 2617) for handles:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/julienschmidt/httprouter\"\n)\n\nfunc BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {\n\treturn func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n\t\t// Get the Basic Authentication credentials\n\t\tuser, password, hasAuth := r.BasicAuth()\n\n\t\tif hasAuth && user == requiredUser && password == requiredPassword {\n\t\t\t// Delegate request to the given handle\n\t\t\th(w, r, ps)\n\t\t} else {\n\t\t\t// Request Basic Authentication otherwise\n\t\t\tw.Header().Set(\"WWW-Authenticate\", \"Basic realm=Restricted\")\n\t\t\thttp.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)\n\t\t}\n\t}\n}\n\nfunc Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\tfmt.Fprint(w, \"Not protected!\\n\")\n}\n\nfunc Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\tfmt.Fprint(w, \"Protected!\\n\")\n}\n\nfunc main() {\n\tuser := \"gordon\"\n\tpass := \"secret!\"\n\n\trouter := httprouter.New()\n\trouter.GET(\"/\", Index)\n\trouter.GET(\"/protected/\", BasicAuth(Protected, user, pass))\n\n\tlog.Fatal(http.ListenAndServe(\":8080\", router))\n}\n```\n\n## Chaining with the NotFound handler\n\n**NOTE: It might be required to set [`Router.HandleMethodNotAllowed`](https://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.**\n\nYou can use another [`http.Handler`](https://golang.org/pkg/net/http/#Handler), for example another router, to handle requests which could not be matched by this router by using the [`Router.NotFound`](https://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining.\n\n### Static files\n\nThe `NotFound` handler can for example be used to serve static files from the root path `/` (like an `index.html` file along with other assets):\n\n```go\n// Serve static files from the ./public directory\nrouter.NotFound = http.FileServer(http.Dir(\"public\"))\n```\n\nBut this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.\n\n## Web Frameworks based on HttpRouter\n\nIf the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:\n\n* [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework\n* [api2go](https://github.com/manyminds/api2go): A JSON API Implementation for Go\n* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance\n* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go\n* [goMiddlewareChain](https://github.com/TobiEiss/goMiddlewareChain): An express.js-like-middleware-chain\n* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine\n* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow\n* [httpway](https://github.com/corneldamian/httpway): Simple middleware extension with context for httprouter and a server with gracefully shutdown support\n* [intake](https://github.com/dbubel/intake): intake is a minimal http framework with enphasis on middleware groups\n* [Jett](https://github.com/saurabh0719/jett): A lightweight framework with subrouters, graceful shutdown and middleware at all levels.\n* [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context\n* [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba\n* [nchi](https://github.com/muir/nchi): provides a [chi](https://github.com/go-chi/chi)-like framework using [nject](https://github.com/muir/nject) for flexibility and ease-of-use\n* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang\n* [pbgo](https://github.com/chai2010/pbgo): pbgo is a mini RPC/REST framework based on Protobuf\n* [River](https://github.com/abiosoft/river): River is a simple and lightweight REST server\n* [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts\n* [xmux](https://github.com/rs/xmux): xmux is a httprouter fork on top of xhandler (net/context aware)\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/julienschmidt/httprouter\n\ngo 1.7\n"
  },
  {
    "path": "path.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Based on the path package, Copyright 2009 The Go Authors.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\npackage httprouter\n\n// CleanPath is the URL version of path.Clean, it returns a canonical URL path\n// for p, eliminating . and .. elements.\n//\n// The following rules are applied iteratively until no further processing can\n// be done:\n//\t1. Replace multiple slashes with a single slash.\n//\t2. Eliminate each . path name element (the current directory).\n//\t3. Eliminate each inner .. path name element (the parent directory)\n//\t   along with the non-.. element that precedes it.\n//\t4. Eliminate .. elements that begin a rooted path:\n//\t   that is, replace \"/..\" by \"/\" at the beginning of a path.\n//\n// If the result of this process is an empty string, \"/\" is returned\nfunc CleanPath(p string) string {\n\tconst stackBufSize = 128\n\n\t// Turn empty string into \"/\"\n\tif p == \"\" {\n\t\treturn \"/\"\n\t}\n\n\t// Reasonably sized buffer on stack to avoid allocations in the common case.\n\t// If a larger buffer is required, it gets allocated dynamically.\n\tbuf := make([]byte, 0, stackBufSize)\n\n\tn := len(p)\n\n\t// Invariants:\n\t//      reading from path; r is index of next byte to process.\n\t//      writing to buf; w is index of next byte to write.\n\n\t// path must start with '/'\n\tr := 1\n\tw := 1\n\n\tif p[0] != '/' {\n\t\tr = 0\n\n\t\tif n+1 > stackBufSize {\n\t\t\tbuf = make([]byte, n+1)\n\t\t} else {\n\t\t\tbuf = buf[:n+1]\n\t\t}\n\t\tbuf[0] = '/'\n\t}\n\n\ttrailing := n > 1 && p[n-1] == '/'\n\n\t// A bit more clunky without a 'lazybuf' like the path package, but the loop\n\t// gets completely inlined (bufApp calls).\n\t// So in contrast to the path package this loop has no expensive function\n\t// calls (except make, if needed).\n\n\tfor r < n {\n\t\tswitch {\n\t\tcase p[r] == '/':\n\t\t\t// empty path element, trailing slash is added after the end\n\t\t\tr++\n\n\t\tcase p[r] == '.' && r+1 == n:\n\t\t\ttrailing = true\n\t\t\tr++\n\n\t\tcase p[r] == '.' && p[r+1] == '/':\n\t\t\t// . element\n\t\t\tr += 2\n\n\t\tcase p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):\n\t\t\t// .. element: remove to last /\n\t\t\tr += 3\n\n\t\t\tif w > 1 {\n\t\t\t\t// can backtrack\n\t\t\t\tw--\n\n\t\t\t\tif len(buf) == 0 {\n\t\t\t\t\tfor w > 1 && p[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor w > 1 && buf[w] != '/' {\n\t\t\t\t\t\tw--\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tdefault:\n\t\t\t// Real path element.\n\t\t\t// Add slash if needed\n\t\t\tif w > 1 {\n\t\t\t\tbufApp(&buf, p, w, '/')\n\t\t\t\tw++\n\t\t\t}\n\n\t\t\t// Copy element\n\t\t\tfor r < n && p[r] != '/' {\n\t\t\t\tbufApp(&buf, p, w, p[r])\n\t\t\t\tw++\n\t\t\t\tr++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Re-append trailing slash\n\tif trailing && w > 1 {\n\t\tbufApp(&buf, p, w, '/')\n\t\tw++\n\t}\n\n\t// If the original string was not modified (or only shortened at the end),\n\t// return the respective substring of the original string.\n\t// Otherwise return a new string from the buffer.\n\tif len(buf) == 0 {\n\t\treturn p[:w]\n\t}\n\treturn string(buf[:w])\n}\n\n// Internal helper to lazily create a buffer if necessary.\n// Calls to this function get inlined.\nfunc bufApp(buf *[]byte, s string, w int, c byte) {\n\tb := *buf\n\tif len(b) == 0 {\n\t\t// No modification of the original string so far.\n\t\t// If the next character is the same as in the original string, we do\n\t\t// not yet have to allocate a buffer.\n\t\tif s[w] == c {\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise use either the stack buffer, if it is large enough, or\n\t\t// allocate a new buffer on the heap, and copy all previous characters.\n\t\tif l := len(s); l > cap(b) {\n\t\t\t*buf = make([]byte, len(s))\n\t\t} else {\n\t\t\t*buf = (*buf)[:l]\n\t\t}\n\t\tb = *buf\n\n\t\tcopy(b, s[:w])\n\t}\n\tb[w] = c\n}\n"
  },
  {
    "path": "path_test.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Based on the path package, Copyright 2009 The Go Authors.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\npackage httprouter\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\ntype cleanPathTest struct {\n\tpath, result string\n}\n\nvar cleanTests = []cleanPathTest{\n\t// Already clean\n\t{\"/\", \"/\"},\n\t{\"/abc\", \"/abc\"},\n\t{\"/a/b/c\", \"/a/b/c\"},\n\t{\"/abc/\", \"/abc/\"},\n\t{\"/a/b/c/\", \"/a/b/c/\"},\n\n\t// missing root\n\t{\"\", \"/\"},\n\t{\"a/\", \"/a/\"},\n\t{\"abc\", \"/abc\"},\n\t{\"abc/def\", \"/abc/def\"},\n\t{\"a/b/c\", \"/a/b/c\"},\n\n\t// Remove doubled slash\n\t{\"//\", \"/\"},\n\t{\"/abc//\", \"/abc/\"},\n\t{\"/abc/def//\", \"/abc/def/\"},\n\t{\"/a/b/c//\", \"/a/b/c/\"},\n\t{\"/abc//def//ghi\", \"/abc/def/ghi\"},\n\t{\"//abc\", \"/abc\"},\n\t{\"///abc\", \"/abc\"},\n\t{\"//abc//\", \"/abc/\"},\n\n\t// Remove . elements\n\t{\".\", \"/\"},\n\t{\"./\", \"/\"},\n\t{\"/abc/./def\", \"/abc/def\"},\n\t{\"/./abc/def\", \"/abc/def\"},\n\t{\"/abc/.\", \"/abc/\"},\n\n\t// Remove .. elements\n\t{\"..\", \"/\"},\n\t{\"../\", \"/\"},\n\t{\"../../\", \"/\"},\n\t{\"../..\", \"/\"},\n\t{\"../../abc\", \"/abc\"},\n\t{\"/abc/def/ghi/../jkl\", \"/abc/def/jkl\"},\n\t{\"/abc/def/../ghi/../jkl\", \"/abc/jkl\"},\n\t{\"/abc/def/..\", \"/abc\"},\n\t{\"/abc/def/../..\", \"/\"},\n\t{\"/abc/def/../../..\", \"/\"},\n\t{\"/abc/def/../../..\", \"/\"},\n\t{\"/abc/def/../../../ghi/jkl/../../../mno\", \"/mno\"},\n\n\t// Combinations\n\t{\"abc/./../def\", \"/def\"},\n\t{\"abc//./../def\", \"/def\"},\n\t{\"abc/../../././../def\", \"/def\"},\n}\n\nfunc TestPathClean(t *testing.T) {\n\tfor _, test := range cleanTests {\n\t\tif s := CleanPath(test.path); s != test.result {\n\t\t\tt.Errorf(\"CleanPath(%q) = %q, want %q\", test.path, s, test.result)\n\t\t}\n\t\tif s := CleanPath(test.result); s != test.result {\n\t\t\tt.Errorf(\"CleanPath(%q) = %q, want %q\", test.result, s, test.result)\n\t\t}\n\t}\n}\n\nfunc TestPathCleanMallocs(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping malloc count in short mode\")\n\t}\n\n\tfor _, test := range cleanTests {\n\t\ttest := test\n\t\tallocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })\n\t\tif allocs > 0 {\n\t\t\tt.Errorf(\"CleanPath(%q): %v allocs, want zero\", test.result, allocs)\n\t\t}\n\t}\n}\n\nfunc BenchmarkPathClean(b *testing.B) {\n\tb.ReportAllocs()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, test := range cleanTests {\n\t\t\tCleanPath(test.path)\n\t\t}\n\t}\n}\n\nfunc genLongPaths() (testPaths []cleanPathTest) {\n\tfor i := 1; i <= 1234; i++ {\n\t\tss := strings.Repeat(\"a\", i)\n\n\t\tcorrectPath := \"/\" + ss\n\t\ttestPaths = append(testPaths, cleanPathTest{\n\t\t\tpath:   correctPath,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   ss,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   \"//\" + ss,\n\t\t\tresult: correctPath,\n\t\t}, cleanPathTest{\n\t\t\tpath:   \"/\" + ss + \"/b/..\",\n\t\t\tresult: correctPath,\n\t\t})\n\t}\n\treturn testPaths\n}\n\nfunc TestPathCleanLong(t *testing.T) {\n\tcleanTests := genLongPaths()\n\n\tfor _, test := range cleanTests {\n\t\tif s := CleanPath(test.path); s != test.result {\n\t\t\tt.Errorf(\"CleanPath(%q) = %q, want %q\", test.path, s, test.result)\n\t\t}\n\t\tif s := CleanPath(test.result); s != test.result {\n\t\t\tt.Errorf(\"CleanPath(%q) = %q, want %q\", test.result, s, test.result)\n\t\t}\n\t}\n}\n\nfunc BenchmarkPathCleanLong(b *testing.B) {\n\tcleanTests := genLongPaths()\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tfor _, test := range cleanTests {\n\t\t\tCleanPath(test.path)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "router.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\n// Package httprouter is a trie based high performance HTTP request router.\n//\n// A trivial example is:\n//\n//  package main\n//\n//  import (\n//      \"fmt\"\n//      \"github.com/julienschmidt/httprouter\"\n//      \"net/http\"\n//      \"log\"\n//  )\n//\n//  func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n//      fmt.Fprint(w, \"Welcome!\\n\")\n//  }\n//\n//  func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n//      fmt.Fprintf(w, \"hello, %s!\\n\", ps.ByName(\"name\"))\n//  }\n//\n//  func main() {\n//      router := httprouter.New()\n//      router.GET(\"/\", Index)\n//      router.GET(\"/hello/:name\", Hello)\n//\n//      log.Fatal(http.ListenAndServe(\":8080\", router))\n//  }\n//\n// The router matches incoming requests by the request method and the path.\n// If a handle is registered for this path and method, the router delegates the\n// request to that function.\n// For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to\n// register handles, for all other methods router.Handle can be used.\n//\n// The registered path, against which the router matches incoming requests, can\n// contain two types of parameters:\n//  Syntax    Type\n//  :name     named parameter\n//  *name     catch-all parameter\n//\n// Named parameters are dynamic path segments. They match anything until the\n// next '/' or the path end:\n//  Path: /blog/:category/:post\n//\n//  Requests:\n//   /blog/go/request-routers            match: category=\"go\", post=\"request-routers\"\n//   /blog/go/request-routers/           no match, but the router would redirect\n//   /blog/go/                           no match\n//   /blog/go/request-routers/comments   no match\n//\n// Catch-all parameters match anything until the path end, including the\n// directory index (the '/' before the catch-all). Since they match anything\n// until the end, catch-all parameters must always be the final path element.\n//  Path: /files/*filepath\n//\n//  Requests:\n//   /files/                             match: filepath=\"/\"\n//   /files/LICENSE                      match: filepath=\"/LICENSE\"\n//   /files/templates/article.html       match: filepath=\"/templates/article.html\"\n//   /files                              no match, but the router would redirect\n//\n// The value of parameters is saved as a slice of the Param struct, consisting\n// each of a key and a value. The slice is passed to the Handle func as a third\n// parameter.\n// There are two ways to retrieve the value of a parameter:\n//  // by the name of the parameter\n//  user := ps.ByName(\"user\") // defined by :user or *user\n//\n//  // by the index of the parameter. This way you can also get the name (key)\n//  thirdKey   := ps[2].Key   // the name of the 3rd parameter\n//  thirdValue := ps[2].Value // the value of the 3rd parameter\npackage httprouter\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// Handle is a function that can be registered to a route to handle HTTP\n// requests. Like http.HandlerFunc, but has a third parameter for the values of\n// wildcards (path variables).\ntype Handle func(http.ResponseWriter, *http.Request, Params)\n\n// Param is a single URL parameter, consisting of a key and a value.\ntype Param struct {\n\tKey   string\n\tValue string\n}\n\n// Params is a Param-slice, as returned by the router.\n// The slice is ordered, the first URL parameter is also the first slice value.\n// It is therefore safe to read values by the index.\ntype Params []Param\n\n// ByName returns the value of the first Param which key matches the given name.\n// If no matching Param is found, an empty string is returned.\nfunc (ps Params) ByName(name string) string {\n\tfor _, p := range ps {\n\t\tif p.Key == name {\n\t\t\treturn p.Value\n\t\t}\n\t}\n\treturn \"\"\n}\n\ntype paramsKey struct{}\n\n// ParamsKey is the request context key under which URL params are stored.\nvar ParamsKey = paramsKey{}\n\n// ParamsFromContext pulls the URL parameters from a request context,\n// or returns nil if none are present.\nfunc ParamsFromContext(ctx context.Context) Params {\n\tp, _ := ctx.Value(ParamsKey).(Params)\n\treturn p\n}\n\n// MatchedRoutePathParam is the Param name under which the path of the matched\n// route is stored, if Router.SaveMatchedRoutePath is set.\nvar MatchedRoutePathParam = \"$matchedRoutePath\"\n\n// MatchedRoutePath retrieves the path of the matched route.\n// Router.SaveMatchedRoutePath must have been enabled when the respective\n// handler was added, otherwise this function always returns an empty string.\nfunc (ps Params) MatchedRoutePath() string {\n\treturn ps.ByName(MatchedRoutePathParam)\n}\n\n// Router is a http.Handler which can be used to dispatch requests to different\n// handler functions via configurable routes\ntype Router struct {\n\ttrees map[string]*node\n\n\tparamsPool sync.Pool\n\tmaxParams  uint16\n\n\t// If enabled, adds the matched route path onto the http.Request context\n\t// before invoking the handler.\n\t// The matched route path is only added to handlers of routes that were\n\t// registered when this option was enabled.\n\tSaveMatchedRoutePath bool\n\n\t// Enables automatic redirection if the current route can't be matched but a\n\t// handler for the path with (without) the trailing slash exists.\n\t// For example if /foo/ is requested but a route only exists for /foo, the\n\t// client is redirected to /foo with http status code 301 for GET requests\n\t// and 308 for all other request methods.\n\tRedirectTrailingSlash bool\n\n\t// If enabled, the router tries to fix the current request path, if no\n\t// handle is registered for it.\n\t// First superfluous path elements like ../ or // are removed.\n\t// Afterwards the router does a case-insensitive lookup of the cleaned path.\n\t// If a handle can be found for this route, the router makes a redirection\n\t// to the corrected path with status code 301 for GET requests and 308 for\n\t// all other request methods.\n\t// For example /FOO and /..//Foo could be redirected to /foo.\n\t// RedirectTrailingSlash is independent of this option.\n\tRedirectFixedPath bool\n\n\t// If enabled, the router checks if another method is allowed for the\n\t// current route, if the current request can not be routed.\n\t// If this is the case, the request is answered with 'Method Not Allowed'\n\t// and HTTP status code 405.\n\t// If no other Method is allowed, the request is delegated to the NotFound\n\t// handler.\n\tHandleMethodNotAllowed bool\n\n\t// If enabled, the router automatically replies to OPTIONS requests.\n\t// Custom OPTIONS handlers take priority over automatic replies.\n\tHandleOPTIONS bool\n\n\t// An optional http.Handler that is called on automatic OPTIONS requests.\n\t// The handler is only called if HandleOPTIONS is true and no OPTIONS\n\t// handler for the specific path was set.\n\t// The \"Allowed\" header is set before calling the handler.\n\tGlobalOPTIONS http.Handler\n\n\t// Cached value of global (*) allowed methods\n\tglobalAllowed string\n\n\t// Configurable http.Handler which is called when no matching route is\n\t// found. If it is not set, http.NotFound is used.\n\tNotFound http.Handler\n\n\t// Configurable http.Handler which is called when a request\n\t// cannot be routed and HandleMethodNotAllowed is true.\n\t// If it is not set, http.Error with http.StatusMethodNotAllowed is used.\n\t// The \"Allow\" header with allowed request methods is set before the handler\n\t// is called.\n\tMethodNotAllowed http.Handler\n\n\t// Function to handle panics recovered from http handlers.\n\t// It should be used to generate a error page and return the http error code\n\t// 500 (Internal Server Error).\n\t// The handler can be used to keep your server from crashing because of\n\t// unrecovered panics.\n\tPanicHandler func(http.ResponseWriter, *http.Request, interface{})\n}\n\n// Make sure the Router conforms with the http.Handler interface\nvar _ http.Handler = New()\n\n// New returns a new initialized Router.\n// Path auto-correction, including trailing slashes, is enabled by default.\nfunc New() *Router {\n\treturn &Router{\n\t\tRedirectTrailingSlash:  true,\n\t\tRedirectFixedPath:      true,\n\t\tHandleMethodNotAllowed: true,\n\t\tHandleOPTIONS:          true,\n\t}\n}\n\nfunc (r *Router) getParams() *Params {\n\tps, _ := r.paramsPool.Get().(*Params)\n\t*ps = (*ps)[0:0] // reset slice\n\treturn ps\n}\n\nfunc (r *Router) putParams(ps *Params) {\n\tif ps != nil {\n\t\tr.paramsPool.Put(ps)\n\t}\n}\n\nfunc (r *Router) saveMatchedRoutePath(path string, handle Handle) Handle {\n\treturn func(w http.ResponseWriter, req *http.Request, ps Params) {\n\t\tif ps == nil {\n\t\t\tpsp := r.getParams()\n\t\t\tps = (*psp)[0:1]\n\t\t\tps[0] = Param{Key: MatchedRoutePathParam, Value: path}\n\t\t\thandle(w, req, ps)\n\t\t\tr.putParams(psp)\n\t\t} else {\n\t\t\tps = append(ps, Param{Key: MatchedRoutePathParam, Value: path})\n\t\t\thandle(w, req, ps)\n\t\t}\n\t}\n}\n\n// GET is a shortcut for router.Handle(http.MethodGet, path, handle)\nfunc (r *Router) GET(path string, handle Handle) {\n\tr.Handle(http.MethodGet, path, handle)\n}\n\n// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)\nfunc (r *Router) HEAD(path string, handle Handle) {\n\tr.Handle(http.MethodHead, path, handle)\n}\n\n// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)\nfunc (r *Router) OPTIONS(path string, handle Handle) {\n\tr.Handle(http.MethodOptions, path, handle)\n}\n\n// POST is a shortcut for router.Handle(http.MethodPost, path, handle)\nfunc (r *Router) POST(path string, handle Handle) {\n\tr.Handle(http.MethodPost, path, handle)\n}\n\n// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)\nfunc (r *Router) PUT(path string, handle Handle) {\n\tr.Handle(http.MethodPut, path, handle)\n}\n\n// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)\nfunc (r *Router) PATCH(path string, handle Handle) {\n\tr.Handle(http.MethodPatch, path, handle)\n}\n\n// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)\nfunc (r *Router) DELETE(path string, handle Handle) {\n\tr.Handle(http.MethodDelete, path, handle)\n}\n\n// Handle registers a new request handle with the given path and method.\n//\n// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut\n// functions can be used.\n//\n// This function is intended for bulk loading and to allow the usage of less\n// frequently used, non-standardized or custom methods (e.g. for internal\n// communication with a proxy).\nfunc (r *Router) Handle(method, path string, handle Handle) {\n\tvarsCount := uint16(0)\n\n\tif method == \"\" {\n\t\tpanic(\"method must not be empty\")\n\t}\n\tif len(path) < 1 || path[0] != '/' {\n\t\tpanic(\"path must begin with '/' in path '\" + path + \"'\")\n\t}\n\tif handle == nil {\n\t\tpanic(\"handle must not be nil\")\n\t}\n\n\tif r.SaveMatchedRoutePath {\n\t\tvarsCount++\n\t\thandle = r.saveMatchedRoutePath(path, handle)\n\t}\n\n\tif r.trees == nil {\n\t\tr.trees = make(map[string]*node)\n\t}\n\n\troot := r.trees[method]\n\tif root == nil {\n\t\troot = new(node)\n\t\tr.trees[method] = root\n\n\t\tr.globalAllowed = r.allowed(\"*\", \"\")\n\t}\n\n\troot.addRoute(path, handle)\n\n\t// Update maxParams\n\tif paramsCount := countParams(path); paramsCount+varsCount > r.maxParams {\n\t\tr.maxParams = paramsCount + varsCount\n\t}\n\n\t// Lazy-init paramsPool alloc func\n\tif r.paramsPool.New == nil && r.maxParams > 0 {\n\t\tr.paramsPool.New = func() interface{} {\n\t\t\tps := make(Params, 0, r.maxParams)\n\t\t\treturn &ps\n\t\t}\n\t}\n}\n\n// Handler is an adapter which allows the usage of an http.Handler as a\n// request handle.\n// The Params are available in the request context under ParamsKey.\nfunc (r *Router) Handler(method, path string, handler http.Handler) {\n\tr.Handle(method, path,\n\t\tfunc(w http.ResponseWriter, req *http.Request, p Params) {\n\t\t\tif len(p) > 0 {\n\t\t\t\tctx := req.Context()\n\t\t\t\tctx = context.WithValue(ctx, ParamsKey, p)\n\t\t\t\treq = req.WithContext(ctx)\n\t\t\t}\n\t\t\thandler.ServeHTTP(w, req)\n\t\t},\n\t)\n}\n\n// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a\n// request handle.\nfunc (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {\n\tr.Handler(method, path, handler)\n}\n\n// ServeFiles serves files from the given file system root.\n// The path must end with \"/*filepath\", files are then served from the local\n// path /defined/root/dir/*filepath.\n// For example if root is \"/etc\" and *filepath is \"passwd\", the local file\n// \"/etc/passwd\" would be served.\n// Internally a http.FileServer is used, therefore http.NotFound is used instead\n// of the Router's NotFound handler.\n// To use the operating system's file system implementation,\n// use http.Dir:\n//     router.ServeFiles(\"/src/*filepath\", http.Dir(\"/var/www\"))\nfunc (r *Router) ServeFiles(path string, root http.FileSystem) {\n\tif len(path) < 10 || path[len(path)-10:] != \"/*filepath\" {\n\t\tpanic(\"path must end with /*filepath in path '\" + path + \"'\")\n\t}\n\n\tfileServer := http.FileServer(root)\n\n\tr.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {\n\t\treq.URL.Path = ps.ByName(\"filepath\")\n\t\tfileServer.ServeHTTP(w, req)\n\t})\n}\n\nfunc (r *Router) recv(w http.ResponseWriter, req *http.Request) {\n\tif rcv := recover(); rcv != nil {\n\t\tr.PanicHandler(w, req, rcv)\n\t}\n}\n\n// Lookup allows the manual lookup of a method + path combo.\n// This is e.g. useful to build a framework around this router.\n// If the path was found, it returns the handle function and the path parameter\n// values. Otherwise the third return value indicates whether a redirection to\n// the same path with an extra / without the trailing slash should be performed.\nfunc (r *Router) Lookup(method, path string) (Handle, Params, bool) {\n\tif root := r.trees[method]; root != nil {\n\t\thandle, ps, tsr := root.getValue(path, r.getParams)\n\t\tif handle == nil {\n\t\t\tr.putParams(ps)\n\t\t\treturn nil, nil, tsr\n\t\t}\n\t\tif ps == nil {\n\t\t\treturn handle, nil, tsr\n\t\t}\n\t\treturn handle, *ps, tsr\n\t}\n\treturn nil, nil, false\n}\n\nfunc (r *Router) allowed(path, reqMethod string) (allow string) {\n\tallowed := make([]string, 0, 9)\n\n\tif path == \"*\" { // server-wide\n\t\t// empty method is used for internal calls to refresh the cache\n\t\tif reqMethod == \"\" {\n\t\t\tfor method := range r.trees {\n\t\t\t\tif method == http.MethodOptions {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Add request method to list of allowed methods\n\t\t\t\tallowed = append(allowed, method)\n\t\t\t}\n\t\t} else {\n\t\t\treturn r.globalAllowed\n\t\t}\n\t} else { // specific path\n\t\tfor method := range r.trees {\n\t\t\t// Skip the requested method - we already tried this one\n\t\t\tif method == reqMethod || method == http.MethodOptions {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\thandle, _, _ := r.trees[method].getValue(path, nil)\n\t\t\tif handle != nil {\n\t\t\t\t// Add request method to list of allowed methods\n\t\t\t\tallowed = append(allowed, method)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(allowed) > 0 {\n\t\t// Add request method to list of allowed methods\n\t\tif r.HandleOPTIONS {\n\t\t\tallowed = append(allowed, http.MethodOptions)\n\t\t}\n\n\t\t// Sort allowed methods.\n\t\t// sort.Strings(allowed) unfortunately causes unnecessary allocations\n\t\t// due to allowed being moved to the heap and interface conversion\n\t\tfor i, l := 1, len(allowed); i < l; i++ {\n\t\t\tfor j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {\n\t\t\t\tallowed[j], allowed[j-1] = allowed[j-1], allowed[j]\n\t\t\t}\n\t\t}\n\n\t\t// return as comma separated list\n\t\treturn strings.Join(allowed, \", \")\n\t}\n\n\treturn allow\n}\n\n// ServeHTTP makes the router implement the http.Handler interface.\nfunc (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif r.PanicHandler != nil {\n\t\tdefer r.recv(w, req)\n\t}\n\n\tpath := req.URL.Path\n\n\tif root := r.trees[req.Method]; root != nil {\n\t\tif handle, ps, tsr := root.getValue(path, r.getParams); handle != nil {\n\t\t\tif ps != nil {\n\t\t\t\thandle(w, req, *ps)\n\t\t\t\tr.putParams(ps)\n\t\t\t} else {\n\t\t\t\thandle(w, req, nil)\n\t\t\t}\n\t\t\treturn\n\t\t} else if req.Method != http.MethodConnect && path != \"/\" {\n\t\t\t// Moved Permanently, request with GET method\n\t\t\tcode := http.StatusMovedPermanently\n\t\t\tif req.Method != http.MethodGet {\n\t\t\t\t// Permanent Redirect, request with same method\n\t\t\t\tcode = http.StatusPermanentRedirect\n\t\t\t}\n\n\t\t\tif tsr && r.RedirectTrailingSlash {\n\t\t\t\tif len(path) > 1 && path[len(path)-1] == '/' {\n\t\t\t\t\treq.URL.Path = path[:len(path)-1]\n\t\t\t\t} else {\n\t\t\t\t\treq.URL.Path = path + \"/\"\n\t\t\t\t}\n\t\t\t\thttp.Redirect(w, req, req.URL.String(), code)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Try to fix the request path\n\t\t\tif r.RedirectFixedPath {\n\t\t\t\tfixedPath, found := root.findCaseInsensitivePath(\n\t\t\t\t\tCleanPath(path),\n\t\t\t\t\tr.RedirectTrailingSlash,\n\t\t\t\t)\n\t\t\t\tif found {\n\t\t\t\t\treq.URL.Path = fixedPath\n\t\t\t\t\thttp.Redirect(w, req, req.URL.String(), code)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif req.Method == http.MethodOptions && r.HandleOPTIONS {\n\t\t// Handle OPTIONS requests\n\t\tif allow := r.allowed(path, http.MethodOptions); allow != \"\" {\n\t\t\tw.Header().Set(\"Allow\", allow)\n\t\t\tif r.GlobalOPTIONS != nil {\n\t\t\t\tr.GlobalOPTIONS.ServeHTTP(w, req)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t} else if r.HandleMethodNotAllowed { // Handle 405\n\t\tif allow := r.allowed(path, req.Method); allow != \"\" {\n\t\t\tw.Header().Set(\"Allow\", allow)\n\t\t\tif r.MethodNotAllowed != nil {\n\t\t\t\tr.MethodNotAllowed.ServeHTTP(w, req)\n\t\t\t} else {\n\t\t\t\thttp.Error(w,\n\t\t\t\t\thttp.StatusText(http.StatusMethodNotAllowed),\n\t\t\t\t\thttp.StatusMethodNotAllowed,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Handle 404\n\tif r.NotFound != nil {\n\t\tr.NotFound.ServeHTTP(w, req)\n\t} else {\n\t\thttp.NotFound(w, req)\n\t}\n}\n"
  },
  {
    "path": "router_test.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\npackage httprouter\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype mockResponseWriter struct{}\n\nfunc (m *mockResponseWriter) Header() (h http.Header) {\n\treturn http.Header{}\n}\n\nfunc (m *mockResponseWriter) Write(p []byte) (n int, err error) {\n\treturn len(p), nil\n}\n\nfunc (m *mockResponseWriter) WriteString(s string) (n int, err error) {\n\treturn len(s), nil\n}\n\nfunc (m *mockResponseWriter) WriteHeader(int) {}\n\nfunc TestParams(t *testing.T) {\n\tps := Params{\n\t\tParam{\"param1\", \"value1\"},\n\t\tParam{\"param2\", \"value2\"},\n\t\tParam{\"param3\", \"value3\"},\n\t}\n\tfor i := range ps {\n\t\tif val := ps.ByName(ps[i].Key); val != ps[i].Value {\n\t\t\tt.Errorf(\"Wrong value for %s: Got %s; Want %s\", ps[i].Key, val, ps[i].Value)\n\t\t}\n\t}\n\tif val := ps.ByName(\"noKey\"); val != \"\" {\n\t\tt.Errorf(\"Expected empty string for not found key; got: %s\", val)\n\t}\n}\n\nfunc TestRouter(t *testing.T) {\n\trouter := New()\n\n\trouted := false\n\trouter.Handle(http.MethodGet, \"/user/:name\", func(w http.ResponseWriter, r *http.Request, ps Params) {\n\t\trouted = true\n\t\twant := Params{Param{\"name\", \"gopher\"}}\n\t\tif !reflect.DeepEqual(ps, want) {\n\t\t\tt.Fatalf(\"wrong wildcard values: want %v, got %v\", want, ps)\n\t\t}\n\t})\n\n\tw := new(mockResponseWriter)\n\n\treq, _ := http.NewRequest(http.MethodGet, \"/user/gopher\", nil)\n\trouter.ServeHTTP(w, req)\n\n\tif !routed {\n\t\tt.Fatal(\"routing failed\")\n\t}\n}\n\ntype handlerStruct struct {\n\thandled *bool\n}\n\nfunc (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\t*h.handled = true\n}\n\nfunc TestRouterAPI(t *testing.T) {\n\tvar get, head, options, post, put, patch, delete, handler, handlerFunc bool\n\n\thttpHandler := handlerStruct{&handler}\n\n\trouter := New()\n\trouter.GET(\"/GET\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tget = true\n\t})\n\trouter.HEAD(\"/GET\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\thead = true\n\t})\n\trouter.OPTIONS(\"/GET\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\toptions = true\n\t})\n\trouter.POST(\"/POST\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tpost = true\n\t})\n\trouter.PUT(\"/PUT\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tput = true\n\t})\n\trouter.PATCH(\"/PATCH\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tpatch = true\n\t})\n\trouter.DELETE(\"/DELETE\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tdelete = true\n\t})\n\trouter.Handler(http.MethodGet, \"/Handler\", httpHandler)\n\trouter.HandlerFunc(http.MethodGet, \"/HandlerFunc\", func(w http.ResponseWriter, r *http.Request) {\n\t\thandlerFunc = true\n\t})\n\n\tw := new(mockResponseWriter)\n\n\tr, _ := http.NewRequest(http.MethodGet, \"/GET\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !get {\n\t\tt.Error(\"routing GET failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodHead, \"/GET\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !head {\n\t\tt.Error(\"routing HEAD failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodOptions, \"/GET\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !options {\n\t\tt.Error(\"routing OPTIONS failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodPost, \"/POST\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !post {\n\t\tt.Error(\"routing POST failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodPut, \"/PUT\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !put {\n\t\tt.Error(\"routing PUT failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodPatch, \"/PATCH\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !patch {\n\t\tt.Error(\"routing PATCH failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodDelete, \"/DELETE\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !delete {\n\t\tt.Error(\"routing DELETE failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodGet, \"/Handler\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !handler {\n\t\tt.Error(\"routing Handler failed\")\n\t}\n\n\tr, _ = http.NewRequest(http.MethodGet, \"/HandlerFunc\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !handlerFunc {\n\t\tt.Error(\"routing HandlerFunc failed\")\n\t}\n}\n\nfunc TestRouterInvalidInput(t *testing.T) {\n\trouter := New()\n\n\thandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}\n\n\trecv := catchPanic(func() {\n\t\trouter.Handle(\"\", \"/\", handle)\n\t})\n\tif recv == nil {\n\t\tt.Fatal(\"registering empty method did not panic\")\n\t}\n\n\trecv = catchPanic(func() {\n\t\trouter.GET(\"\", handle)\n\t})\n\tif recv == nil {\n\t\tt.Fatal(\"registering empty path did not panic\")\n\t}\n\n\trecv = catchPanic(func() {\n\t\trouter.GET(\"noSlashRoot\", handle)\n\t})\n\tif recv == nil {\n\t\tt.Fatal(\"registering path not beginning with '/' did not panic\")\n\t}\n\n\trecv = catchPanic(func() {\n\t\trouter.GET(\"/\", nil)\n\t})\n\tif recv == nil {\n\t\tt.Fatal(\"registering nil handler did not panic\")\n\t}\n}\n\nfunc TestRouterChaining(t *testing.T) {\n\trouter1 := New()\n\trouter2 := New()\n\trouter1.NotFound = router2\n\n\tfooHit := false\n\trouter1.POST(\"/foo\", func(w http.ResponseWriter, req *http.Request, _ Params) {\n\t\tfooHit = true\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\n\tbarHit := false\n\trouter2.POST(\"/bar\", func(w http.ResponseWriter, req *http.Request, _ Params) {\n\t\tbarHit = true\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\n\tr, _ := http.NewRequest(http.MethodPost, \"/foo\", nil)\n\tw := httptest.NewRecorder()\n\trouter1.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusOK && fooHit) {\n\t\tt.Errorf(\"Regular routing failed with router chaining.\")\n\t\tt.FailNow()\n\t}\n\n\tr, _ = http.NewRequest(http.MethodPost, \"/bar\", nil)\n\tw = httptest.NewRecorder()\n\trouter1.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusOK && barHit) {\n\t\tt.Errorf(\"Chained routing failed with router chaining.\")\n\t\tt.FailNow()\n\t}\n\n\tr, _ = http.NewRequest(http.MethodPost, \"/qax\", nil)\n\tw = httptest.NewRecorder()\n\trouter1.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNotFound) {\n\t\tt.Errorf(\"NotFound behavior failed with router chaining.\")\n\t\tt.FailNow()\n\t}\n}\n\nfunc BenchmarkAllowed(b *testing.B) {\n\thandlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}\n\n\trouter := New()\n\trouter.POST(\"/path\", handlerFunc)\n\trouter.GET(\"/path\", handlerFunc)\n\n\tb.Run(\"Global\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\t_ = router.allowed(\"*\", http.MethodOptions)\n\t\t}\n\t})\n\tb.Run(\"Path\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\t_ = router.allowed(\"/path\", http.MethodOptions)\n\t\t}\n\t})\n}\n\nfunc TestRouterOPTIONS(t *testing.T) {\n\thandlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}\n\n\trouter := New()\n\trouter.POST(\"/path\", handlerFunc)\n\n\t// test not allowed\n\t// * (server)\n\tr, _ := http.NewRequest(http.MethodOptions, \"*\", nil)\n\tw := httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusOK) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\t// path\n\tr, _ = http.NewRequest(http.MethodOptions, \"/path\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusOK) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\tr, _ = http.NewRequest(http.MethodOptions, \"/doesnotexist\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNotFound) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t}\n\n\t// add another method\n\trouter.GET(\"/path\", handlerFunc)\n\n\t// set a global OPTIONS handler\n\trouter.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// Adjust status code to 204\n\t\tw.WriteHeader(http.StatusNoContent)\n\t})\n\n\t// test again\n\t// * (server)\n\tr, _ = http.NewRequest(http.MethodOptions, \"*\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNoContent) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"GET, OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\t// path\n\tr, _ = http.NewRequest(http.MethodOptions, \"/path\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNoContent) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"GET, OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\t// custom handler\n\tvar custom bool\n\trouter.OPTIONS(\"/path\", func(w http.ResponseWriter, r *http.Request, _ Params) {\n\t\tcustom = true\n\t})\n\n\t// test again\n\t// * (server)\n\tr, _ = http.NewRequest(http.MethodOptions, \"*\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNoContent) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"GET, OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\tif custom {\n\t\tt.Error(\"custom handler called on *\")\n\t}\n\n\t// path\n\tr, _ = http.NewRequest(http.MethodOptions, \"/path\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusOK) {\n\t\tt.Errorf(\"OPTIONS handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t}\n\tif !custom {\n\t\tt.Error(\"custom handler not called\")\n\t}\n}\n\nfunc TestRouterNotAllowed(t *testing.T) {\n\thandlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}\n\n\trouter := New()\n\trouter.POST(\"/path\", handlerFunc)\n\n\t// test not allowed\n\tr, _ := http.NewRequest(http.MethodGet, \"/path\", nil)\n\tw := httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusMethodNotAllowed) {\n\t\tt.Errorf(\"NotAllowed handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\t// add another method\n\trouter.DELETE(\"/path\", handlerFunc)\n\trouter.OPTIONS(\"/path\", handlerFunc) // must be ignored\n\n\t// test again\n\tr, _ = http.NewRequest(http.MethodGet, \"/path\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusMethodNotAllowed) {\n\t\tt.Errorf(\"NotAllowed handling failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t} else if allow := w.Header().Get(\"Allow\"); allow != \"DELETE, OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n\n\t// test custom handler\n\tw = httptest.NewRecorder()\n\tresponseText := \"custom method\"\n\trouter.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tw.WriteHeader(http.StatusTeapot)\n\t\tw.Write([]byte(responseText))\n\t})\n\trouter.ServeHTTP(w, r)\n\tif got := w.Body.String(); !(got == responseText) {\n\t\tt.Errorf(\"unexpected response got %q want %q\", got, responseText)\n\t}\n\tif w.Code != http.StatusTeapot {\n\t\tt.Errorf(\"unexpected response code %d want %d\", w.Code, http.StatusTeapot)\n\t}\n\tif allow := w.Header().Get(\"Allow\"); allow != \"DELETE, OPTIONS, POST\" {\n\t\tt.Error(\"unexpected Allow header value: \" + allow)\n\t}\n}\n\nfunc TestRouterNotFound(t *testing.T) {\n\thandlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}\n\n\trouter := New()\n\trouter.GET(\"/path\", handlerFunc)\n\trouter.GET(\"/dir/\", handlerFunc)\n\trouter.GET(\"/\", handlerFunc)\n\n\ttestRoutes := []struct {\n\t\troute    string\n\t\tcode     int\n\t\tlocation string\n\t}{\n\t\t{\"/path/\", http.StatusMovedPermanently, \"/path\"},   // TSR -/\n\t\t{\"/dir\", http.StatusMovedPermanently, \"/dir/\"},     // TSR +/\n\t\t{\"\", http.StatusMovedPermanently, \"/\"},             // TSR +/\n\t\t{\"/PATH\", http.StatusMovedPermanently, \"/path\"},    // Fixed Case\n\t\t{\"/DIR/\", http.StatusMovedPermanently, \"/dir/\"},    // Fixed Case\n\t\t{\"/PATH/\", http.StatusMovedPermanently, \"/path\"},   // Fixed Case -/\n\t\t{\"/DIR\", http.StatusMovedPermanently, \"/dir/\"},     // Fixed Case +/\n\t\t{\"/../path\", http.StatusMovedPermanently, \"/path\"}, // CleanPath\n\t\t{\"/nope\", http.StatusNotFound, \"\"},                 // NotFound\n\t}\n\tfor _, tr := range testRoutes {\n\t\tr, _ := http.NewRequest(http.MethodGet, tr.route, nil)\n\t\tw := httptest.NewRecorder()\n\t\trouter.ServeHTTP(w, r)\n\t\tif !(w.Code == tr.code && (w.Code == http.StatusNotFound || fmt.Sprint(w.Header().Get(\"Location\")) == tr.location)) {\n\t\t\tt.Errorf(\"NotFound handling route %s failed: Code=%d, Header=%v\", tr.route, w.Code, w.Header().Get(\"Location\"))\n\t\t}\n\t}\n\n\t// Test custom not found handler\n\tvar notFound bool\n\trouter.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {\n\t\trw.WriteHeader(http.StatusNotFound)\n\t\tnotFound = true\n\t})\n\tr, _ := http.NewRequest(http.MethodGet, \"/nope\", nil)\n\tw := httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNotFound && notFound == true) {\n\t\tt.Errorf(\"Custom NotFound handler failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t}\n\n\t// Test other method than GET (want 308 instead of 301)\n\trouter.PATCH(\"/path\", handlerFunc)\n\tr, _ = http.NewRequest(http.MethodPatch, \"/path/\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusPermanentRedirect && fmt.Sprint(w.Header()) == \"map[Location:[/path]]\") {\n\t\tt.Errorf(\"Custom NotFound handler failed: Code=%d, Header=%v\", w.Code, w.Header())\n\t}\n\n\t// Test special case where no node for the prefix \"/\" exists\n\trouter = New()\n\trouter.GET(\"/a\", handlerFunc)\n\tr, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\tw = httptest.NewRecorder()\n\trouter.ServeHTTP(w, r)\n\tif !(w.Code == http.StatusNotFound) {\n\t\tt.Errorf(\"NotFound handling route / failed: Code=%d\", w.Code)\n\t}\n}\n\nfunc TestRouterPanicHandler(t *testing.T) {\n\trouter := New()\n\tpanicHandled := false\n\n\trouter.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {\n\t\tpanicHandled = true\n\t}\n\n\trouter.Handle(http.MethodPut, \"/user/:name\", func(_ http.ResponseWriter, _ *http.Request, _ Params) {\n\t\tpanic(\"oops!\")\n\t})\n\n\tw := new(mockResponseWriter)\n\treq, _ := http.NewRequest(http.MethodPut, \"/user/gopher\", nil)\n\n\tdefer func() {\n\t\tif rcv := recover(); rcv != nil {\n\t\t\tt.Fatal(\"handling panic failed\")\n\t\t}\n\t}()\n\n\trouter.ServeHTTP(w, req)\n\n\tif !panicHandled {\n\t\tt.Fatal(\"simulating failed\")\n\t}\n}\n\nfunc TestRouterLookup(t *testing.T) {\n\trouted := false\n\twantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {\n\t\trouted = true\n\t}\n\twantParams := Params{Param{\"name\", \"gopher\"}}\n\n\trouter := New()\n\n\t// try empty router first\n\thandle, _, tsr := router.Lookup(http.MethodGet, \"/nope\")\n\tif handle != nil {\n\t\tt.Fatalf(\"Got handle for unregistered pattern: %v\", handle)\n\t}\n\tif tsr {\n\t\tt.Error(\"Got wrong TSR recommendation!\")\n\t}\n\n\t// insert route and try again\n\trouter.GET(\"/user/:name\", wantHandle)\n\thandle, params, _ := router.Lookup(http.MethodGet, \"/user/gopher\")\n\tif handle == nil {\n\t\tt.Fatal(\"Got no handle!\")\n\t} else {\n\t\thandle(nil, nil, nil)\n\t\tif !routed {\n\t\t\tt.Fatal(\"Routing failed!\")\n\t\t}\n\t}\n\tif !reflect.DeepEqual(params, wantParams) {\n\t\tt.Fatalf(\"Wrong parameter values: want %v, got %v\", wantParams, params)\n\t}\n\trouted = false\n\n\t// route without param\n\trouter.GET(\"/user\", wantHandle)\n\thandle, params, _ = router.Lookup(http.MethodGet, \"/user\")\n\tif handle == nil {\n\t\tt.Fatal(\"Got no handle!\")\n\t} else {\n\t\thandle(nil, nil, nil)\n\t\tif !routed {\n\t\t\tt.Fatal(\"Routing failed!\")\n\t\t}\n\t}\n\tif params != nil {\n\t\tt.Fatalf(\"Wrong parameter values: want %v, got %v\", nil, params)\n\t}\n\n\thandle, _, tsr = router.Lookup(http.MethodGet, \"/user/gopher/\")\n\tif handle != nil {\n\t\tt.Fatalf(\"Got handle for unregistered pattern: %v\", handle)\n\t}\n\tif !tsr {\n\t\tt.Error(\"Got no TSR recommendation!\")\n\t}\n\n\thandle, _, tsr = router.Lookup(http.MethodGet, \"/nope\")\n\tif handle != nil {\n\t\tt.Fatalf(\"Got handle for unregistered pattern: %v\", handle)\n\t}\n\tif tsr {\n\t\tt.Error(\"Got wrong TSR recommendation!\")\n\t}\n}\n\nfunc TestRouterParamsFromContext(t *testing.T) {\n\trouted := false\n\n\twantParams := Params{Param{\"name\", \"gopher\"}}\n\thandlerFunc := func(_ http.ResponseWriter, req *http.Request) {\n\t\t// get params from request context\n\t\tparams := ParamsFromContext(req.Context())\n\n\t\tif !reflect.DeepEqual(params, wantParams) {\n\t\t\tt.Fatalf(\"Wrong parameter values: want %v, got %v\", wantParams, params)\n\t\t}\n\n\t\trouted = true\n\t}\n\n\tvar nilParams Params\n\thandlerFuncNil := func(_ http.ResponseWriter, req *http.Request) {\n\t\t// get params from request context\n\t\tparams := ParamsFromContext(req.Context())\n\n\t\tif !reflect.DeepEqual(params, nilParams) {\n\t\t\tt.Fatalf(\"Wrong parameter values: want %v, got %v\", nilParams, params)\n\t\t}\n\n\t\trouted = true\n\t}\n\trouter := New()\n\trouter.HandlerFunc(http.MethodGet, \"/user\", handlerFuncNil)\n\trouter.HandlerFunc(http.MethodGet, \"/user/:name\", handlerFunc)\n\n\tw := new(mockResponseWriter)\n\tr, _ := http.NewRequest(http.MethodGet, \"/user/gopher\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !routed {\n\t\tt.Fatal(\"Routing failed!\")\n\t}\n\n\trouted = false\n\tr, _ = http.NewRequest(http.MethodGet, \"/user\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !routed {\n\t\tt.Fatal(\"Routing failed!\")\n\t}\n}\n\nfunc TestRouterMatchedRoutePath(t *testing.T) {\n\troute1 := \"/user/:name\"\n\trouted1 := false\n\thandle1 := func(_ http.ResponseWriter, req *http.Request, ps Params) {\n\t\troute := ps.MatchedRoutePath()\n\t\tif route != route1 {\n\t\t\tt.Fatalf(\"Wrong matched route: want %s, got %s\", route1, route)\n\t\t}\n\t\trouted1 = true\n\t}\n\n\troute2 := \"/user/:name/details\"\n\trouted2 := false\n\thandle2 := func(_ http.ResponseWriter, req *http.Request, ps Params) {\n\t\troute := ps.MatchedRoutePath()\n\t\tif route != route2 {\n\t\t\tt.Fatalf(\"Wrong matched route: want %s, got %s\", route2, route)\n\t\t}\n\t\trouted2 = true\n\t}\n\n\troute3 := \"/\"\n\trouted3 := false\n\thandle3 := func(_ http.ResponseWriter, req *http.Request, ps Params) {\n\t\troute := ps.MatchedRoutePath()\n\t\tif route != route3 {\n\t\t\tt.Fatalf(\"Wrong matched route: want %s, got %s\", route3, route)\n\t\t}\n\t\trouted3 = true\n\t}\n\n\trouter := New()\n\trouter.SaveMatchedRoutePath = true\n\trouter.Handle(http.MethodGet, route1, handle1)\n\trouter.Handle(http.MethodGet, route2, handle2)\n\trouter.Handle(http.MethodGet, route3, handle3)\n\n\tw := new(mockResponseWriter)\n\tr, _ := http.NewRequest(http.MethodGet, \"/user/gopher\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !routed1 || routed2 || routed3 {\n\t\tt.Fatal(\"Routing failed!\")\n\t}\n\n\tw = new(mockResponseWriter)\n\tr, _ = http.NewRequest(http.MethodGet, \"/user/gopher/details\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !routed2 || routed3 {\n\t\tt.Fatal(\"Routing failed!\")\n\t}\n\n\tw = new(mockResponseWriter)\n\tr, _ = http.NewRequest(http.MethodGet, \"/\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !routed3 {\n\t\tt.Fatal(\"Routing failed!\")\n\t}\n}\n\ntype mockFileSystem struct {\n\topened bool\n}\n\nfunc (mfs *mockFileSystem) Open(name string) (http.File, error) {\n\tmfs.opened = true\n\treturn nil, errors.New(\"this is just a mock\")\n}\n\nfunc TestRouterServeFiles(t *testing.T) {\n\trouter := New()\n\tmfs := &mockFileSystem{}\n\n\trecv := catchPanic(func() {\n\t\trouter.ServeFiles(\"/noFilepath\", mfs)\n\t})\n\tif recv == nil {\n\t\tt.Fatal(\"registering path not ending with '*filepath' did not panic\")\n\t}\n\n\trouter.ServeFiles(\"/*filepath\", mfs)\n\tw := new(mockResponseWriter)\n\tr, _ := http.NewRequest(http.MethodGet, \"/favicon.ico\", nil)\n\trouter.ServeHTTP(w, r)\n\tif !mfs.opened {\n\t\tt.Error(\"serving file failed\")\n\t}\n}\n"
  },
  {
    "path": "tree.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\npackage httprouter\n\nimport (\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\nfunc min(a, b int) int {\n\tif a <= b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc longestCommonPrefix(a, b string) int {\n\ti := 0\n\tmax := min(len(a), len(b))\n\tfor i < max && a[i] == b[i] {\n\t\ti++\n\t}\n\treturn i\n}\n\n// Search for a wildcard segment and check the name for invalid characters.\n// Returns -1 as index, if no wildcard was found.\nfunc findWildcard(path string) (wilcard string, i int, valid bool) {\n\t// Find start\n\tfor start, c := range []byte(path) {\n\t\t// A wildcard starts with ':' (param) or '*' (catch-all)\n\t\tif c != ':' && c != '*' {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Find end and check for invalid characters\n\t\tvalid = true\n\t\tfor end, c := range []byte(path[start+1:]) {\n\t\t\tswitch c {\n\t\t\tcase '/':\n\t\t\t\treturn path[start : start+1+end], start, valid\n\t\t\tcase ':', '*':\n\t\t\t\tvalid = false\n\t\t\t}\n\t\t}\n\t\treturn path[start:], start, valid\n\t}\n\treturn \"\", -1, false\n}\n\nfunc countParams(path string) uint16 {\n\tvar n uint16\n\tfor i := range []byte(path) {\n\t\tswitch path[i] {\n\t\tcase ':', '*':\n\t\t\tn++\n\t\t}\n\t}\n\treturn n\n}\n\ntype nodeType uint8\n\nconst (\n\tstatic nodeType = iota // default\n\troot\n\tparam\n\tcatchAll\n)\n\ntype node struct {\n\tpath      string\n\tindices   string\n\twildChild bool\n\tnType     nodeType\n\tpriority  uint32\n\tchildren  []*node\n\thandle    Handle\n}\n\n// Increments priority of the given child and reorders if necessary\nfunc (n *node) incrementChildPrio(pos int) int {\n\tcs := n.children\n\tcs[pos].priority++\n\tprio := cs[pos].priority\n\n\t// Adjust position (move to front)\n\tnewPos := pos\n\tfor ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {\n\t\t// Swap node positions\n\t\tcs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]\n\t}\n\n\t// Build new index char string\n\tif newPos != pos {\n\t\tn.indices = n.indices[:newPos] + // Unchanged prefix, might be empty\n\t\t\tn.indices[pos:pos+1] + // The index char we move\n\t\t\tn.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'\n\t}\n\n\treturn newPos\n}\n\n// addRoute adds a node with the given handle to the path.\n// Not concurrency-safe!\nfunc (n *node) addRoute(path string, handle Handle) {\n\tfullPath := path\n\tn.priority++\n\n\t// Empty tree\n\tif n.path == \"\" && n.indices == \"\" {\n\t\tn.insertChild(path, fullPath, handle)\n\t\tn.nType = root\n\t\treturn\n\t}\n\nwalk:\n\tfor {\n\t\t// Find the longest common prefix.\n\t\t// This also implies that the common prefix contains no ':' or '*'\n\t\t// since the existing key can't contain those chars.\n\t\ti := longestCommonPrefix(path, n.path)\n\n\t\t// Split edge\n\t\tif i < len(n.path) {\n\t\t\tchild := node{\n\t\t\t\tpath:      n.path[i:],\n\t\t\t\twildChild: n.wildChild,\n\t\t\t\tnType:     static,\n\t\t\t\tindices:   n.indices,\n\t\t\t\tchildren:  n.children,\n\t\t\t\thandle:    n.handle,\n\t\t\t\tpriority:  n.priority - 1,\n\t\t\t}\n\n\t\t\tn.children = []*node{&child}\n\t\t\t// []byte for proper unicode char conversion, see #65\n\t\t\tn.indices = string([]byte{n.path[i]})\n\t\t\tn.path = path[:i]\n\t\t\tn.handle = nil\n\t\t\tn.wildChild = false\n\t\t}\n\n\t\t// Make new node a child of this node\n\t\tif i < len(path) {\n\t\t\tpath = path[i:]\n\n\t\t\tif n.wildChild {\n\t\t\t\tn = n.children[0]\n\t\t\t\tn.priority++\n\n\t\t\t\t// Check if the wildcard matches\n\t\t\t\tif len(path) >= len(n.path) && n.path == path[:len(n.path)] &&\n\t\t\t\t\t// Adding a child to a catchAll is not possible\n\t\t\t\t\tn.nType != catchAll &&\n\t\t\t\t\t// Check for longer wildcard, e.g. :name and :names\n\t\t\t\t\t(len(n.path) >= len(path) || path[len(n.path)] == '/') {\n\t\t\t\t\tcontinue walk\n\t\t\t\t} else {\n\t\t\t\t\t// Wildcard conflict\n\t\t\t\t\tpathSeg := path\n\t\t\t\t\tif n.nType != catchAll {\n\t\t\t\t\t\tpathSeg = strings.SplitN(pathSeg, \"/\", 2)[0]\n\t\t\t\t\t}\n\t\t\t\t\tprefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path\n\t\t\t\t\tpanic(\"'\" + pathSeg +\n\t\t\t\t\t\t\"' in new path '\" + fullPath +\n\t\t\t\t\t\t\"' conflicts with existing wildcard '\" + n.path +\n\t\t\t\t\t\t\"' in existing prefix '\" + prefix +\n\t\t\t\t\t\t\"'\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tidxc := path[0]\n\n\t\t\t// '/' after param\n\t\t\tif n.nType == param && idxc == '/' && len(n.children) == 1 {\n\t\t\t\tn = n.children[0]\n\t\t\t\tn.priority++\n\t\t\t\tcontinue walk\n\t\t\t}\n\n\t\t\t// Check if a child with the next path byte exists\n\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\tif c == idxc {\n\t\t\t\t\ti = n.incrementChildPrio(i)\n\t\t\t\t\tn = n.children[i]\n\t\t\t\t\tcontinue walk\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Otherwise insert it\n\t\t\tif idxc != ':' && idxc != '*' {\n\t\t\t\t// []byte for proper unicode char conversion, see #65\n\t\t\t\tn.indices += string([]byte{idxc})\n\t\t\t\tchild := &node{}\n\t\t\t\tn.children = append(n.children, child)\n\t\t\t\tn.incrementChildPrio(len(n.indices) - 1)\n\t\t\t\tn = child\n\t\t\t}\n\t\t\tn.insertChild(path, fullPath, handle)\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise add handle to current node\n\t\tif n.handle != nil {\n\t\t\tpanic(\"a handle is already registered for path '\" + fullPath + \"'\")\n\t\t}\n\t\tn.handle = handle\n\t\treturn\n\t}\n}\n\nfunc (n *node) insertChild(path, fullPath string, handle Handle) {\n\tfor {\n\t\t// Find prefix until first wildcard\n\t\twildcard, i, valid := findWildcard(path)\n\t\tif i < 0 { // No wilcard found\n\t\t\tbreak\n\t\t}\n\n\t\t// The wildcard name must not contain ':' and '*'\n\t\tif !valid {\n\t\t\tpanic(\"only one wildcard per path segment is allowed, has: '\" +\n\t\t\t\twildcard + \"' in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\t// Check if the wildcard has a name\n\t\tif len(wildcard) < 2 {\n\t\t\tpanic(\"wildcards must be named with a non-empty name in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\t// Check if this node has existing children which would be\n\t\t// unreachable if we insert the wildcard here\n\t\tif len(n.children) > 0 {\n\t\t\tpanic(\"wildcard segment '\" + wildcard +\n\t\t\t\t\"' conflicts with existing children in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\t// param\n\t\tif wildcard[0] == ':' {\n\t\t\tif i > 0 {\n\t\t\t\t// Insert prefix before the current wildcard\n\t\t\t\tn.path = path[:i]\n\t\t\t\tpath = path[i:]\n\t\t\t}\n\n\t\t\tn.wildChild = true\n\t\t\tchild := &node{\n\t\t\t\tnType: param,\n\t\t\t\tpath:  wildcard,\n\t\t\t}\n\t\t\tn.children = []*node{child}\n\t\t\tn = child\n\t\t\tn.priority++\n\n\t\t\t// If the path doesn't end with the wildcard, then there\n\t\t\t// will be another non-wildcard subpath starting with '/'\n\t\t\tif len(wildcard) < len(path) {\n\t\t\t\tpath = path[len(wildcard):]\n\t\t\t\tchild := &node{\n\t\t\t\t\tpriority: 1,\n\t\t\t\t}\n\t\t\t\tn.children = []*node{child}\n\t\t\t\tn = child\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Otherwise we're done. Insert the handle in the new leaf\n\t\t\tn.handle = handle\n\t\t\treturn\n\t\t}\n\n\t\t// catchAll\n\t\tif i+len(wildcard) != len(path) {\n\t\t\tpanic(\"catch-all routes are only allowed at the end of the path in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tif len(n.path) > 0 && n.path[len(n.path)-1] == '/' {\n\t\t\tpanic(\"catch-all conflicts with existing handle for the path segment root in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\t// Currently fixed width 1 for '/'\n\t\ti--\n\t\tif path[i] != '/' {\n\t\t\tpanic(\"no / before catch-all in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tn.path = path[:i]\n\n\t\t// First node: catchAll node with empty path\n\t\tchild := &node{\n\t\t\twildChild: true,\n\t\t\tnType:     catchAll,\n\t\t}\n\t\tn.children = []*node{child}\n\t\tn.indices = string('/')\n\t\tn = child\n\t\tn.priority++\n\n\t\t// Second node: node holding the variable\n\t\tchild = &node{\n\t\t\tpath:     path[i:],\n\t\t\tnType:    catchAll,\n\t\t\thandle:   handle,\n\t\t\tpriority: 1,\n\t\t}\n\t\tn.children = []*node{child}\n\n\t\treturn\n\t}\n\n\t// If no wildcard was found, simply insert the path and handle\n\tn.path = path\n\tn.handle = handle\n}\n\n// Returns the handle registered with the given path (key). The values of\n// wildcards are saved to a map.\n// If no handle can be found, a TSR (trailing slash redirect) recommendation is\n// made if a handle exists with an extra (without the) trailing slash for the\n// given path.\nfunc (n *node) getValue(path string, params func() *Params) (handle Handle, ps *Params, tsr bool) {\nwalk: // Outer loop for walking the tree\n\tfor {\n\t\tprefix := n.path\n\t\tif len(path) > len(prefix) {\n\t\t\tif path[:len(prefix)] == prefix {\n\t\t\t\tpath = path[len(prefix):]\n\n\t\t\t\t// If this node does not have a wildcard (param or catchAll)\n\t\t\t\t// child, we can just look up the next child node and continue\n\t\t\t\t// to walk down the tree\n\t\t\t\tif !n.wildChild {\n\t\t\t\t\tidxc := path[0]\n\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Nothing found.\n\t\t\t\t\t// We can recommend to redirect to the same URL without a\n\t\t\t\t\t// trailing slash if a leaf exists for that path.\n\t\t\t\t\ttsr = (path == \"/\" && n.handle != nil)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Handle wildcard child\n\t\t\t\tn = n.children[0]\n\t\t\t\tswitch n.nType {\n\t\t\t\tcase param:\n\t\t\t\t\t// Find param end (either '/' or path end)\n\t\t\t\t\tend := 0\n\t\t\t\t\tfor end < len(path) && path[end] != '/' {\n\t\t\t\t\t\tend++\n\t\t\t\t\t}\n\n\t\t\t\t\t// Save param value\n\t\t\t\t\tif params != nil {\n\t\t\t\t\t\tif ps == nil {\n\t\t\t\t\t\t\tps = params()\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Expand slice within preallocated capacity\n\t\t\t\t\t\ti := len(*ps)\n\t\t\t\t\t\t*ps = (*ps)[:i+1]\n\t\t\t\t\t\t(*ps)[i] = Param{\n\t\t\t\t\t\t\tKey:   n.path[1:],\n\t\t\t\t\t\t\tValue: path[:end],\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// We need to go deeper!\n\t\t\t\t\tif end < len(path) {\n\t\t\t\t\t\tif len(n.children) > 0 {\n\t\t\t\t\t\t\tpath = path[end:]\n\t\t\t\t\t\t\tn = n.children[0]\n\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// ... but we can't\n\t\t\t\t\t\ttsr = (len(path) == end+1)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif handle = n.handle; handle != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else if len(n.children) == 1 {\n\t\t\t\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t\t\t\t// trailing slash exists for TSR recommendation\n\t\t\t\t\t\tn = n.children[0]\n\t\t\t\t\t\ttsr = (n.path == \"/\" && n.handle != nil) || (n.path == \"\" && n.indices == \"/\")\n\t\t\t\t\t}\n\n\t\t\t\t\treturn\n\n\t\t\t\tcase catchAll:\n\t\t\t\t\t// Save param value\n\t\t\t\t\tif params != nil {\n\t\t\t\t\t\tif ps == nil {\n\t\t\t\t\t\t\tps = params()\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Expand slice within preallocated capacity\n\t\t\t\t\t\ti := len(*ps)\n\t\t\t\t\t\t*ps = (*ps)[:i+1]\n\t\t\t\t\t\t(*ps)[i] = Param{\n\t\t\t\t\t\t\tKey:   n.path[2:],\n\t\t\t\t\t\t\tValue: path,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\thandle = n.handle\n\t\t\t\t\treturn\n\n\t\t\t\tdefault:\n\t\t\t\t\tpanic(\"invalid node type\")\n\t\t\t\t}\n\t\t\t}\n\t\t} else if path == prefix {\n\t\t\t// We should have reached the node containing the handle.\n\t\t\t// Check if this node has a handle registered.\n\t\t\tif handle = n.handle; handle != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If there is no handle for this route, but this route has a\n\t\t\t// wildcard child, there must be a handle for this path with an\n\t\t\t// additional trailing slash\n\t\t\tif path == \"/\" && n.wildChild && n.nType != root {\n\t\t\t\ttsr = true\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif path == \"/\" && n.nType == static {\n\t\t\t\ttsr = true\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t// trailing slash exists for trailing slash recommendation\n\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\tif c == '/' {\n\t\t\t\t\tn = n.children[i]\n\t\t\t\t\ttsr = (len(n.path) == 1 && n.handle != nil) ||\n\t\t\t\t\t\t(n.nType == catchAll && n.children[0].handle != nil)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Nothing found. We can recommend to redirect to the same URL with an\n\t\t// extra trailing slash if a leaf exists for that path\n\t\ttsr = (path == \"/\") ||\n\t\t\t(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&\n\t\t\t\tpath == prefix[:len(prefix)-1] && n.handle != nil)\n\t\treturn\n\t}\n}\n\n// Makes a case-insensitive lookup of the given path and tries to find a handler.\n// It can optionally also fix trailing slashes.\n// It returns the case-corrected path and a bool indicating whether the lookup\n// was successful.\nfunc (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (fixedPath string, found bool) {\n\tconst stackBufSize = 128\n\n\t// Use a static sized buffer on the stack in the common case.\n\t// If the path is too long, allocate a buffer on the heap instead.\n\tbuf := make([]byte, 0, stackBufSize)\n\tif l := len(path) + 1; l > stackBufSize {\n\t\tbuf = make([]byte, 0, l)\n\t}\n\n\tciPath := n.findCaseInsensitivePathRec(\n\t\tpath,\n\t\tbuf,       // Preallocate enough memory for new path\n\t\t[4]byte{}, // Empty rune buffer\n\t\tfixTrailingSlash,\n\t)\n\n\treturn string(ciPath), ciPath != nil\n}\n\n// Shift bytes in array by n bytes left\nfunc shiftNRuneBytes(rb [4]byte, n int) [4]byte {\n\tswitch n {\n\tcase 0:\n\t\treturn rb\n\tcase 1:\n\t\treturn [4]byte{rb[1], rb[2], rb[3], 0}\n\tcase 2:\n\t\treturn [4]byte{rb[2], rb[3]}\n\tcase 3:\n\t\treturn [4]byte{rb[3]}\n\tdefault:\n\t\treturn [4]byte{}\n\t}\n}\n\n// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath\nfunc (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {\n\tnpLen := len(n.path)\n\nwalk: // Outer loop for walking the tree\n\tfor len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {\n\t\t// Add common prefix to result\n\t\toldPath := path\n\t\tpath = path[npLen:]\n\t\tciPath = append(ciPath, n.path...)\n\n\t\tif len(path) > 0 {\n\t\t\t// If this node does not have a wildcard (param or catchAll) child,\n\t\t\t// we can just look up the next child node and continue to walk down\n\t\t\t// the tree\n\t\t\tif !n.wildChild {\n\t\t\t\t// Skip rune bytes already processed\n\t\t\t\trb = shiftNRuneBytes(rb, npLen)\n\n\t\t\t\tif rb[0] != 0 {\n\t\t\t\t\t// Old rune not finished\n\t\t\t\t\tidxc := rb[0]\n\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\t// continue with child node\n\t\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Process a new rune\n\t\t\t\t\tvar rv rune\n\n\t\t\t\t\t// Find rune start.\n\t\t\t\t\t// Runes are up to 4 byte long,\n\t\t\t\t\t// -4 would definitely be another rune.\n\t\t\t\t\tvar off int\n\t\t\t\t\tfor max := min(npLen, 3); off < max; off++ {\n\t\t\t\t\t\tif i := npLen - off; utf8.RuneStart(oldPath[i]) {\n\t\t\t\t\t\t\t// read rune from cached path\n\t\t\t\t\t\t\trv, _ = utf8.DecodeRuneInString(oldPath[i:])\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Calculate lowercase bytes of current rune\n\t\t\t\t\tlo := unicode.ToLower(rv)\n\t\t\t\t\tutf8.EncodeRune(rb[:], lo)\n\n\t\t\t\t\t// Skip already processed bytes\n\t\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\t\tidxc := rb[0]\n\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\t// Lowercase matches\n\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\t// must use a recursive approach since both the\n\t\t\t\t\t\t\t// uppercase byte and the lowercase byte might exist\n\t\t\t\t\t\t\t// as an index\n\t\t\t\t\t\t\tif out := n.children[i].findCaseInsensitivePathRec(\n\t\t\t\t\t\t\t\tpath, ciPath, rb, fixTrailingSlash,\n\t\t\t\t\t\t\t); out != nil {\n\t\t\t\t\t\t\t\treturn out\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we found no match, the same for the uppercase rune,\n\t\t\t\t\t// if it differs\n\t\t\t\t\tif up := unicode.ToUpper(rv); up != lo {\n\t\t\t\t\t\tutf8.EncodeRune(rb[:], up)\n\t\t\t\t\t\trb = shiftNRuneBytes(rb, off)\n\n\t\t\t\t\t\tidxc := rb[0]\n\t\t\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\t\t\t// Uppercase matches\n\t\t\t\t\t\t\tif c == idxc {\n\t\t\t\t\t\t\t\t// Continue with child node\n\t\t\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\t\t\t\tcontinue walk\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Nothing found. We can recommend to redirect to the same URL\n\t\t\t\t// without a trailing slash if a leaf exists for that path\n\t\t\t\tif fixTrailingSlash && path == \"/\" && n.handle != nil {\n\t\t\t\t\treturn ciPath\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tn = n.children[0]\n\t\t\tswitch n.nType {\n\t\t\tcase param:\n\t\t\t\t// Find param end (either '/' or path end)\n\t\t\t\tend := 0\n\t\t\t\tfor end < len(path) && path[end] != '/' {\n\t\t\t\t\tend++\n\t\t\t\t}\n\n\t\t\t\t// Add param value to case insensitive path\n\t\t\t\tciPath = append(ciPath, path[:end]...)\n\n\t\t\t\t// We need to go deeper!\n\t\t\t\tif end < len(path) {\n\t\t\t\t\tif len(n.children) > 0 {\n\t\t\t\t\t\t// Continue with child node\n\t\t\t\t\t\tn = n.children[0]\n\t\t\t\t\t\tnpLen = len(n.path)\n\t\t\t\t\t\tpath = path[end:]\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// ... but we can't\n\t\t\t\t\tif fixTrailingSlash && len(path) == end+1 {\n\t\t\t\t\t\treturn ciPath\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tif n.handle != nil {\n\t\t\t\t\treturn ciPath\n\t\t\t\t} else if fixTrailingSlash && len(n.children) == 1 {\n\t\t\t\t\t// No handle found. Check if a handle for this path + a\n\t\t\t\t\t// trailing slash exists\n\t\t\t\t\tn = n.children[0]\n\t\t\t\t\tif n.path == \"/\" && n.handle != nil {\n\t\t\t\t\t\treturn append(ciPath, '/')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\n\t\t\tcase catchAll:\n\t\t\t\treturn append(ciPath, path...)\n\n\t\t\tdefault:\n\t\t\t\tpanic(\"invalid node type\")\n\t\t\t}\n\t\t} else {\n\t\t\t// We should have reached the node containing the handle.\n\t\t\t// Check if this node has a handle registered.\n\t\t\tif n.handle != nil {\n\t\t\t\treturn ciPath\n\t\t\t}\n\n\t\t\t// No handle found.\n\t\t\t// Try to fix the path by adding a trailing slash\n\t\t\tif fixTrailingSlash {\n\t\t\t\tfor i, c := range []byte(n.indices) {\n\t\t\t\t\tif c == '/' {\n\t\t\t\t\t\tn = n.children[i]\n\t\t\t\t\t\tif (len(n.path) == 1 && n.handle != nil) ||\n\t\t\t\t\t\t\t(n.nType == catchAll && n.children[0].handle != nil) {\n\t\t\t\t\t\t\treturn append(ciPath, '/')\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Nothing found.\n\t// Try to fix the path by adding / removing a trailing slash\n\tif fixTrailingSlash {\n\t\tif path == \"/\" {\n\t\t\treturn ciPath\n\t\t}\n\t\tif len(path)+1 == npLen && n.path[len(path)] == '/' &&\n\t\t\tstrings.EqualFold(path[1:], n.path[1:len(path)]) && n.handle != nil {\n\t\t\treturn append(ciPath, n.path...)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "tree_test.go",
    "content": "// Copyright 2013 Julien Schmidt. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be found\n// in the LICENSE file.\n\npackage httprouter\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// func printChildren(n *node, prefix string) {\n// \tfmt.Printf(\" %02d %s%s[%d] %v %t %d \\r\\n\", n.priority, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)\n// \tfor l := len(n.path); l > 0; l-- {\n// \t\tprefix += \" \"\n// \t}\n// \tfor _, child := range n.children {\n// \t\tprintChildren(child, prefix)\n// \t}\n// }\n\n// Used as a workaround since we can't compare functions or their addresses\nvar fakeHandlerValue string\n\nfunc fakeHandler(val string) Handle {\n\treturn func(http.ResponseWriter, *http.Request, Params) {\n\t\tfakeHandlerValue = val\n\t}\n}\n\ntype testRequests []struct {\n\tpath       string\n\tnilHandler bool\n\troute      string\n\tps         Params\n}\n\nfunc getParams() *Params {\n\tps := make(Params, 0, 20)\n\treturn &ps\n}\n\nfunc checkRequests(t *testing.T, tree *node, requests testRequests) {\n\tfor _, request := range requests {\n\t\thandler, psp, _ := tree.getValue(request.path, getParams)\n\n\t\tswitch {\n\t\tcase handler == nil:\n\t\t\tif !request.nilHandler {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected non-nil handle\", request.path)\n\t\t\t}\n\t\tcase request.nilHandler:\n\t\t\tt.Errorf(\"handle mismatch for route '%s': Expected nil handle\", request.path)\n\t\tdefault:\n\t\t\thandler(nil, nil, nil)\n\t\t\tif fakeHandlerValue != request.route {\n\t\t\t\tt.Errorf(\"handle mismatch for route '%s': Wrong handle (%s != %s)\", request.path, fakeHandlerValue, request.route)\n\t\t\t}\n\t\t}\n\n\t\tvar ps Params\n\t\tif psp != nil {\n\t\t\tps = *psp\n\t\t}\n\n\t\tif !reflect.DeepEqual(ps, request.ps) {\n\t\t\tt.Errorf(\"Params mismatch for route '%s'\", request.path)\n\t\t}\n\t}\n}\n\nfunc checkPriorities(t *testing.T, n *node) uint32 {\n\tvar prio uint32\n\tfor i := range n.children {\n\t\tprio += checkPriorities(t, n.children[i])\n\t}\n\n\tif n.handle != nil {\n\t\tprio++\n\t}\n\n\tif n.priority != prio {\n\t\tt.Errorf(\n\t\t\t\"priority mismatch for node '%s': is %d, should be %d\",\n\t\t\tn.path, n.priority, prio,\n\t\t)\n\t}\n\n\treturn prio\n}\n\nfunc TestCountParams(t *testing.T) {\n\tif countParams(\"/path/:param1/static/*catch-all\") != 2 {\n\t\tt.Fail()\n\t}\n\tif countParams(strings.Repeat(\"/:param\", 256)) != 256 {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestTreeAddAndGet(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/contact\",\n\t\t\"/co\",\n\t\t\"/c\",\n\t\t\"/a\",\n\t\t\"/ab\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/α\",\n\t\t\"/β\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\t// printChildren(tree, \"\")\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/a\", false, \"/a\", nil},\n\t\t{\"/\", true, \"\", nil},\n\t\t{\"/hi\", false, \"/hi\", nil},\n\t\t{\"/contact\", false, \"/contact\", nil},\n\t\t{\"/co\", false, \"/co\", nil},\n\t\t{\"/con\", true, \"\", nil},  // key mismatch\n\t\t{\"/cona\", true, \"\", nil}, // key mismatch\n\t\t{\"/no\", true, \"\", nil},   // no matching child\n\t\t{\"/ab\", false, \"/ab\", nil},\n\t\t{\"/α\", false, \"/α\", nil},\n\t\t{\"/β\", false, \"/β\", nil},\n\t})\n\n\tcheckPriorities(t, tree)\n}\n\nfunc TestTreeWildcard(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/cmd/:tool/:sub\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/\",\n\t\t\"/search/:query\",\n\t\t\"/user_:name\",\n\t\t\"/user_:name/about\",\n\t\t\"/files/:dir/*filepath\",\n\t\t\"/doc/\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/info/:user/public\",\n\t\t\"/info/:user/project/:project\",\n\t}\n\tfor _, route := range routes {\n\t\ttree.addRoute(route, fakeHandler(route))\n\t}\n\n\t// printChildren(tree, \"\")\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/cmd/test/\", false, \"/cmd/:tool/\", Params{Param{\"tool\", \"test\"}}},\n\t\t{\"/cmd/test\", true, \"\", Params{Param{\"tool\", \"test\"}}},\n\t\t{\"/cmd/test/3\", false, \"/cmd/:tool/:sub\", Params{Param{\"tool\", \"test\"}, Param{\"sub\", \"3\"}}},\n\t\t{\"/src/\", false, \"/src/*filepath\", Params{Param{\"filepath\", \"/\"}}},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", Params{Param{\"filepath\", \"/some/file.png\"}}},\n\t\t{\"/search/\", false, \"/search/\", nil},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", Params{Param{\"query\", \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé/\", true, \"\", Params{Param{\"query\", \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/user_gopher\", false, \"/user_:name\", Params{Param{\"name\", \"gopher\"}}},\n\t\t{\"/user_gopher/about\", false, \"/user_:name/about\", Params{Param{\"name\", \"gopher\"}}},\n\t\t{\"/files/js/inc/framework.js\", false, \"/files/:dir/*filepath\", Params{Param{\"dir\", \"js\"}, Param{\"filepath\", \"/inc/framework.js\"}}},\n\t\t{\"/info/gordon/public\", false, \"/info/:user/public\", Params{Param{\"user\", \"gordon\"}}},\n\t\t{\"/info/gordon/project/go\", false, \"/info/:user/project/:project\", Params{Param{\"user\", \"gordon\"}, Param{\"project\", \"go\"}}},\n\t})\n\n\tcheckPriorities(t, tree)\n}\n\nfunc catchPanic(testFunc func()) (recv interface{}) {\n\tdefer func() {\n\t\trecv = recover()\n\t}()\n\n\ttestFunc()\n\treturn\n}\n\ntype testRoute struct {\n\tpath     string\n\tconflict bool\n}\n\nfunc testRoutes(t *testing.T, routes []testRoute) {\n\ttree := &node{}\n\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route.path, nil)\n\t\t})\n\n\t\tif route.conflict {\n\t\t\tif recv == nil {\n\t\t\t\tt.Errorf(\"no panic for conflicting route '%s'\", route.path)\n\t\t\t}\n\t\t} else if recv != nil {\n\t\t\tt.Errorf(\"unexpected panic for route '%s': %v\", route.path, recv)\n\t\t}\n\t}\n\n\t// printChildren(tree, \"\")\n}\n\nfunc TestTreeWildcardConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/:tool/:sub\", false},\n\t\t{\"/cmd/vet\", true},\n\t\t{\"/src/*filepath\", false},\n\t\t{\"/src/*filepathx\", true},\n\t\t{\"/src/\", true},\n\t\t{\"/src1/\", false},\n\t\t{\"/src1/*filepath\", true},\n\t\t{\"/src2*filepath\", true},\n\t\t{\"/search/:query\", false},\n\t\t{\"/search/invalid\", true},\n\t\t{\"/user_:name\", false},\n\t\t{\"/user_x\", true},\n\t\t{\"/user_:name\", false},\n\t\t{\"/id:id\", false},\n\t\t{\"/id/:id\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeChildConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/cmd/vet\", false},\n\t\t{\"/cmd/:tool/:sub\", true},\n\t\t{\"/src/AUTHORS\", false},\n\t\t{\"/src/*filepath\", true},\n\t\t{\"/user_x\", false},\n\t\t{\"/user_:name\", true},\n\t\t{\"/id/:id\", false},\n\t\t{\"/id:id\", true},\n\t\t{\"/:id\", true},\n\t\t{\"/*filepath\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeDupliatePath(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/\",\n\t\t\"/doc/\",\n\t\t\"/src/*filepath\",\n\t\t\"/search/:query\",\n\t\t\"/user_:name\",\n\t}\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\n\t\t// Add again\n\t\trecv = catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting duplicate route '%s\", route)\n\t\t}\n\t}\n\n\t// printChildren(tree, \"\")\n\n\tcheckRequests(t, tree, testRequests{\n\t\t{\"/\", false, \"/\", nil},\n\t\t{\"/doc/\", false, \"/doc/\", nil},\n\t\t{\"/src/some/file.png\", false, \"/src/*filepath\", Params{Param{\"filepath\", \"/some/file.png\"}}},\n\t\t{\"/search/someth!ng+in+ünìcodé\", false, \"/search/:query\", Params{Param{\"query\", \"someth!ng+in+ünìcodé\"}}},\n\t\t{\"/user_gopher\", false, \"/user_:name\", Params{Param{\"name\", \"gopher\"}}},\n\t})\n}\n\nfunc TestEmptyWildcardName(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/user:\",\n\t\t\"/user:/\",\n\t\t\"/cmd/:/\",\n\t\t\"/src/*\",\n\t}\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\t\tif recv == nil {\n\t\t\tt.Fatalf(\"no panic while inserting route with empty wildcard name '%s\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeCatchAllConflict(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/src/*filepath/x\", true},\n\t\t{\"/src2/\", false},\n\t\t{\"/src2/*filepath/x\", true},\n\t\t{\"/src3/*filepath\", false},\n\t\t{\"/src3/*filepath/x\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeCatchAllConflictRoot(t *testing.T) {\n\troutes := []testRoute{\n\t\t{\"/\", false},\n\t\t{\"/*filepath\", true},\n\t}\n\ttestRoutes(t, routes)\n}\n\nfunc TestTreeCatchMaxParams(t *testing.T) {\n\ttree := &node{}\n\tvar route = \"/cmd/*filepath\"\n\ttree.addRoute(route, fakeHandler(route))\n}\n\nfunc TestTreeDoubleWildcard(t *testing.T) {\n\tconst panicMsg = \"only one wildcard per path segment is allowed\"\n\n\troutes := [...]string{\n\t\t\"/:foo:bar\",\n\t\t\"/:foo:bar/\",\n\t\t\"/:foo*bar\",\n\t}\n\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\ttree := &node{}\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, nil)\n\t\t})\n\n\t\tif rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {\n\t\t\tt.Fatalf(`\"Expected panic \"%s\" for route '%s', got \"%v\"`, panicMsg, route, recv)\n\t\t}\n\t}\n}\n\nfunc TestTreeTrailingSlashRedirect(t *testing.T) {\n\ttree := &node{}\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/admin\",\n\t\t\"/admin/:category\",\n\t\t\"/admin/:category/:page\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/api/hello/:name\",\n\t\t\"/vendor/:x/*y\",\n\t}\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// printChildren(tree, \"\")\n\n\ttsrRoutes := [...]string{\n\t\t\"/hi/\",\n\t\t\"/b\",\n\t\t\"/search/gopher/\",\n\t\t\"/cmd/vet\",\n\t\t\"/src\",\n\t\t\"/x/\",\n\t\t\"/y\",\n\t\t\"/0/go/\",\n\t\t\"/1/go\",\n\t\t\"/a\",\n\t\t\"/admin/\",\n\t\t\"/admin/config/\",\n\t\t\"/admin/config/permissions/\",\n\t\t\"/doc/\",\n\t\t\"/vendor/x\",\n\t}\n\tfor _, route := range tsrRoutes {\n\t\thandler, _, tsr := tree.getValue(route, nil)\n\t\tif handler != nil {\n\t\t\tt.Fatalf(\"non-nil handler for TSR route '%s\", route)\n\t\t} else if !tsr {\n\t\t\tt.Errorf(\"expected TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n\n\tnoTsrRoutes := [...]string{\n\t\t\"/\",\n\t\t\"/no\",\n\t\t\"/no/\",\n\t\t\"/_\",\n\t\t\"/_/\",\n\t\t\"/api/world/abc\",\n\t}\n\tfor _, route := range noTsrRoutes {\n\t\thandler, _, tsr := tree.getValue(route, nil)\n\t\tif handler != nil {\n\t\t\tt.Fatalf(\"non-nil handler for No-TSR route '%s\", route)\n\t\t} else if tsr {\n\t\t\tt.Errorf(\"expected no TSR recommendation for route '%s'\", route)\n\t\t}\n\t}\n}\n\nfunc TestTreeRootTrailingSlashRedirect(t *testing.T) {\n\ttree := &node{}\n\n\trecv := catchPanic(func() {\n\t\ttree.addRoute(\"/:test\", fakeHandler(\"/:test\"))\n\t})\n\tif recv != nil {\n\t\tt.Fatalf(\"panic inserting test route: %v\", recv)\n\t}\n\n\thandler, _, tsr := tree.getValue(\"/\", nil)\n\tif handler != nil {\n\t\tt.Fatalf(\"non-nil handler\")\n\t} else if tsr {\n\t\tt.Errorf(\"expected no TSR recommendation\")\n\t}\n}\n\nfunc TestTreeFindCaseInsensitivePath(t *testing.T) {\n\ttree := &node{}\n\n\tlongPath := \"/l\" + strings.Repeat(\"o\", 128) + \"ng\"\n\tlOngPath := \"/l\" + strings.Repeat(\"O\", 128) + \"ng/\"\n\n\troutes := [...]string{\n\t\t\"/hi\",\n\t\t\"/b/\",\n\t\t\"/ABC/\",\n\t\t\"/search/:query\",\n\t\t\"/cmd/:tool/\",\n\t\t\"/src/*filepath\",\n\t\t\"/x\",\n\t\t\"/x/y\",\n\t\t\"/y/\",\n\t\t\"/y/z\",\n\t\t\"/0/:id\",\n\t\t\"/0/:id/1\",\n\t\t\"/1/:id/\",\n\t\t\"/1/:id/2\",\n\t\t\"/aa\",\n\t\t\"/a/\",\n\t\t\"/doc\",\n\t\t\"/doc/go_faq.html\",\n\t\t\"/doc/go1.html\",\n\t\t\"/doc/go/away\",\n\t\t\"/no/a\",\n\t\t\"/no/b\",\n\t\t\"/Π\",\n\t\t\"/u/apfêl/\",\n\t\t\"/u/äpfêl/\",\n\t\t\"/u/öpfêl\",\n\t\t\"/v/Äpfêl/\",\n\t\t\"/v/Öpfêl\",\n\t\t\"/w/♬\",  // 3 byte\n\t\t\"/w/♭/\", // 3 byte, last byte differs\n\t\t\"/w/𠜎\",  // 4 byte\n\t\t\"/w/𠜏/\", // 4 byte\n\t\tlongPath,\n\t}\n\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t})\n\t\tif recv != nil {\n\t\t\tt.Fatalf(\"panic inserting route '%s': %v\", route, recv)\n\t\t}\n\t}\n\n\t// Check out == in for all registered routes\n\t// With fixTrailingSlash = true\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\tout, found := tree.findCaseInsensitivePath(route, true)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if out != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, out)\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor i := range routes {\n\t\troute := routes[i]\n\t\tout, found := tree.findCaseInsensitivePath(route, false)\n\t\tif !found {\n\t\t\tt.Errorf(\"Route '%s' not found!\", route)\n\t\t} else if out != route {\n\t\t\tt.Errorf(\"Wrong result for route '%s': %s\", route, out)\n\t\t}\n\t}\n\n\ttests := []struct {\n\t\tin    string\n\t\tout   string\n\t\tfound bool\n\t\tslash bool\n\t}{\n\t\t{\"/HI\", \"/hi\", true, false},\n\t\t{\"/HI/\", \"/hi\", true, true},\n\t\t{\"/B\", \"/b/\", true, true},\n\t\t{\"/B/\", \"/b/\", true, false},\n\t\t{\"/abc\", \"/ABC/\", true, true},\n\t\t{\"/abc/\", \"/ABC/\", true, false},\n\t\t{\"/aBc\", \"/ABC/\", true, true},\n\t\t{\"/aBc/\", \"/ABC/\", true, false},\n\t\t{\"/abC\", \"/ABC/\", true, true},\n\t\t{\"/abC/\", \"/ABC/\", true, false},\n\t\t{\"/SEARCH/QUERY\", \"/search/QUERY\", true, false},\n\t\t{\"/SEARCH/QUERY/\", \"/search/QUERY\", true, true},\n\t\t{\"/CMD/TOOL/\", \"/cmd/TOOL/\", true, false},\n\t\t{\"/CMD/TOOL\", \"/cmd/TOOL/\", true, true},\n\t\t{\"/SRC/FILE/PATH\", \"/src/FILE/PATH\", true, false},\n\t\t{\"/x/Y\", \"/x/y\", true, false},\n\t\t{\"/x/Y/\", \"/x/y\", true, true},\n\t\t{\"/X/y\", \"/x/y\", true, false},\n\t\t{\"/X/y/\", \"/x/y\", true, true},\n\t\t{\"/X/Y\", \"/x/y\", true, false},\n\t\t{\"/X/Y/\", \"/x/y\", true, true},\n\t\t{\"/Y/\", \"/y/\", true, false},\n\t\t{\"/Y\", \"/y/\", true, true},\n\t\t{\"/Y/z\", \"/y/z\", true, false},\n\t\t{\"/Y/z/\", \"/y/z\", true, true},\n\t\t{\"/Y/Z\", \"/y/z\", true, false},\n\t\t{\"/Y/Z/\", \"/y/z\", true, true},\n\t\t{\"/y/Z\", \"/y/z\", true, false},\n\t\t{\"/y/Z/\", \"/y/z\", true, true},\n\t\t{\"/Aa\", \"/aa\", true, false},\n\t\t{\"/Aa/\", \"/aa\", true, true},\n\t\t{\"/AA\", \"/aa\", true, false},\n\t\t{\"/AA/\", \"/aa\", true, true},\n\t\t{\"/aA\", \"/aa\", true, false},\n\t\t{\"/aA/\", \"/aa\", true, true},\n\t\t{\"/A/\", \"/a/\", true, false},\n\t\t{\"/A\", \"/a/\", true, true},\n\t\t{\"/DOC\", \"/doc\", true, false},\n\t\t{\"/DOC/\", \"/doc\", true, true},\n\t\t{\"/NO\", \"\", false, true},\n\t\t{\"/DOC/GO\", \"\", false, true},\n\t\t{\"/π\", \"/Π\", true, false},\n\t\t{\"/π/\", \"/Π\", true, true},\n\t\t{\"/u/ÄPFÊL/\", \"/u/äpfêl/\", true, false},\n\t\t{\"/u/ÄPFÊL\", \"/u/äpfêl/\", true, true},\n\t\t{\"/u/ÖPFÊL/\", \"/u/öpfêl\", true, true},\n\t\t{\"/u/ÖPFÊL\", \"/u/öpfêl\", true, false},\n\t\t{\"/v/äpfêL/\", \"/v/Äpfêl/\", true, false},\n\t\t{\"/v/äpfêL\", \"/v/Äpfêl/\", true, true},\n\t\t{\"/v/öpfêL/\", \"/v/Öpfêl\", true, true},\n\t\t{\"/v/öpfêL\", \"/v/Öpfêl\", true, false},\n\t\t{\"/w/♬/\", \"/w/♬\", true, true},\n\t\t{\"/w/♭\", \"/w/♭/\", true, true},\n\t\t{\"/w/𠜎/\", \"/w/𠜎\", true, true},\n\t\t{\"/w/𠜏\", \"/w/𠜏/\", true, true},\n\t\t{lOngPath, longPath, true, true},\n\t}\n\t// With fixTrailingSlash = true\n\tfor _, test := range tests {\n\t\tout, found := tree.findCaseInsensitivePath(test.in, true)\n\t\tif found != test.found || (found && (out != test.out)) {\n\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\ttest.in, out, found, test.out, test.found)\n\t\t\treturn\n\t\t}\n\t}\n\t// With fixTrailingSlash = false\n\tfor _, test := range tests {\n\t\tout, found := tree.findCaseInsensitivePath(test.in, false)\n\t\tif test.slash {\n\t\t\tif found { // test needs a trailingSlash fix. It must not be found!\n\t\t\t\tt.Errorf(\"Found without fixTrailingSlash: %s; got %s\", test.in, out)\n\t\t\t}\n\t\t} else {\n\t\t\tif found != test.found || (found && (out != test.out)) {\n\t\t\t\tt.Errorf(\"Wrong result for '%s': got %s, %t; want %s, %t\",\n\t\t\t\t\ttest.in, out, found, test.out, test.found)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTreeInvalidNodeType(t *testing.T) {\n\tconst panicMsg = \"invalid node type\"\n\n\ttree := &node{}\n\ttree.addRoute(\"/\", fakeHandler(\"/\"))\n\ttree.addRoute(\"/:page\", fakeHandler(\"/:page\"))\n\n\t// set invalid node type\n\ttree.children[0].nType = 42\n\n\t// normal lookup\n\trecv := catchPanic(func() {\n\t\ttree.getValue(\"/test\", nil)\n\t})\n\tif rs, ok := recv.(string); !ok || rs != panicMsg {\n\t\tt.Fatalf(\"Expected panic '\"+panicMsg+\"', got '%v'\", recv)\n\t}\n\n\t// case-insensitive lookup\n\trecv = catchPanic(func() {\n\t\ttree.findCaseInsensitivePath(\"/test\", true)\n\t})\n\tif rs, ok := recv.(string); !ok || rs != panicMsg {\n\t\tt.Fatalf(\"Expected panic '\"+panicMsg+\"', got '%v'\", recv)\n\t}\n}\n\nfunc TestTreeWildcardConflictEx(t *testing.T) {\n\tconflicts := [...]struct {\n\t\troute        string\n\t\tsegPath      string\n\t\texistPath    string\n\t\texistSegPath string\n\t}{\n\t\t{\"/who/are/foo\", \"/foo\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/who/are/foo/\", \"/foo/\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/who/are/foo/bar\", \"/foo/bar\", `/who/are/\\*you`, `/\\*you`},\n\t\t{\"/conxxx\", \"xxx\", `/con:tact`, `:tact`},\n\t\t{\"/conooo/xxx\", \"ooo\", `/con:tact`, `:tact`},\n\t}\n\n\tfor i := range conflicts {\n\t\tconflict := conflicts[i]\n\n\t\t// I have to re-create a 'tree', because the 'tree' will be\n\t\t// in an inconsistent state when the loop recovers from the\n\t\t// panic which threw by 'addRoute' function.\n\t\ttree := &node{}\n\t\troutes := [...]string{\n\t\t\t\"/con:tact\",\n\t\t\t\"/who/are/*you\",\n\t\t\t\"/who/foo/hello\",\n\t\t}\n\n\t\tfor i := range routes {\n\t\t\troute := routes[i]\n\t\t\ttree.addRoute(route, fakeHandler(route))\n\t\t}\n\n\t\trecv := catchPanic(func() {\n\t\t\ttree.addRoute(conflict.route, fakeHandler(conflict.route))\n\t\t})\n\n\t\tif !regexp.MustCompile(fmt.Sprintf(\"'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'\", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) {\n\t\t\tt.Fatalf(\"invalid wildcard conflict error (%v)\", recv)\n\t\t}\n\t}\n}\n\nfunc TestRedirectTrailingSlash(t *testing.T) {\n\tvar data = []struct {\n\t\tpath string\n\t}{\n\t\t{\"/hello/:name\"},\n\t\t{\"/hello/:name/123\"},\n\t\t{\"/hello/:name/234\"},\n\t}\n\n\tnode := &node{}\n\tfor _, item := range data {\n\t\tnode.addRoute(item.path, fakeHandler(\"test\"))\n\t}\n\n\t_, _, tsr := node.getValue(\"/hello/abx/\", nil)\n\tif tsr != true {\n\t\tt.Fatalf(\"want true, is false\")\n\t}\n}\n"
  }
]