
attempt to remove glide from integration glide trim Revert "attempt to remove glide from integration" This reverts commit c5b42b6cdebb44e730080a0cf20a871c11ef095b.
161 lines
4.7 KiB
Go
161 lines
4.7 KiB
Go
package v2
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
var (
|
|
// according to rfc7230
|
|
reToken = regexp.MustCompile(`^[^"(),/:;<=>?@[\]{}[:space:][:cntrl:]]+`)
|
|
reQuotedValue = regexp.MustCompile(`^[^\\"]+`)
|
|
reEscapedCharacter = regexp.MustCompile(`^[[:blank:][:graph:]]`)
|
|
)
|
|
|
|
// parseForwardedHeader is a benevolent parser of Forwarded header defined in rfc7239. The header contains
|
|
// a comma-separated list of forwarding key-value pairs. Each list element is set by single proxy. The
|
|
// function parses only the first element of the list, which is set by the very first proxy. It returns a map
|
|
// of corresponding key-value pairs and an unparsed slice of the input string.
|
|
//
|
|
// Examples of Forwarded header values:
|
|
//
|
|
// 1. Forwarded: For=192.0.2.43; Proto=https,For="[2001:db8:cafe::17]",For=unknown
|
|
// 2. Forwarded: for="192.0.2.43:443"; host="registry.example.org", for="10.10.05.40:80"
|
|
//
|
|
// The first will be parsed into {"for": "192.0.2.43", "proto": "https"} while the second into
|
|
// {"for": "192.0.2.43:443", "host": "registry.example.org"}.
|
|
func parseForwardedHeader(forwarded string) (map[string]string, string, error) {
|
|
// Following are states of forwarded header parser. Any state could transition to a failure.
|
|
const (
|
|
// terminating state; can transition to Parameter
|
|
stateElement = iota
|
|
// terminating state; can transition to KeyValueDelimiter
|
|
stateParameter
|
|
// can transition to Value
|
|
stateKeyValueDelimiter
|
|
// can transition to one of { QuotedValue, PairEnd }
|
|
stateValue
|
|
// can transition to one of { EscapedCharacter, PairEnd }
|
|
stateQuotedValue
|
|
// can transition to one of { QuotedValue }
|
|
stateEscapedCharacter
|
|
// terminating state; can transition to one of { Parameter, Element }
|
|
statePairEnd
|
|
)
|
|
|
|
var (
|
|
parameter string
|
|
value string
|
|
parse = forwarded[:]
|
|
res = map[string]string{}
|
|
state = stateElement
|
|
)
|
|
|
|
Loop:
|
|
for {
|
|
// skip spaces unless in quoted value
|
|
if state != stateQuotedValue && state != stateEscapedCharacter {
|
|
parse = strings.TrimLeftFunc(parse, unicode.IsSpace)
|
|
}
|
|
|
|
if len(parse) == 0 {
|
|
if state != stateElement && state != statePairEnd && state != stateParameter {
|
|
return nil, parse, fmt.Errorf("unexpected end of input")
|
|
}
|
|
// terminating
|
|
break
|
|
}
|
|
|
|
switch state {
|
|
// terminate at list element delimiter
|
|
case stateElement:
|
|
if parse[0] == ',' {
|
|
parse = parse[1:]
|
|
break Loop
|
|
}
|
|
state = stateParameter
|
|
|
|
// parse parameter (the key of key-value pair)
|
|
case stateParameter:
|
|
match := reToken.FindString(parse)
|
|
if len(match) == 0 {
|
|
return nil, parse, fmt.Errorf("failed to parse token at position %d", len(forwarded)-len(parse))
|
|
}
|
|
parameter = strings.ToLower(match)
|
|
parse = parse[len(match):]
|
|
state = stateKeyValueDelimiter
|
|
|
|
// parse '='
|
|
case stateKeyValueDelimiter:
|
|
if parse[0] != '=' {
|
|
return nil, parse, fmt.Errorf("expected '=', not '%c' at position %d", parse[0], len(forwarded)-len(parse))
|
|
}
|
|
parse = parse[1:]
|
|
state = stateValue
|
|
|
|
// parse value or quoted value
|
|
case stateValue:
|
|
if parse[0] == '"' {
|
|
parse = parse[1:]
|
|
state = stateQuotedValue
|
|
} else {
|
|
value = reToken.FindString(parse)
|
|
if len(value) == 0 {
|
|
return nil, parse, fmt.Errorf("failed to parse value at position %d", len(forwarded)-len(parse))
|
|
}
|
|
if _, exists := res[parameter]; exists {
|
|
return nil, parse, fmt.Errorf("duplicate parameter %q at position %d", parameter, len(forwarded)-len(parse))
|
|
}
|
|
res[parameter] = value
|
|
parse = parse[len(value):]
|
|
value = ""
|
|
state = statePairEnd
|
|
}
|
|
|
|
// parse a part of quoted value until the first backslash
|
|
case stateQuotedValue:
|
|
match := reQuotedValue.FindString(parse)
|
|
value += match
|
|
parse = parse[len(match):]
|
|
switch {
|
|
case len(parse) == 0:
|
|
return nil, parse, fmt.Errorf("unterminated quoted string")
|
|
case parse[0] == '"':
|
|
res[parameter] = value
|
|
value = ""
|
|
parse = parse[1:]
|
|
state = statePairEnd
|
|
case parse[0] == '\\':
|
|
parse = parse[1:]
|
|
state = stateEscapedCharacter
|
|
}
|
|
|
|
// parse escaped character in a quoted string, ignore the backslash
|
|
// transition back to QuotedValue state
|
|
case stateEscapedCharacter:
|
|
c := reEscapedCharacter.FindString(parse)
|
|
if len(c) == 0 {
|
|
return nil, parse, fmt.Errorf("invalid escape sequence at position %d", len(forwarded)-len(parse)-1)
|
|
}
|
|
value += c
|
|
parse = parse[1:]
|
|
state = stateQuotedValue
|
|
|
|
// expect either a new key-value pair, new list or end of input
|
|
case statePairEnd:
|
|
switch parse[0] {
|
|
case ';':
|
|
parse = parse[1:]
|
|
state = stateParameter
|
|
case ',':
|
|
state = stateElement
|
|
default:
|
|
return nil, parse, fmt.Errorf("expected ',' or ';', not %c at position %d", parse[0], len(forwarded)-len(parse))
|
|
}
|
|
}
|
|
}
|
|
|
|
return res, parse, nil
|
|
}
|