194 lines
5.3 KiB
Go
194 lines
5.3 KiB
Go
// Copyright 2011 Andy Balholm. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package icap
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
// ServeMux is an ICAP request multiplexer.
|
|
// It matches the URL of each incoming request against a list of registered
|
|
// patterns and calls the handler for the pattern that
|
|
// most closely matches the URL.
|
|
//
|
|
// For more details, see the documentation for http.ServeMux
|
|
type ServeMux struct {
|
|
m map[string]Handler
|
|
}
|
|
|
|
// NewServeMux allocates and returns a new ServeMux.
|
|
func NewServeMux() *ServeMux { return &ServeMux{make(map[string]Handler)} }
|
|
|
|
// DefaultServeMux is the default ServeMux used by Serve.
|
|
var DefaultServeMux = NewServeMux()
|
|
|
|
// Does path match pattern?
|
|
func pathMatch(pattern, path string) bool {
|
|
if len(pattern) == 0 {
|
|
// should not happen
|
|
return false
|
|
}
|
|
n := len(pattern)
|
|
if pattern[n-1] != '/' {
|
|
return pattern == path
|
|
}
|
|
return len(path) >= n && path[0:n] == pattern
|
|
}
|
|
|
|
// Return the canonical path for p, eliminating . and .. elements.
|
|
func cleanPath(p string) string {
|
|
if p == "" {
|
|
return "/"
|
|
}
|
|
if p[0] != '/' {
|
|
p = "/" + p
|
|
}
|
|
np := path.Clean(p)
|
|
// path.Clean removes trailing slash except for root;
|
|
// put the trailing slash back if necessary.
|
|
if p[len(p)-1] == '/' && np != "/" {
|
|
np += "/"
|
|
}
|
|
return np
|
|
}
|
|
|
|
// Find a handler on a handler map given a path string
|
|
// Most-specific (longest) pattern wins
|
|
func (mux *ServeMux) match(path string) Handler {
|
|
var h Handler
|
|
var n = 0
|
|
for k, v := range mux.m {
|
|
if !pathMatch(k, path) {
|
|
continue
|
|
}
|
|
if h == nil || len(k) > n {
|
|
n = len(k)
|
|
h = v
|
|
}
|
|
}
|
|
return h
|
|
}
|
|
|
|
// ServeICAP dispatches the request to the handler whose
|
|
// pattern most closely matches the request URL.
|
|
func (mux *ServeMux) ServeICAP(w ResponseWriter, r *Request) {
|
|
// Clean path to canonical form and redirect.
|
|
if p := cleanPath(r.URL.Path); p != r.URL.Path {
|
|
w.Header().Set("Location", p)
|
|
w.WriteHeader(http.StatusMovedPermanently, nil, false)
|
|
return
|
|
}
|
|
// Host-specific pattern takes precedence over generic ones
|
|
h := mux.match(r.URL.Host + r.URL.Path)
|
|
if h == nil {
|
|
h = mux.match(r.URL.Path)
|
|
}
|
|
if h == nil {
|
|
h = NotFoundHandler()
|
|
}
|
|
h.ServeICAP(w, r)
|
|
}
|
|
|
|
// Handle registers the handler for the given pattern.
|
|
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
|
if pattern == "" {
|
|
panic("icap: invalid pattern " + pattern)
|
|
}
|
|
|
|
mux.m[pattern] = handler
|
|
|
|
// Helpful behavior:
|
|
// If pattern is /tree/, insert permanent redirect for /tree.
|
|
n := len(pattern)
|
|
if n > 0 && pattern[n-1] == '/' {
|
|
mux.m[pattern[0:n-1]] = RedirectHandler(pattern, http.StatusMovedPermanently)
|
|
}
|
|
}
|
|
|
|
// HandleFunc registers the handler function for the given pattern.
|
|
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
|
mux.Handle(pattern, HandlerFunc(handler))
|
|
}
|
|
|
|
// Handle registers the handler for the given pattern
|
|
// in the DefaultServeMux.
|
|
// The documentation for ServeMux explains how patterns are matched.
|
|
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
|
|
|
|
// HandleFunc registers the handler function for the given pattern
|
|
// in the DefaultServeMux.
|
|
// The documentation for ServeMux explains how patterns are matched.
|
|
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
|
DefaultServeMux.HandleFunc(pattern, handler)
|
|
}
|
|
|
|
// NotFound replies to the request with an HTTP 404 not found error.
|
|
func NotFound(w ResponseWriter, r *Request) {
|
|
w.WriteHeader(http.StatusNotFound, nil, false)
|
|
}
|
|
|
|
// NotFoundHandler returns a simple request handler
|
|
// that replies to each request with a ``404 page not found'' reply.
|
|
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
|
|
|
|
// Redirect to a fixed URL
|
|
type redirectHandler struct {
|
|
url string
|
|
code int
|
|
}
|
|
|
|
func (rh *redirectHandler) ServeICAP(w ResponseWriter, r *Request) {
|
|
Redirect(w, r, rh.url, rh.code)
|
|
}
|
|
|
|
// RedirectHandler returns a request handler that redirects
|
|
// each request it receives to the given url using the given
|
|
// status code.
|
|
func RedirectHandler(redirectURL string, code int) Handler {
|
|
return &redirectHandler{redirectURL, code}
|
|
}
|
|
|
|
// Redirect replies to the request with a redirect to url,
|
|
// which may be a path relative to the request path.
|
|
func Redirect(w ResponseWriter, r *Request, redirectURL string, code int) {
|
|
if u, err := url.Parse(redirectURL); err == nil {
|
|
// If url was relative, make absolute by
|
|
// combining with request path.
|
|
// The browser would probably do this for us,
|
|
// but doing it ourselves is more reliable.
|
|
oldpath := r.URL.Path
|
|
if oldpath == "" { // should not happen, but avoid a crash if it does
|
|
oldpath = "/"
|
|
}
|
|
if u.Scheme == "" {
|
|
// no leading icap://server
|
|
if redirectURL == "" || redirectURL[0] != '/' {
|
|
// make relative path absolute
|
|
olddir, _ := path.Split(oldpath)
|
|
redirectURL = olddir + redirectURL
|
|
}
|
|
|
|
var query string
|
|
if i := strings.Index(redirectURL, "?"); i != -1 {
|
|
redirectURL, query = redirectURL[:i], redirectURL[i:]
|
|
}
|
|
|
|
// clean up but preserve trailing slash
|
|
trailing := redirectURL[len(redirectURL)-1] == '/'
|
|
redirectURL = path.Clean(redirectURL)
|
|
if trailing && redirectURL[len(redirectURL)-1] != '/' {
|
|
redirectURL += "/"
|
|
}
|
|
redirectURL += query
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Location", redirectURL)
|
|
w.WriteHeader(code, nil, false)
|
|
}
|