...

Source file src/cuelabs.dev/go/oci/ociregistry/ociauth/challenge.go

Documentation: cuelabs.dev/go/oci/ociregistry/ociauth

     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  }
   168  

View as plain text