github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/remotes/docker/auth/parse.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package auth
    18  
    19  import (
    20  	"net/http"
    21  	"sort"
    22  	"strings"
    23  )
    24  
    25  // AuthenticationScheme defines scheme of the authentication method
    26  type AuthenticationScheme byte
    27  
    28  const (
    29  	// BasicAuth is scheme for Basic HTTP Authentication RFC 7617
    30  	BasicAuth AuthenticationScheme = 1 << iota
    31  	// DigestAuth is scheme for HTTP Digest Access Authentication RFC 7616
    32  	DigestAuth
    33  	// BearerAuth is scheme for OAuth 2.0 Bearer Tokens RFC 6750
    34  	BearerAuth
    35  )
    36  
    37  // Challenge carries information from a WWW-Authenticate response header.
    38  // See RFC 2617.
    39  type Challenge struct {
    40  	// scheme is the auth-scheme according to RFC 2617
    41  	Scheme AuthenticationScheme
    42  
    43  	// parameters are the auth-params according to RFC 2617
    44  	Parameters map[string]string
    45  }
    46  
    47  type byScheme []Challenge
    48  
    49  func (bs byScheme) Len() int      { return len(bs) }
    50  func (bs byScheme) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
    51  
    52  // Sort in priority order: token > digest > basic
    53  func (bs byScheme) Less(i, j int) bool { return bs[i].Scheme > bs[j].Scheme }
    54  
    55  // Octet types from RFC 2616.
    56  type octetType byte
    57  
    58  var octetTypes [256]octetType
    59  
    60  const (
    61  	isToken octetType = 1 << iota
    62  	isSpace
    63  )
    64  
    65  func init() {
    66  	// OCTET      = <any 8-bit sequence of data>
    67  	// CHAR       = <any US-ASCII character (octets 0 - 127)>
    68  	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
    69  	// CR         = <US-ASCII CR, carriage return (13)>
    70  	// LF         = <US-ASCII LF, linefeed (10)>
    71  	// SP         = <US-ASCII SP, space (32)>
    72  	// HT         = <US-ASCII HT, horizontal-tab (9)>
    73  	// <">        = <US-ASCII double-quote mark (34)>
    74  	// CRLF       = CR LF
    75  	// LWS        = [CRLF] 1*( SP | HT )
    76  	// TEXT       = <any OCTET except CTLs, but including LWS>
    77  	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
    78  	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
    79  	// token      = 1*<any CHAR except CTLs or separators>
    80  	// qdtext     = <any TEXT except <">>
    81  
    82  	for c := 0; c < 256; c++ {
    83  		var t octetType
    84  		isCtl := c <= 31 || c == 127
    85  		isChar := 0 <= c && c <= 127
    86  		isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
    87  		if strings.ContainsRune(" \t\r\n", rune(c)) {
    88  			t |= isSpace
    89  		}
    90  		if isChar && !isCtl && !isSeparator {
    91  			t |= isToken
    92  		}
    93  		octetTypes[c] = t
    94  	}
    95  }
    96  
    97  // ParseAuthHeader parses challenges from WWW-Authenticate header
    98  func ParseAuthHeader(header http.Header) []Challenge {
    99  	challenges := []Challenge{}
   100  	for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
   101  		v, p := parseValueAndParams(h)
   102  		var s AuthenticationScheme
   103  		switch v {
   104  		case "basic":
   105  			s = BasicAuth
   106  		case "digest":
   107  			s = DigestAuth
   108  		case "bearer":
   109  			s = BearerAuth
   110  		default:
   111  			continue
   112  		}
   113  		challenges = append(challenges, Challenge{Scheme: s, Parameters: p})
   114  	}
   115  	sort.Stable(byScheme(challenges))
   116  	return challenges
   117  }
   118  
   119  func parseValueAndParams(header string) (value string, params map[string]string) {
   120  	params = make(map[string]string)
   121  	value, s := expectToken(header)
   122  	if value == "" {
   123  		return
   124  	}
   125  	value = strings.ToLower(value)
   126  	for {
   127  		var pkey string
   128  		pkey, s = expectToken(skipSpace(s))
   129  		if pkey == "" {
   130  			return
   131  		}
   132  		if !strings.HasPrefix(s, "=") {
   133  			return
   134  		}
   135  		var pvalue string
   136  		pvalue, s = expectTokenOrQuoted(s[1:])
   137  		if pvalue == "" {
   138  			return
   139  		}
   140  		pkey = strings.ToLower(pkey)
   141  		params[pkey] = pvalue
   142  		s = skipSpace(s)
   143  		if !strings.HasPrefix(s, ",") {
   144  			return
   145  		}
   146  		s = s[1:]
   147  	}
   148  }
   149  
   150  func skipSpace(s string) (rest string) {
   151  	i := 0
   152  	for ; i < len(s); i++ {
   153  		if octetTypes[s[i]]&isSpace == 0 {
   154  			break
   155  		}
   156  	}
   157  	return s[i:]
   158  }
   159  
   160  func expectToken(s string) (token, rest string) {
   161  	i := 0
   162  	for ; i < len(s); i++ {
   163  		if octetTypes[s[i]]&isToken == 0 {
   164  			break
   165  		}
   166  	}
   167  	return s[:i], s[i:]
   168  }
   169  
   170  func expectTokenOrQuoted(s string) (value string, rest string) {
   171  	if !strings.HasPrefix(s, "\"") {
   172  		return expectToken(s)
   173  	}
   174  	s = s[1:]
   175  	for i := 0; i < len(s); i++ {
   176  		switch s[i] {
   177  		case '"':
   178  			return s[:i], s[i+1:]
   179  		case '\\':
   180  			p := make([]byte, len(s)-1)
   181  			j := copy(p, s[:i])
   182  			escape := true
   183  			for i = i + 1; i < len(s); i++ {
   184  				b := s[i]
   185  				switch {
   186  				case escape:
   187  					escape = false
   188  					p[j] = b
   189  					j++
   190  				case b == '\\':
   191  					escape = true
   192  				case b == '"':
   193  					return string(p[:j]), s[i+1:]
   194  				default:
   195  					p[j] = b
   196  					j++
   197  				}
   198  			}
   199  			return "", ""
   200  		}
   201  	}
   202  	return "", ""
   203  }