forked from sourcegraph/sourcegraph-public-snapshot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathheader.go
160 lines (144 loc) · 4.9 KB
/
header.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package authz
import (
"bytes"
"fmt"
"strings"
"github.com/pkg/errors"
)
const (
SchemeToken = "token" // Scheme for Authorization header with only an access token
SchemeTokenSudo = "token-sudo" // Scheme for Authorization header with access token and sudo user
)
// errUnrecognizedScheme occurs when the Authorization header scheme (the first token) is not
// recognized.
var errUnrecognizedScheme = fmt.Errorf("unrecognized HTTP Authorization request header scheme (supported values: %q, %q)", SchemeToken, SchemeTokenSudo)
// IsUnrecognizedScheme reports whether err indicates that the request's Authorization header scheme
// is unrecognized or unparseable (i.e., is neither "token" nor "token-sudo").
func IsUnrecognizedScheme(err error) bool {
return err == errUnrecognizedScheme || err == errHTTPAuthParamsDuplicateKey || err == errHTTPAuthParamsNoEquals
}
// ParseAuthorizationHeader parses the HTTP Authorization request header for supported credentials
// values.
//
// Two forms of the Authorization header's "credentials" token are supported (see [RFC 7235,
// Appendix C](https://tools.ietf.org/html/rfc7235#appendix-C):
//
// - With only an access token: "token" 1*SP token68
// - With a token as params:
// "token" 1*SP "token" BWS "=" BWS quoted-string
//
// The returned values are derived directly from user input and have not been validated or
// authenticated.
func ParseAuthorizationHeader(headerValue string) (token, sudoUser string, err error) {
scheme, token68, params, err := parseHTTPCredentials(headerValue)
if err != nil {
return "", "", err
}
if scheme != SchemeToken && scheme != SchemeTokenSudo {
return "", "", errUnrecognizedScheme
}
if token68 != "" {
switch scheme {
case SchemeToken:
return token68, "", nil
case SchemeTokenSudo:
return "", "", errors.New(`HTTP Authorization request header value must be of the following form: token="TOKEN",user="USERNAME"`)
}
}
token = params["token"]
if token == "" {
return "", "", errors.New("no token value in the HTTP Authorization request header")
}
sudoUser = params["user"]
return token, sudoUser, nil
}
// parseHTTPCredentials parses the "credentials" token as defined in [RFC 7235 Appendix
// C](https://tools.ietf.org/html/rfc7235#appendix-C).
func parseHTTPCredentials(credentials string) (scheme, token68 string, params map[string]string, err error) {
parts := strings.SplitN(credentials, " ", 2)
scheme = parts[0]
if len(parts) == 1 {
return scheme, "", nil, nil
}
params, err = parseHTTPAuthParams(parts[1])
if err == errHTTPAuthParamsNoEquals {
// Likely just a token68.
token68 = parts[1]
return scheme, token68, nil, nil
}
if err != nil {
return "", "", nil, err
}
return scheme, "", params, nil
}
// parseHTTPAuthParams extracts key/value pairs from a comma-separated list of auth-params as defined
// in [RFC 7235, Appendix C](https://tools.ietf.org/html/rfc7235#appendix-C) and returns a map.
//
// The resulting values are unquoted. The keys are matched case-insensitively, and each key MUST
// only occur once per challenge (according to [RFC 7235, Section
// 2.1](https://tools.ietf.org/html/rfc7235#section-2.1)).
func parseHTTPAuthParams(value string) (params map[string]string, err error) {
// Implementation derived from
// https://code.google.com/p/gorilla/source/browse/http/parser/parser.go.
params = make(map[string]string)
for _, pair := range parseHTTPHeaderList(strings.TrimSpace(value)) {
i := strings.Index(pair, "=")
if i < 0 || strings.HasSuffix(pair, "=") {
return nil, errHTTPAuthParamsNoEquals
}
v := pair[i+1:]
if v[0] == '"' && v[len(v)-1] == '"' {
// Unquote it.
v = v[1 : len(v)-1]
}
key := strings.ToLower(pair[:i])
if _, seen := params[key]; seen {
return nil, errHTTPAuthParamsDuplicateKey
}
params[key] = v
}
return params, nil
}
var (
errHTTPAuthParamsDuplicateKey = errors.New("duplicate key in HTTP auth-params")
errHTTPAuthParamsNoEquals = errors.New("invalid HTTP auth-params list (parameter has no value)")
)
// parseHTTPHeaderList parses a "#rule" as defined in [RFC 2068 Section
// 2.1](https://tools.ietf.org/html/rfc2068#section-2.1).
func parseHTTPHeaderList(value string) []string {
// Implementation derived from from
// https://code.google.com/p/gorilla/source/browse/http/parser/parser.go which was ported from
// urllib2.parse_http_list, from the Python standard library.
var list []string
var escape, quote bool
b := new(bytes.Buffer)
for _, r := range value {
switch {
case escape:
b.WriteRune(r)
escape = false
case quote:
if r == '\\' {
escape = true
} else {
if r == '"' {
quote = false
}
b.WriteRune(r)
}
case r == ',':
list = append(list, strings.TrimSpace(b.String()))
b.Reset()
case r == '"':
quote = true
b.WriteRune(r)
default:
b.WriteRune(r)
}
}
// Append last part.
if s := b.String(); s != "" {
list = append(list, strings.TrimSpace(s))
}
return list
}