...
1 package v2
2
3 import (
4 "fmt"
5 "regexp"
6 "strings"
7 "unicode"
8 )
9
10 var (
11
12 reToken = regexp.MustCompile(`^[^"(),/:;<=>?@[\]{}[:space:][:cntrl:]]+`)
13 reQuotedValue = regexp.MustCompile(`^[^\\"]+`)
14 reEscapedCharacter = regexp.MustCompile(`^[[:blank:][:graph:]]`)
15 )
16
17
18
19
20
21
22
23
24
25
26
27
28
29 func parseForwardedHeader(forwarded string) (map[string]string, string, error) {
30
31 const (
32
33 stateElement = iota
34
35 stateParameter
36
37 stateKeyValueDelimiter
38
39 stateValue
40
41 stateQuotedValue
42
43 stateEscapedCharacter
44
45 statePairEnd
46 )
47
48 var (
49 parameter string
50 value string
51 parse = forwarded[:]
52 res = map[string]string{}
53 state = stateElement
54 )
55
56 Loop:
57 for {
58
59 if state != stateQuotedValue && state != stateEscapedCharacter {
60 parse = strings.TrimLeftFunc(parse, unicode.IsSpace)
61 }
62
63 if len(parse) == 0 {
64 if state != stateElement && state != statePairEnd && state != stateParameter {
65 return nil, parse, fmt.Errorf("unexpected end of input")
66 }
67
68 break
69 }
70
71 switch state {
72
73 case stateElement:
74 if parse[0] == ',' {
75 parse = parse[1:]
76 break Loop
77 }
78 state = stateParameter
79
80
81 case stateParameter:
82 match := reToken.FindString(parse)
83 if len(match) == 0 {
84 return nil, parse, fmt.Errorf("failed to parse token at position %d", len(forwarded)-len(parse))
85 }
86 parameter = strings.ToLower(match)
87 parse = parse[len(match):]
88 state = stateKeyValueDelimiter
89
90
91 case stateKeyValueDelimiter:
92 if parse[0] != '=' {
93 return nil, parse, fmt.Errorf("expected '=', not '%c' at position %d", parse[0], len(forwarded)-len(parse))
94 }
95 parse = parse[1:]
96 state = stateValue
97
98
99 case stateValue:
100 if parse[0] == '"' {
101 parse = parse[1:]
102 state = stateQuotedValue
103 } else {
104 value = reToken.FindString(parse)
105 if len(value) == 0 {
106 return nil, parse, fmt.Errorf("failed to parse value at position %d", len(forwarded)-len(parse))
107 }
108 if _, exists := res[parameter]; exists {
109 return nil, parse, fmt.Errorf("duplicate parameter %q at position %d", parameter, len(forwarded)-len(parse))
110 }
111 res[parameter] = value
112 parse = parse[len(value):]
113 value = ""
114 state = statePairEnd
115 }
116
117
118 case stateQuotedValue:
119 match := reQuotedValue.FindString(parse)
120 value += match
121 parse = parse[len(match):]
122 switch {
123 case len(parse) == 0:
124 return nil, parse, fmt.Errorf("unterminated quoted string")
125 case parse[0] == '"':
126 res[parameter] = value
127 value = ""
128 parse = parse[1:]
129 state = statePairEnd
130 case parse[0] == '\\':
131 parse = parse[1:]
132 state = stateEscapedCharacter
133 }
134
135
136
137 case stateEscapedCharacter:
138 c := reEscapedCharacter.FindString(parse)
139 if len(c) == 0 {
140 return nil, parse, fmt.Errorf("invalid escape sequence at position %d", len(forwarded)-len(parse)-1)
141 }
142 value += c
143 parse = parse[1:]
144 state = stateQuotedValue
145
146
147 case statePairEnd:
148 switch parse[0] {
149 case ';':
150 parse = parse[1:]
151 state = stateParameter
152 case ',':
153 state = stateElement
154 default:
155 return nil, parse, fmt.Errorf("expected ',' or ';', not %c at position %d", parse[0], len(forwarded)-len(parse))
156 }
157 }
158 }
159
160 return res, parse, nil
161 }
162
View as plain text