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 }