2018-01-31 19:10:04 +01:00
package redirect
import (
"bytes"
2018-11-14 10:18:03 +01:00
"context"
"html/template"
2018-01-31 19:10:04 +01:00
"io"
"net/http"
"net/url"
"regexp"
"strings"
2019-08-03 03:58:23 +02:00
"github.com/containous/traefik/v2/pkg/tracing"
2018-11-14 10:18:03 +01:00
"github.com/opentracing/opentracing-go/ext"
2018-01-31 19:10:04 +01:00
"github.com/vulcand/oxy/utils"
)
2018-11-14 10:18:03 +01:00
type redirect struct {
next http . Handler
regex * regexp . Regexp
replacement string
permanent bool
errHandler utils . ErrorHandler
name string
2018-01-31 19:10:04 +01:00
}
2019-01-21 23:30:04 -08:00
// New creates a Redirect middleware.
2019-03-04 16:40:05 +01:00
func newRedirect ( _ context . Context , next http . Handler , regex string , replacement string , permanent bool , name string ) ( http . Handler , error ) {
2019-01-21 23:30:04 -08:00
re , err := regexp . Compile ( regex )
2018-01-31 19:10:04 +01:00
if err != nil {
return nil , err
}
2018-11-14 10:18:03 +01:00
return & redirect {
regex : re ,
2019-01-21 23:30:04 -08:00
replacement : replacement ,
permanent : permanent ,
2018-01-31 19:10:04 +01:00
errHandler : utils . DefaultHandler ,
2018-11-14 10:18:03 +01:00
next : next ,
name : name ,
2018-01-31 19:10:04 +01:00
} , nil
}
2018-11-14 10:18:03 +01:00
func ( r * redirect ) GetTracingInformation ( ) ( string , ext . SpanKindEnum ) {
return r . name , tracing . SpanKindNoneEnum
2018-01-31 19:10:04 +01:00
}
2018-11-14 10:18:03 +01:00
func ( r * redirect ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2018-01-31 19:10:04 +01:00
oldURL := rawURL ( req )
2018-11-14 10:18:03 +01:00
// If the Regexp doesn't match, skip to the next handler
if ! r . regex . MatchString ( oldURL ) {
r . next . ServeHTTP ( rw , req )
2018-01-31 19:10:04 +01:00
return
}
// apply a rewrite regexp to the URL
2018-11-14 10:18:03 +01:00
newURL := r . regex . ReplaceAllString ( oldURL , r . replacement )
2018-01-31 19:10:04 +01:00
// replace any variables that may be in there
rewrittenURL := & bytes . Buffer { }
if err := applyString ( newURL , rewrittenURL , req ) ; err != nil {
2018-11-14 10:18:03 +01:00
r . errHandler . ServeHTTP ( rw , req , err )
2018-01-31 19:10:04 +01:00
return
}
// parse the rewritten URL and replace request URL with it
parsedURL , err := url . Parse ( rewrittenURL . String ( ) )
if err != nil {
2018-11-14 10:18:03 +01:00
r . errHandler . ServeHTTP ( rw , req , err )
2018-01-31 19:10:04 +01:00
return
}
if newURL != oldURL {
2018-11-14 10:18:03 +01:00
handler := & moveHandler { location : parsedURL , permanent : r . permanent }
2018-01-31 19:10:04 +01:00
handler . ServeHTTP ( rw , req )
return
}
req . URL = parsedURL
// make sure the request URI corresponds the rewritten URL
req . RequestURI = req . URL . RequestURI ( )
2018-11-14 10:18:03 +01:00
r . next . ServeHTTP ( rw , req )
2018-01-31 19:10:04 +01:00
}
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 ( ) )
2018-11-14 10:18:03 +01:00
2018-01-31 19:10:04 +01:00
status := http . StatusFound
2019-01-07 17:56:04 +01:00
if req . Method != http . MethodGet {
status = http . StatusTemporaryRedirect
}
2018-01-31 19:10:04 +01:00
if m . permanent {
status = http . StatusMovedPermanently
2019-01-07 17:56:04 +01:00
if req . Method != http . MethodGet {
status = http . StatusPermanentRedirect
}
2018-01-31 19:10:04 +01:00
}
rw . WriteHeader ( status )
2018-11-14 10:18:03 +01:00
_ , err := rw . Write ( [ ] byte ( http . StatusText ( status ) ) )
if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
}
2018-01-31 19:10:04 +01:00
}
2018-11-14 10:18:03 +01:00
func rawURL ( req * http . Request ) string {
2018-01-31 19:10:04 +01:00
scheme := "http"
2019-01-21 23:30:04 -08:00
host := req . Host
port := ""
uri := req . RequestURI
schemeRegex := ` ^(https?):\/\/([\w\._-]+)(:\d+)?(.*)$ `
re , _ := regexp . Compile ( schemeRegex )
if re . Match ( [ ] byte ( req . RequestURI ) ) {
match := re . FindStringSubmatch ( req . RequestURI )
scheme = match [ 1 ]
if len ( match [ 2 ] ) > 0 {
host = match [ 2 ]
}
if len ( match [ 3 ] ) > 0 {
port = match [ 3 ]
}
uri = match [ 4 ]
}
2018-11-14 10:18:03 +01:00
if req . TLS != nil || isXForwardedHTTPS ( req ) {
2018-01-31 19:10:04 +01:00
scheme = "https"
}
2019-01-21 23:30:04 -08:00
return strings . Join ( [ ] string { scheme , "://" , host , port , uri } , "" )
2018-01-31 19:10:04 +01:00
}
func isXForwardedHTTPS ( request * http . Request ) bool {
xForwardedProto := request . Header . Get ( "X-Forwarded-Proto" )
return len ( xForwardedProto ) > 0 && xForwardedProto == "https"
}
2018-11-14 10:18:03 +01:00
func applyString ( in string , out io . Writer , req * http . Request ) error {
2018-01-31 19:10:04 +01:00
t , err := template . New ( "t" ) . Parse ( in )
if err != nil {
return err
}
2018-11-14 10:18:03 +01:00
data := struct { Request * http . Request } { Request : req }
2018-01-31 19:10:04 +01:00
return t . Execute ( out , data )
}