pages-server/redirects/redirects.go
2024-03-26 13:41:12 -05:00

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
}