1package jwt
2
3import (
4	"net/http"
5	"net/url"
6	"strconv"
7	"strings"
8
9	"github.com/lestrrat-go/jwx/internal/pool"
10	"github.com/pkg/errors"
11)
12
13// ParseHeader parses a JWT stored in a http.Header.
14//
15// For the header "Authorization", it will strip the prefix "Bearer " and will
16// treat the remaining value as a JWT.
17func ParseHeader(hdr http.Header, name string, options ...ParseOption) (Token, error) {
18	key := http.CanonicalHeaderKey(name)
19	v := strings.TrimSpace(hdr.Get(key))
20	if v == "" {
21		return nil, errors.Errorf(`empty header (%s)`, key)
22	}
23
24	if key == "Authorization" {
25		// Authorization header is an exception. We strip the "Bearer " from
26		// the prefix
27		v = strings.TrimSpace(strings.TrimPrefix(v, "Bearer"))
28	}
29
30	return ParseString(v, options...)
31}
32
33// ParseForm parses a JWT stored in a url.Value.
34func ParseForm(values url.Values, name string, options ...ParseOption) (Token, error) {
35	v := strings.TrimSpace(values.Get(name))
36	if v == "" {
37		return nil, errors.Errorf(`empty value (%s)`, name)
38	}
39
40	return ParseString(v, options...)
41}
42
43// ParseRequest searches a http.Request object for a JWT token.
44//
45// Specifying WithHeaderKey() will tell it to search under a specific
46// header key. Specifying WithFormKey() will tell it to search under
47// a specific form field.
48//
49// By default, "Authorization" header will be searched.
50//
51// If WithHeaderKey() is used, you must explicitly re-enable searching for "Authorization" header.
52//
53//   # searches for "Authorization"
54//   jwt.ParseRequest(req)
55//
56//   # searches for "x-my-token" ONLY.
57//   jwt.ParseRequest(req, http.WithHeaderKey("x-my-token"))
58//
59//   # searches for "Authorization" AND "x-my-token"
60//   jwt.ParseRequest(req, http.WithHeaderKey("Authorization"), http.WithHeaderKey("x-my-token"))
61func ParseRequest(req *http.Request, options ...ParseOption) (Token, error) {
62	var hdrkeys []string
63	var formkeys []string
64	var parseOptions []ParseOption
65	for _, option := range options {
66		switch option.Ident() {
67		case identHeaderKey{}:
68			hdrkeys = append(hdrkeys, option.Value().(string))
69		case identFormKey{}:
70			formkeys = append(formkeys, option.Value().(string))
71		default:
72			parseOptions = append(parseOptions, option)
73		}
74	}
75	if len(hdrkeys) == 0 {
76		hdrkeys = append(hdrkeys, "Authorization")
77	}
78
79	mhdrs := pool.GetKeyToErrorMap()
80	defer pool.ReleaseKeyToErrorMap(mhdrs)
81	mfrms := pool.GetKeyToErrorMap()
82	defer pool.ReleaseKeyToErrorMap(mfrms)
83
84	for _, hdrkey := range hdrkeys {
85		// Check presence via a direct map lookup
86		if _, ok := req.Header[http.CanonicalHeaderKey(hdrkey)]; !ok {
87			// if non-existent, not error
88			continue
89		}
90
91		tok, err := ParseHeader(req.Header, hdrkey, parseOptions...)
92		if err != nil {
93			mhdrs[hdrkey] = err
94			continue
95		}
96		return tok, nil
97	}
98
99	if cl := req.ContentLength; cl > 0 {
100		if err := req.ParseForm(); err != nil {
101			return nil, errors.Wrap(err, `failed to parse form`)
102		}
103	}
104
105	for _, formkey := range formkeys {
106		// Check presence via a direct map lookup
107		if _, ok := req.Form[formkey]; !ok {
108			// if non-existent, not error
109			continue
110		}
111
112		tok, err := ParseForm(req.Form, formkey, parseOptions...)
113		if err != nil {
114			mfrms[formkey] = err
115			continue
116		}
117		return tok, nil
118	}
119
120	// Everything below is a preulde to error reporting.
121	var triedHdrs strings.Builder
122	for i, hdrkey := range hdrkeys {
123		if i > 0 {
124			triedHdrs.WriteString(", ")
125		}
126		triedHdrs.WriteString(strconv.Quote(hdrkey))
127	}
128
129	var triedForms strings.Builder
130	for i, formkey := range formkeys {
131		if i > 0 {
132			triedForms.WriteString(", ")
133		}
134		triedForms.WriteString(strconv.Quote(formkey))
135	}
136
137	var b strings.Builder
138	b.WriteString(`failed to find a valid token in any location of the request (tried: [header keys: `)
139	b.WriteString(triedHdrs.String())
140	b.WriteByte(']')
141	if triedForms.Len() > 0 {
142		b.WriteString(", form keys: [")
143		b.WriteString(triedForms.String())
144		b.WriteByte(']')
145	}
146	b.WriteByte(')')
147
148	lmhdrs := len(mhdrs)
149	lmfrms := len(mfrms)
150	if lmhdrs > 0 || lmfrms > 0 {
151		b.WriteString(". Additionally, errors were encountered during attempts to parse")
152
153		if lmhdrs > 0 {
154			b.WriteString(" headers: (")
155			count := 0
156			for hdrkey, err := range mhdrs {
157				if count > 0 {
158					b.WriteString(", ")
159				}
160				b.WriteString("[header key: ")
161				b.WriteString(strconv.Quote(hdrkey))
162				b.WriteString(", error: ")
163				b.WriteString(strconv.Quote(err.Error()))
164				b.WriteString("]")
165				count++
166			}
167			b.WriteString(")")
168		}
169
170		if lmfrms > 0 {
171			count := 0
172			b.WriteString(" forms: (")
173			for formkey, err := range mfrms {
174				if count > 0 {
175					b.WriteString(", ")
176				}
177				b.WriteString("[form key: ")
178				b.WriteString(strconv.Quote(formkey))
179				b.WriteString(", error: ")
180				b.WriteString(strconv.Quote(err.Error()))
181				b.WriteString("]")
182				count++
183			}
184		}
185	}
186	return nil, errors.New(b.String())
187}
188