cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ociauth/challenge.go (about) 1 package ociauth 2 3 import ( 4 "net/http" 5 "strings" 6 ) 7 8 // Octet types from RFC 2616. 9 type octetType byte 10 11 var octetTypes [256]octetType 12 13 const ( 14 isToken octetType = 1 << iota 15 isSpace 16 ) 17 18 func init() { 19 // OCTET = <any 8-bit sequence of data> 20 // CHAR = <any US-ASCII character (octets 0 - 127)> 21 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> 22 // CR = <US-ASCII CR, carriage return (13)> 23 // LF = <US-ASCII LF, linefeed (10)> 24 // SP = <US-ASCII SP, space (32)> 25 // HT = <US-ASCII HT, horizontal-tab (9)> 26 // <"> = <US-ASCII double-quote mark (34)> 27 // CRLF = CR LF 28 // LWS = [CRLF] 1*( SP | HT ) 29 // TEXT = <any OCTET except CTLs, but including LWS> 30 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> 31 // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT 32 // token = 1*<any CHAR except CTLs or separators> 33 // qdtext = <any TEXT except <">> 34 35 for c := 0; c < 256; c++ { 36 var t octetType 37 isCtl := c <= 31 || c == 127 38 isChar := 0 <= c && c <= 127 39 isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) 40 if strings.ContainsRune(" \t\r\n", rune(c)) { 41 t |= isSpace 42 } 43 if isChar && !isCtl && !isSeparator { 44 t |= isToken 45 } 46 octetTypes[c] = t 47 } 48 } 49 50 // authHeader holds the parsed contents of a Www-Authenticate HTTP header. 51 type authHeader struct { 52 scheme string 53 params map[string]string 54 } 55 56 func challengeFromResponse(resp *http.Response) *authHeader { 57 var h *authHeader 58 for _, chalStr := range resp.Header["Www-Authenticate"] { 59 h1 := parseWWWAuthenticate(chalStr) 60 if h1 == nil { 61 continue 62 } 63 if h1.scheme != "basic" && h1.scheme != "bearer" { 64 continue 65 } 66 if h == nil { 67 h = h1 68 } else if h1.scheme == "basic" && h.scheme == "bearer" { 69 // We prefer basic auth to bearer auth. 70 h = h1 71 } 72 } 73 return h 74 } 75 76 // parseWWWAuthenticate parses the contents of a Www-Authenticate HTTP header. 77 // It returns nil if the parsing fails. 78 func parseWWWAuthenticate(header string) *authHeader { 79 var h authHeader 80 h.params = make(map[string]string) 81 82 scheme, s := expectToken(header) 83 if scheme == "" { 84 return nil 85 } 86 h.scheme = strings.ToLower(scheme) 87 s = skipSpace(s) 88 for len(s) > 0 { 89 var pkey, pvalue string 90 pkey, s = expectToken(skipSpace(s)) 91 if pkey == "" { 92 return nil 93 } 94 if !strings.HasPrefix(s, "=") { 95 return nil 96 } 97 pvalue, s = expectTokenOrQuoted(s[1:]) 98 if pvalue == "" { 99 return nil 100 } 101 h.params[strings.ToLower(pkey)] = pvalue 102 s = skipSpace(s) 103 if !strings.HasPrefix(s, ",") { 104 break 105 } 106 s = s[1:] 107 } 108 if len(s) > 0 { 109 return nil 110 } 111 return &h 112 } 113 114 func skipSpace(s string) (rest string) { 115 i := 0 116 for ; i < len(s); i++ { 117 if octetTypes[s[i]]&isSpace == 0 { 118 break 119 } 120 } 121 return s[i:] 122 } 123 124 func expectToken(s string) (token, rest string) { 125 i := 0 126 for ; i < len(s); i++ { 127 if octetTypes[s[i]]&isToken == 0 { 128 break 129 } 130 } 131 return s[:i], s[i:] 132 } 133 134 func expectTokenOrQuoted(s string) (value string, rest string) { 135 if !strings.HasPrefix(s, "\"") { 136 return expectToken(s) 137 } 138 s = s[1:] 139 for i := 0; i < len(s); i++ { 140 switch s[i] { 141 case '"': 142 return s[:i], s[i+1:] 143 case '\\': 144 p := make([]byte, len(s)-1) 145 j := copy(p, s[:i]) 146 escape := true 147 for i = i + 1; i < len(s); i++ { 148 b := s[i] 149 switch { 150 case escape: 151 escape = false 152 p[j] = b 153 j++ 154 case b == '\\': 155 escape = true 156 case b == '"': 157 return string(p[:j]), s[i+1:] 158 default: 159 p[j] = b 160 j++ 161 } 162 } 163 return "", "" 164 } 165 } 166 return "", "" 167 }