4c8e4dc373
Adds functionality to create a Repository client which connects to a remote endpoint. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
150 lines
3.3 KiB
Go
150 lines
3.3 KiB
Go
package client
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Octet types from RFC 2616.
|
|
type octetType byte
|
|
|
|
// AuthorizationChallenge carries information
|
|
// from a WWW-Authenticate response header.
|
|
type AuthorizationChallenge struct {
|
|
Scheme string
|
|
Parameters map[string]string
|
|
}
|
|
|
|
var octetTypes [256]octetType
|
|
|
|
const (
|
|
isToken octetType = 1 << iota
|
|
isSpace
|
|
)
|
|
|
|
func init() {
|
|
// OCTET = <any 8-bit sequence of data>
|
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
|
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
|
// CR = <US-ASCII CR, carriage return (13)>
|
|
// LF = <US-ASCII LF, linefeed (10)>
|
|
// SP = <US-ASCII SP, space (32)>
|
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
|
// <"> = <US-ASCII double-quote mark (34)>
|
|
// CRLF = CR LF
|
|
// LWS = [CRLF] 1*( SP | HT )
|
|
// TEXT = <any OCTET except CTLs, but including LWS>
|
|
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
|
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
|
// token = 1*<any CHAR except CTLs or separators>
|
|
// qdtext = <any TEXT except <">>
|
|
|
|
for c := 0; c < 256; c++ {
|
|
var t octetType
|
|
isCtl := c <= 31 || c == 127
|
|
isChar := 0 <= c && c <= 127
|
|
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
|
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
|
t |= isSpace
|
|
}
|
|
if isChar && !isCtl && !isSeparator {
|
|
t |= isToken
|
|
}
|
|
octetTypes[c] = t
|
|
}
|
|
}
|
|
|
|
func parseAuthHeader(header http.Header) []AuthorizationChallenge {
|
|
var challenges []AuthorizationChallenge
|
|
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
|
|
v, p := parseValueAndParams(h)
|
|
if v != "" {
|
|
challenges = append(challenges, AuthorizationChallenge{Scheme: v, Parameters: p})
|
|
}
|
|
}
|
|
return challenges
|
|
}
|
|
|
|
func parseValueAndParams(header string) (value string, params map[string]string) {
|
|
params = make(map[string]string)
|
|
value, s := expectToken(header)
|
|
if value == "" {
|
|
return
|
|
}
|
|
value = strings.ToLower(value)
|
|
s = "," + skipSpace(s)
|
|
for strings.HasPrefix(s, ",") {
|
|
var pkey string
|
|
pkey, s = expectToken(skipSpace(s[1:]))
|
|
if pkey == "" {
|
|
return
|
|
}
|
|
if !strings.HasPrefix(s, "=") {
|
|
return
|
|
}
|
|
var pvalue string
|
|
pvalue, s = expectTokenOrQuoted(s[1:])
|
|
if pvalue == "" {
|
|
return
|
|
}
|
|
pkey = strings.ToLower(pkey)
|
|
params[pkey] = pvalue
|
|
s = skipSpace(s)
|
|
}
|
|
return
|
|
}
|
|
|
|
func skipSpace(s string) (rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isSpace == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[i:]
|
|
}
|
|
|
|
func expectToken(s string) (token, rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isToken == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[:i], s[i:]
|
|
}
|
|
|
|
func expectTokenOrQuoted(s string) (value string, rest string) {
|
|
if !strings.HasPrefix(s, "\"") {
|
|
return expectToken(s)
|
|
}
|
|
s = s[1:]
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '"':
|
|
return s[:i], s[i+1:]
|
|
case '\\':
|
|
p := make([]byte, len(s)-1)
|
|
j := copy(p, s[:i])
|
|
escape := true
|
|
for i = i + i; i < len(s); i++ {
|
|
b := s[i]
|
|
switch {
|
|
case escape:
|
|
escape = false
|
|
p[j] = b
|
|
j++
|
|
case b == '\\':
|
|
escape = true
|
|
case b == '"':
|
|
return string(p[:j]), s[i+1:]
|
|
default:
|
|
p[j] = b
|
|
j++
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|