-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredirect.go
146 lines (127 loc) · 3.16 KB
/
redirect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package pgs
import (
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
)
type RedirectRule struct {
From string
To string
Status int
Query map[string]string
Conditions map[string]string
Force bool
Signed bool
}
var reSplitWhitespace = regexp.MustCompile(`\s+`)
func isUrl(text string) bool {
return strings.HasPrefix(text, "http://") || strings.HasPrefix(text, "https://")
}
func isToPart(part string) bool {
return strings.HasPrefix(part, "/") || isUrl(part)
}
func hasStatusCode(part string) (int, bool) {
status := 0
forced := false
pt := part
if strings.HasSuffix(part, "!") {
pt = strings.TrimSuffix(part, "!")
forced = true
}
status, err := strconv.Atoi(pt)
if err != nil {
return 0, forced
}
return status, forced
}
func parsePairs(pairs []string) map[string]string {
mapper := map[string]string{}
for _, pair := range pairs {
val := strings.SplitN(pair, "=", 1)
if len(val) > 1 {
mapper[val[0]] = val[1]
}
}
return mapper
}
/*
https://github.com/netlify/build/blob/main/packages/redirect-parser/src/line_parser.js#L9-L26
Parse `_redirects` file to an array of objects.
Each line in that file must be either:
- An empty line
- A comment starting with #
- A redirect line, optionally ended with a comment
Each redirect line has the following format:
from [query] [to] [status[!]] [conditions]
The parts are:
- "from": a path or a URL
- "query": a whitespace-separated list of "key=value"
- "to": a path or a URL
- "status": an HTTP status integer
- "!": an optional exclamation mark appended to "status" meant to indicate
"forced"
- "conditions": a whitespace-separated list of "key=value"
- "Sign" is a special condition
*/
func parseRedirectText(text string) ([]*RedirectRule, error) {
rules := []*RedirectRule{}
origLines := strings.Split(text, "\n")
for _, line := range origLines {
trimmed := strings.TrimSpace(line)
// ignore empty lines
if trimmed == "" {
continue
}
// ignore comments
if strings.HasPrefix(trimmed, "#") {
continue
}
parts := reSplitWhitespace.Split(trimmed, -1)
if len(parts) < 2 {
return rules, fmt.Errorf("missing destination path/URL")
}
from := parts[0]
rest := parts[1:]
status, forced := hasStatusCode(rest[0])
if status != 0 {
rules = append(rules, &RedirectRule{
Query: map[string]string{},
Status: status,
Force: forced,
})
} else {
toIndex := -1
for idx, part := range rest {
if isToPart(part) {
toIndex = idx
}
}
if toIndex == -1 {
return rules, fmt.Errorf("the destination path/URL must start with '/', 'http:' or 'https:'")
}
queryParts := rest[:toIndex]
to := rest[toIndex]
lastParts := rest[toIndex+1:]
conditions := map[string]string{}
sts := http.StatusMovedPermanently
frcd := false
if len(lastParts) > 0 {
sts, frcd = hasStatusCode(lastParts[0])
}
if len(lastParts) > 1 {
conditions = parsePairs(lastParts[1:])
}
rules = append(rules, &RedirectRule{
To: to,
From: from,
Status: sts,
Force: frcd,
Query: parsePairs(queryParts),
Conditions: conditions,
})
}
}
return rules, nil
}