@@ -6,29 +6,125 @@ import (
6
6
"strings"
7
7
)
8
8
9
- // Rule represents an allow rule with optional HTTP method restrictions
9
+ // Rule represents an allow rule passed to the cli with --allow or read from the config file.
10
+ // Rules have a specific grammar that we need to parse carefully.
11
+ // Example: --allow="method=GET,PATCH domain=wibble.wobble.com, path=/posts/*"
10
12
type Rule struct {
11
13
12
- // The path segments of the url
13
- // nil means all paths allowed
14
- // a path segment of `*` acts as a wild card.
15
- // sub paths automatically match
14
+ // The path segments of the url.
15
+ // - nil means all paths allowed
16
+ // - a path segment of `*` acts as a wild card.
17
+ // - sub paths automatically match
16
18
PathPattern []segmentPattern
17
19
18
- // The labels of the host, i.e. ["google", "com"]
19
- // nil means all hosts allowed
20
- // A label of `*` acts as a wild card.
21
- // subdomains automatically match
20
+ // The labels of the host, i.e. ["google", "com"].
21
+ // - nil means all hosts allowed
22
+ // - A label of `*` acts as a wild card.
23
+ // - subdomains automatically match
22
24
HostPattern []labelPattern
23
25
24
- // The allowed http methods
25
- // nil means all methods allowed
26
+ // The allowed http methods.
27
+ // - nil means all methods allowed
26
28
MethodPatterns map [methodPattern ]struct {}
27
29
28
30
// Raw rule string for logging
29
31
Raw string
30
32
}
31
33
34
+ // ParseAllowSpecs parses a slice of --allow specs into allow Rules.
35
+ func ParseAllowSpecs (allowStrings []string ) ([]Rule , error ) {
36
+ var out []Rule
37
+ for _ , s := range allowStrings {
38
+ r , err := parseAllowRule (s )
39
+ if err != nil {
40
+ return nil , fmt .Errorf ("failed to parse allow '%s': %v" , s , err )
41
+ }
42
+ out = append (out , r )
43
+ }
44
+ return out , nil
45
+ }
46
+
47
+ // parseAllowRule takes an allow rule string and tries to parse it as a rule.
48
+ func parseAllowRule (ruleStr string ) (Rule , error ) {
49
+ rule := Rule {
50
+ Raw : ruleStr ,
51
+ }
52
+
53
+ // Functions called by this function used a really common pattern: recursive descent parsing.
54
+ // All the helper functions for parsing an allow rule will be called like `thing, rest, err := parseThing(rest)`.
55
+ // What's going on here is that we try to parse some expected text from the front of the string.
56
+ // If we succeed, we get back the thing we parsed and the remaining text. If we fail, we get back a non nil error.
57
+ rest := ruleStr
58
+ var key string
59
+ var err error
60
+
61
+ // Ann allow rule can have as many key=value pairs as needed, we go until there's no more text in the rule.
62
+ for rest != "" {
63
+ // Parse the key
64
+ key , rest , err = parseKey (rest )
65
+ if err != nil {
66
+ return Rule {}, fmt .Errorf ("failed to parse key: %v" , err )
67
+ }
68
+
69
+ // Parse the value based on the key type
70
+ switch key {
71
+ case "method" :
72
+ // Initialize Methods map if needed
73
+ if rule .MethodPatterns == nil {
74
+ rule .MethodPatterns = make (map [methodPattern ]struct {})
75
+ }
76
+
77
+ var method methodPattern
78
+ for {
79
+ method , rest , err = parseMethodPattern (rest )
80
+ if err != nil {
81
+ return Rule {}, fmt .Errorf ("failed to parse method: %v" , err )
82
+ }
83
+
84
+ rule .MethodPatterns [method ] = struct {}{}
85
+
86
+ // Check if there's a comma for more methods
87
+ if rest != "" && rest [0 ] == ',' {
88
+ rest = rest [1 :] // Skip the comma
89
+ continue
90
+ }
91
+
92
+ break
93
+ }
94
+
95
+ case "domain" :
96
+ var host []labelPattern
97
+ host , rest , err = parseHostPattern (rest )
98
+ if err != nil {
99
+ return Rule {}, fmt .Errorf ("failed to parse domain: %v" , err )
100
+ }
101
+
102
+ // Convert labels to strings
103
+ rule .HostPattern = append (rule .HostPattern , host ... )
104
+
105
+ case "path" :
106
+ var segments []segmentPattern
107
+ segments , rest , err = parsePathPattern (rest )
108
+ if err != nil {
109
+ return Rule {}, fmt .Errorf ("failed to parse path: %v" , err )
110
+ }
111
+
112
+ // Convert segments to strings
113
+ rule .PathPattern = append (rule .PathPattern , segments ... )
114
+
115
+ default :
116
+ return Rule {}, fmt .Errorf ("unknown key: %s" , key )
117
+ }
118
+
119
+ // Skip whitespace or comma separators
120
+ for rest != "" && (rest [0 ] == ' ' || rest [0 ] == '\t' || rest [0 ] == ',' ) {
121
+ rest = rest [1 :]
122
+ }
123
+ }
124
+
125
+ return rule , nil
126
+ }
127
+
32
128
type methodPattern string
33
129
34
130
// Beyond the 9 methods defined in HTTP 1.1, there actually are many more seldom used extension methods by
@@ -300,92 +396,3 @@ func parseKey(rule string) (string, string, error) {
300
396
301
397
return "" , "" , errors .New ("expected key" )
302
398
}
303
-
304
- func parseAllowRule (ruleStr string ) (Rule , error ) {
305
- rule := Rule {
306
- Raw : ruleStr ,
307
- }
308
-
309
- rest := ruleStr
310
-
311
- for rest != "" {
312
- // Parse the key
313
- key , valueRest , err := parseKey (rest )
314
- if err != nil {
315
- return Rule {}, fmt .Errorf ("failed to parse key: %v" , err )
316
- }
317
-
318
- // Parse the value based on the key type
319
- switch key {
320
- case "method" :
321
- // Handle comma-separated methods
322
- methodsRest := valueRest
323
-
324
- // Initialize Methods map if needed
325
- if rule .MethodPatterns == nil {
326
- rule .MethodPatterns = make (map [methodPattern ]struct {})
327
- }
328
-
329
- for {
330
- token , remaining , err := parseMethodPattern (methodsRest )
331
- if err != nil {
332
- return Rule {}, fmt .Errorf ("failed to parse method: %v" , err )
333
- }
334
-
335
- rule .MethodPatterns [token ] = struct {}{}
336
-
337
- // Check if there's a comma for more methods
338
- if remaining != "" && remaining [0 ] == ',' {
339
- methodsRest = remaining [1 :] // Skip the comma
340
- continue
341
- }
342
-
343
- rest = remaining
344
- break
345
- }
346
-
347
- case "domain" :
348
- hostLabels , remaining , err := parseHostPattern (valueRest )
349
- if err != nil {
350
- return Rule {}, fmt .Errorf ("failed to parse domain: %v" , err )
351
- }
352
-
353
- // Convert labels to strings
354
- rule .HostPattern = append (rule .HostPattern , hostLabels ... )
355
- rest = remaining
356
-
357
- case "path" :
358
- segments , remaining , err := parsePathPattern (valueRest )
359
- if err != nil {
360
- return Rule {}, fmt .Errorf ("failed to parse path: %v" , err )
361
- }
362
-
363
- // Convert segments to strings
364
- rule .PathPattern = append (rule .PathPattern , segments ... )
365
- rest = remaining
366
-
367
- default :
368
- return Rule {}, fmt .Errorf ("unknown key: %s" , key )
369
- }
370
-
371
- // Skip whitespace or comma separators
372
- for rest != "" && (rest [0 ] == ' ' || rest [0 ] == '\t' || rest [0 ] == ',' ) {
373
- rest = rest [1 :]
374
- }
375
- }
376
-
377
- return rule , nil
378
- }
379
-
380
- // ParseAllowSpecs parses a slice of --allow specs into allow Rules.
381
- func ParseAllowSpecs (allowStrings []string ) ([]Rule , error ) {
382
- var out []Rule
383
- for _ , s := range allowStrings {
384
- r , err := parseAllowRule (s )
385
- if err != nil {
386
- return nil , fmt .Errorf ("failed to parse allow '%s': %v" , s , err )
387
- }
388
- out = append (out , r )
389
- }
390
- return out , nil
391
- }
0 commit comments