Skip to content

Commit 3fb4ef0

Browse files
committed
parse path
1 parent 18aa4a4 commit 3fb4ef0

File tree

2 files changed

+462
-15
lines changed

2 files changed

+462
-15
lines changed

rules/rules.go

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,7 @@ func isHTTPTokenChar(c byte) bool {
8585
// Represents a valid host.
8686
// https://datatracker.ietf.org/doc/html/rfc952
8787
// https://datatracker.ietf.org/doc/html/rfc1123#page-13
88-
type host []label
89-
90-
func parseHost(input string) (host host, rest string, err error) {
88+
func parseHost(input string) (host []label, rest string, err error) {
9189
rest = input
9290
var label label
9391

@@ -165,6 +163,129 @@ func isValidLabelChar(c byte) bool {
165163
}
166164
}
167165

166+
func parsePath(input string) ([]segment, string, error) {
167+
if input == "" {
168+
return nil, "", nil
169+
}
170+
171+
var segments []segment
172+
rest := input
173+
174+
// If the path doesn't start with '/', it's not a valid absolute path
175+
// But we'll be flexible and parse relative paths too
176+
for {
177+
// Skip leading slash if present
178+
if rest != "" && rest[0] == '/' {
179+
rest = rest[1:]
180+
}
181+
182+
// If we've consumed all input, we're done
183+
if rest == "" {
184+
break
185+
}
186+
187+
// Parse the next segment
188+
seg, remaining, err := parsePathSegment(rest)
189+
if err != nil {
190+
return nil, "", err
191+
}
192+
193+
// If we got an empty segment and there's still input,
194+
// it means we hit an invalid character
195+
if seg == "" && remaining != "" {
196+
break
197+
}
198+
199+
segments = append(segments, seg)
200+
rest = remaining
201+
202+
// If there's no slash after the segment, we're done parsing the path
203+
if rest == "" || rest[0] != '/' {
204+
break
205+
}
206+
}
207+
208+
return segments, rest, nil
209+
}
210+
211+
// Represents a valid url path segment.
212+
type segment string
213+
214+
func parsePathSegment(input string) (segment, string, error) {
215+
if input == "" {
216+
return "", "", nil
217+
}
218+
219+
var i int
220+
for i = 0; i < len(input); i++ {
221+
c := input[i]
222+
223+
// Check for percent-encoded characters (%XX)
224+
if c == '%' {
225+
if i+2 >= len(input) || !isHexDigit(input[i+1]) || !isHexDigit(input[i+2]) {
226+
break
227+
}
228+
i += 2
229+
continue
230+
}
231+
232+
// Check for valid pchar characters
233+
if !isPChar(c) {
234+
break
235+
}
236+
}
237+
238+
return segment(input[:i]), input[i:], nil
239+
}
240+
241+
// isUnreserved returns true if the character is unreserved per RFC 3986
242+
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
243+
func isUnreserved(c byte) bool {
244+
return (c >= 'A' && c <= 'Z') ||
245+
(c >= 'a' && c <= 'z') ||
246+
(c >= '0' && c <= '9') ||
247+
c == '-' || c == '.' || c == '_' || c == '~'
248+
}
249+
250+
// isSubDelim returns true if the character is a sub-delimiter per RFC 3986
251+
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
252+
func isSubDelim(c byte) bool {
253+
return c == '!' || c == '$' || c == '&' || c == '\'' ||
254+
c == '(' || c == ')' || c == '*' || c == '+' ||
255+
c == ',' || c == ';' || c == '='
256+
}
257+
258+
// isPChar returns true if the character is valid in a path segment (excluding percent-encoded)
259+
// pchar = unreserved / sub-delims / ":" / "@"
260+
func isPChar(c byte) bool {
261+
return isUnreserved(c) || isSubDelim(c) || c == ':' || c == '@'
262+
}
263+
264+
// isHexDigit returns true if the character is a hexadecimal digit
265+
func isHexDigit(c byte) bool {
266+
return (c >= '0' && c <= '9') ||
267+
(c >= 'A' && c <= 'F') ||
268+
(c >= 'a' && c <= 'f')
269+
}
270+
271+
// parseKey parses the predefined keys that the cli can handle. Also strips the `=` following the key.
272+
func parseKey(rule string) (string, string, error) {
273+
if rule == "" {
274+
return "", "", errors.New("expected key")
275+
}
276+
277+
// These are the current keys we support.
278+
keys := []string{"method", "domain", "path"}
279+
280+
for _, key := range keys {
281+
if rest, found := strings.CutPrefix(rule, key+"="); found {
282+
return key, rest, nil
283+
}
284+
}
285+
286+
return "", "", errors.New("expected key")
287+
}
288+
168289
func parseAllowRule(string) (Rule, error) {
169290
return Rule{}, nil
170291
}

0 commit comments

Comments
 (0)