104 lines
2.2 KiB
Go
104 lines
2.2 KiB
Go
package redirects
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
paramsRe = regexp.MustCompile(`:[\w-]+`)
|
|
)
|
|
|
|
type Redirect struct {
|
|
From *regexp.Regexp
|
|
To string
|
|
StatusCode int
|
|
}
|
|
|
|
type RedirectEngine struct {
|
|
Redirects []Redirect
|
|
}
|
|
|
|
func ParseRedirectPath(from string) (pathRe *regexp.Regexp, err error) {
|
|
from = strings.ReplaceAll(regexp.QuoteMeta(from), "\\*", "(?P<splat>.*?)")
|
|
params := paramsRe.FindAllStringSubmatch(from, -1)
|
|
for _, param := range params {
|
|
from = strings.ReplaceAll(from, regexp.QuoteMeta(param[0]), fmt.Sprintf("(?P<%s>[^/]+?)", param[0][1:]))
|
|
}
|
|
result := fmt.Sprintf("^%s$", from)
|
|
return regexp.Compile(result)
|
|
}
|
|
|
|
func ParseRedirects(body string) (engine *RedirectEngine, err error) {
|
|
engine = &RedirectEngine{
|
|
Redirects: []Redirect{},
|
|
}
|
|
for _, line := range strings.Split(string(body), "\n") {
|
|
redirectArr := strings.Fields(line)
|
|
// Ignore comments and invalid lines
|
|
if strings.HasPrefix(line, "#") || len(redirectArr) < 2 {
|
|
continue
|
|
}
|
|
statusCode := 301
|
|
if len(redirectArr) == 3 {
|
|
statusCode, err = strconv.Atoi(redirectArr[2])
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
fromRe, err := ParseRedirectPath(redirectArr[0])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
engine.Redirects = append(engine.Redirects, Redirect{
|
|
From: fromRe,
|
|
To: redirectArr[1],
|
|
StatusCode: statusCode,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
type RedirectResult struct {
|
|
To string
|
|
StatusCode int
|
|
}
|
|
|
|
func (engine *RedirectEngine) Apply(path string) (result *RedirectResult, ok bool) {
|
|
for _, redirect := range engine.Redirects {
|
|
if redirect.From.MatchString(path) {
|
|
result, ok := redirect.Apply(path)
|
|
if ok {
|
|
return &RedirectResult{
|
|
To: result,
|
|
StatusCode: redirect.StatusCode,
|
|
}, ok
|
|
}
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (re *Redirect) Apply(path string) (result string, ok bool) {
|
|
keys := re.From.SubexpNames()[1:]
|
|
result = re.To
|
|
submatches := re.From.FindStringSubmatch(path)
|
|
if submatches == nil {
|
|
return
|
|
}
|
|
if len(submatches) <= 1 {
|
|
return result, true
|
|
}
|
|
for i, match := range submatches[1:] {
|
|
if i >= len(keys) {
|
|
break
|
|
}
|
|
name := fmt.Sprintf(":%s", keys[i])
|
|
result = strings.ReplaceAll(result, name, match)
|
|
}
|
|
ok = true
|
|
return
|
|
}
|