package redirect import ( "bytes" "fmt" "io" "net/http" "net/url" "regexp" "strings" "text/template" "github.com/containous/traefik/configuration" "github.com/urfave/negroni" "github.com/vulcand/oxy/utils" ) const ( defaultRedirectRegex = `^(?:https?:\/\/)?([\w\._-]+)(?::\d+)?(.*)$` ) // NewEntryPointHandler create a new redirection handler base on entry point func NewEntryPointHandler(dstEntryPoint *configuration.EntryPoint, permanent bool) (negroni.Handler, error) { exp := regexp.MustCompile(`(:\d+)`) match := exp.FindStringSubmatch(dstEntryPoint.Address) if len(match) == 0 { return nil, fmt.Errorf("bad Address format %q", dstEntryPoint.Address) } protocol := "http" if dstEntryPoint.TLS != nil { protocol = "https" } replacement := protocol + "://$1" + match[0] + "$2" return NewRegexHandler(defaultRedirectRegex, replacement, permanent) } // NewRegexHandler create a new redirection handler base on regex func NewRegexHandler(exp string, replacement string, permanent bool) (negroni.Handler, error) { re, err := regexp.Compile(exp) if err != nil { return nil, err } return &handler{ regexp: re, replacement: replacement, permanent: permanent, errHandler: utils.DefaultHandler, }, nil } type handler struct { regexp *regexp.Regexp replacement string permanent bool errHandler utils.ErrorHandler } func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) { oldURL := rawURL(req) // only continue if the Regexp param matches the URL if !h.regexp.MatchString(oldURL) { next.ServeHTTP(rw, req) return } // apply a rewrite regexp to the URL newURL := h.regexp.ReplaceAllString(oldURL, h.replacement) // replace any variables that may be in there rewrittenURL := &bytes.Buffer{} if err := applyString(newURL, rewrittenURL, req); err != nil { h.errHandler.ServeHTTP(rw, req, err) return } // parse the rewritten URL and replace request URL with it parsedURL, err := url.Parse(rewrittenURL.String()) if err != nil { h.errHandler.ServeHTTP(rw, req, err) return } if newURL != oldURL { handler := &moveHandler{location: parsedURL, permanent: h.permanent} handler.ServeHTTP(rw, req) return } req.URL = parsedURL // make sure the request URI corresponds the rewritten URL req.RequestURI = req.URL.RequestURI() next.ServeHTTP(rw, req) } type moveHandler struct { location *url.URL permanent bool } func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Location", m.location.String()) status := http.StatusFound if m.permanent { status = http.StatusMovedPermanently } rw.WriteHeader(status) rw.Write([]byte(http.StatusText(status))) } func rawURL(request *http.Request) string { scheme := "http" if request.TLS != nil || isXForwardedHTTPS(request) { scheme = "https" } return strings.Join([]string{scheme, "://", request.Host, request.RequestURI}, "") } func isXForwardedHTTPS(request *http.Request) bool { xForwardedProto := request.Header.Get("X-Forwarded-Proto") return len(xForwardedProto) > 0 && xForwardedProto == "https" } func applyString(in string, out io.Writer, request *http.Request) error { t, err := template.New("t").Parse(in) if err != nil { return err } data := struct { Request *http.Request }{ Request: request, } return t.Execute(out, data) }