Initial QSfera import
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user