...

Source file src/github.com/docker/distribution/registry/api/v2/headerparser.go

Documentation: github.com/docker/distribution/registry/api/v2

     1  package v2
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  	"unicode"
     8  )
     9  
    10  var (
    11  	// according to rfc7230
    12  	reToken            = regexp.MustCompile(`^[^"(),/:;<=>?@[\]{}[:space:][:cntrl:]]+`)
    13  	reQuotedValue      = regexp.MustCompile(`^[^\\"]+`)
    14  	reEscapedCharacter = regexp.MustCompile(`^[[:blank:][:graph:]]`)
    15  )
    16  
    17  // parseForwardedHeader is a benevolent parser of Forwarded header defined in rfc7239. The header contains
    18  // a comma-separated list of forwarding key-value pairs. Each list element is set by single proxy. The
    19  // function parses only the first element of the list, which is set by the very first proxy. It returns a map
    20  // of corresponding key-value pairs and an unparsed slice of the input string.
    21  //
    22  // Examples of Forwarded header values:
    23  //
    24  //  1. Forwarded: For=192.0.2.43; Proto=https,For="[2001:db8:cafe::17]",For=unknown
    25  //  2. Forwarded: for="192.0.2.43:443"; host="registry.example.org", for="10.10.05.40:80"
    26  //
    27  // The first will be parsed into {"for": "192.0.2.43", "proto": "https"} while the second into
    28  // {"for": "192.0.2.43:443", "host": "registry.example.org"}.
    29  func parseForwardedHeader(forwarded string) (map[string]string, string, error) {
    30  	// Following are states of forwarded header parser. Any state could transition to a failure.
    31  	const (
    32  		// terminating state; can transition to Parameter
    33  		stateElement = iota
    34  		// terminating state; can transition to KeyValueDelimiter
    35  		stateParameter
    36  		// can transition to Value
    37  		stateKeyValueDelimiter
    38  		// can transition to one of { QuotedValue, PairEnd }
    39  		stateValue
    40  		// can transition to one of { EscapedCharacter, PairEnd }
    41  		stateQuotedValue
    42  		// can transition to one of { QuotedValue }
    43  		stateEscapedCharacter
    44  		// terminating state; can transition to one of { Parameter, Element }
    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  		// skip spaces unless in quoted value
    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  			// terminating
    68  			break
    69  		}
    70  
    71  		switch state {
    72  		// terminate at list element delimiter
    73  		case stateElement:
    74  			if parse[0] == ',' {
    75  				parse = parse[1:]
    76  				break Loop
    77  			}
    78  			state = stateParameter
    79  
    80  		// parse parameter (the key of key-value pair)
    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  		// parse '='
    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  		// parse value or quoted value
    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  		// parse a part of quoted value until the first backslash
   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  		// parse escaped character in a quoted string, ignore the backslash
   136  		// transition back to QuotedValue state
   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  		// expect either a new key-value pair, new list or end of input
   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