aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/docker/distribution/registry/api/v2/headerparser.go')
-rw-r--r--vendor/github.com/docker/distribution/registry/api/v2/headerparser.go161
1 files changed, 161 insertions, 0 deletions
diff --git a/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go b/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go
new file mode 100644
index 000000000..9bc41a3a6
--- /dev/null
+++ b/vendor/github.com/docker/distribution/registry/api/v2/headerparser.go
@@ -0,0 +1,161 @@
+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
+}