@@ -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+
168289func parseAllowRule (string ) (Rule , error ) {
169290 return Rule {}, nil
170291}
0 commit comments