Skip to content

Commit 0951d18

Browse files
authored
Merge commit from fork
* Remove strings.Split and add parseToken function * review and add tests
1 parent c035977 commit 0951d18

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-3
lines changed

jwt_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package jwt
2+
3+
import (
4+
"testing"
5+
)
6+
7+
funcTestSplitToken(t*testing.T) {
8+
t.Parallel()
9+
10+
tests:= []struct {
11+
namestring
12+
inputstring
13+
expected []string
14+
isValidbool
15+
}{
16+
{
17+
name: "valid token with three parts",
18+
input: "header.claims.signature",
19+
expected: []string{"header", "claims", "signature"},
20+
isValid: true,
21+
},
22+
{
23+
name: "invalid token with two parts only",
24+
input: "header.claims",
25+
expected: nil,
26+
isValid: false,
27+
},
28+
{
29+
name: "invalid token with one part only",
30+
input: "header",
31+
expected: nil,
32+
isValid: false,
33+
},
34+
{
35+
name: "invalid token with extra delimiter",
36+
input: "header.claims.signature.extra",
37+
expected: nil,
38+
isValid: false,
39+
},
40+
{
41+
name: "invalid empty token",
42+
input: "",
43+
expected: nil,
44+
isValid: false,
45+
},
46+
{
47+
name: "valid token with empty parts",
48+
input: "..signature",
49+
expected: []string{"", "", "signature"},
50+
isValid: true,
51+
},
52+
{
53+
// We are just splitting the token into parts, so we don't care about the actual values.
54+
// It is up to the caller to validate the parts.
55+
name: "valid token with all parts empty",
56+
input: "..",
57+
expected: []string{"", "", ""},
58+
isValid: true,
59+
},
60+
{
61+
name: "invalid token with just delimiters and extra part",
62+
input: "...",
63+
expected: nil,
64+
isValid: false,
65+
},
66+
{
67+
name: "invalid token with many delimiters",
68+
input: "header.claims.signature..................",
69+
expected: nil,
70+
isValid: false,
71+
},
72+
}
73+
74+
for_, tt:=rangetests {
75+
t.Run(tt.name, func(t*testing.T) {
76+
parts, ok:=splitToken(tt.input)
77+
ifok!=tt.isValid {
78+
t.Errorf("expected %t, got %t", tt.isValid, ok)
79+
}
80+
ifok {
81+
fori, part:=rangett.expected {
82+
ifparts[i] !=part {
83+
t.Errorf("expected %s, got %s", part, parts[i])
84+
}
85+
}
86+
}
87+
})
88+
}
89+
}

parser.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"strings"
99
)
1010

11+
consttokenDelimiter="."
12+
1113
typeParserstruct {
1214
// If populated, only these methods will be considered valid.
1315
validMethods []string
@@ -136,9 +138,10 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
136138
// It's only ever useful in cases where you know the signature is valid (since it has already
137139
// been or will be checked elsewhere in the stack) and you want to extract values from it.
138140
func (p*Parser) ParseUnverified(tokenStringstring, claimsClaims) (token*Token, parts []string, errerror) {
139-
parts=strings.Split(tokenString, ".")
140-
iflen(parts) !=3 {
141-
returnnil, parts, newError("token contains an invalid number of segments", ErrTokenMalformed)
141+
varokbool
142+
parts, ok=splitToken(tokenString)
143+
if!ok {
144+
returnnil, nil, newError("token contains an invalid number of segments", ErrTokenMalformed)
142145
}
143146

144147
token=&Token{Raw: tokenString}
@@ -196,6 +199,33 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
196199
returntoken, parts, nil
197200
}
198201

202+
// splitToken splits a token string into three parts: header, claims, and signature. It will only
203+
// return true if the token contains exactly two delimiters and three parts. In all other cases, it
204+
// will return nil parts and false.
205+
funcsplitToken(tokenstring) ([]string, bool) {
206+
parts:=make([]string, 3)
207+
header, remain, ok:=strings.Cut(token, tokenDelimiter)
208+
if!ok {
209+
returnnil, false
210+
}
211+
parts[0] =header
212+
claims, remain, ok:=strings.Cut(remain, tokenDelimiter)
213+
if!ok {
214+
returnnil, false
215+
}
216+
parts[1] =claims
217+
// One more cut to ensure the signature is the last part of the token and there are no more
218+
// delimiters. This avoids an issue where malicious input could contain additional delimiters
219+
// causing unecessary overhead parsing tokens.
220+
signature, _, unexpected:=strings.Cut(remain, tokenDelimiter)
221+
ifunexpected {
222+
returnnil, false
223+
}
224+
parts[2] =signature
225+
226+
returnparts, true
227+
}
228+
199229
// DecodeSegment decodes a JWT specific base64url encoding. This function will
200230
// take into account whether the [Parser] is configured with additional options,
201231
// such as [WithStrictDecoding] or [WithPaddingAllowed].

0 commit comments

Comments
 (0)
close