Skip to content

Commit 44ac225

Browse files
committed
feat(acceptHeaders): support wildcard match
1 parent 2bd4f79 commit 44ac225

File tree

5 files changed

+158
-17
lines changed

5 files changed

+158
-17
lines changed

src/acceptHeaders/acceptItem.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,35 @@ const defaultQuality = 1000
1010
const maxQualityDecimals = 3
1111

1212
type acceptItem struct {
13-
value string
14-
quality int
13+
value string
14+
quality int
15+
wildcards int
16+
}
17+
18+
func (item acceptItem) less(other acceptItem) bool {
19+
if item.quality != other.quality {
20+
return item.quality > other.quality
21+
}
22+
return item.wildcards < other.wildcards
23+
}
24+
25+
func (item acceptItem) match(value string) bool {
26+
if item.value == value {
27+
return true
28+
}
29+
30+
switch item.wildcards {
31+
case 0:
32+
return false
33+
case 1:
34+
slashIndex := strings.IndexByte(item.value, '/')
35+
itemPrefix := item.value[:slashIndex+1]
36+
return strings.HasPrefix(value, itemPrefix)
37+
case 2:
38+
return true
39+
}
40+
41+
return false
1542
}
1643

1744
func parseAcceptItem(input string) acceptItem {
@@ -20,10 +47,17 @@ func parseAcceptItem(input string) acceptItem {
2047
value = value[:semiColonIndex]
2148
}
2249

50+
wildcards := 0
51+
if value == "*/*" {
52+
wildcards = 2
53+
} else if strings.HasSuffix(value, "/*") {
54+
wildcards = 1
55+
}
56+
2357
rest := input[len(value):]
2458
qSignIndex := strings.Index(rest, qualitySign)
2559
if qSignIndex < 0 {
26-
return acceptItem{value, defaultQuality}
60+
return acceptItem{value, defaultQuality, wildcards}
2761
}
2862

2963
rest = rest[qSignIndex+len(qualitySign):]
@@ -33,20 +67,20 @@ func parseAcceptItem(input string) acceptItem {
3367
qLen := len(rest)
3468

3569
if qLen == 0 {
36-
return acceptItem{value, defaultQuality}
70+
return acceptItem{value, defaultQuality, wildcards}
3771
}
3872
if qLen > 1 && rest[1] != '.' {
39-
return acceptItem{value, defaultQuality}
73+
return acceptItem{value, defaultQuality, wildcards}
4074
}
4175

4276
// "q=1" or q is an invalid value
4377
if rest[0] != '0' {
44-
return acceptItem{value, defaultQuality}
78+
return acceptItem{value, defaultQuality, wildcards}
4579
}
4680

4781
// "q=0."
4882
if qLen <= 2 {
49-
return acceptItem{value, 0}
83+
return acceptItem{value, 0, wildcards}
5084
}
5185

5286
rest = rest[2:]
@@ -65,5 +99,5 @@ func parseAcceptItem(input string) acceptItem {
6599
quality *= 10
66100
}
67101
}
68-
return acceptItem{value, quality}
102+
return acceptItem{value, quality, wildcards}
69103
}

src/acceptHeaders/acceptItem_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,44 @@ func TestParseAcceptItem(t *testing.T) {
159159
t.Error(output.quality)
160160
}
161161
}
162+
163+
func TestAcceptItemMatch(t *testing.T) {
164+
var item acceptItem
165+
166+
item = acceptItem{"text/html", 1000, 0}
167+
if !item.match("text/html") {
168+
t.Error()
169+
}
170+
if item.match("text/plain") {
171+
t.Error()
172+
}
173+
174+
item = acceptItem{"text/*", 1000, 1}
175+
if !item.match("text/*") {
176+
t.Error()
177+
}
178+
if !item.match("text/html") {
179+
t.Error()
180+
}
181+
if !item.match("text/plain") {
182+
t.Error()
183+
}
184+
if item.match("image/png") {
185+
t.Error()
186+
}
187+
188+
item = acceptItem{"*/*", 1000, 2}
189+
if !item.match("text/*") {
190+
t.Error()
191+
}
192+
if !item.match("text/html") {
193+
t.Error()
194+
}
195+
if !item.match("text/plain") {
196+
t.Error()
197+
}
198+
if !item.match("image/png") {
199+
t.Error()
200+
}
201+
202+
}

src/acceptHeaders/accepts.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ func (accepts Accepts) Swap(i, j int) {
1616
}
1717

1818
func (accepts Accepts) Less(i, j int) bool {
19-
return accepts[i].quality > accepts[j].quality
19+
return accepts[i].less(accepts[j])
2020
}
2121

2222
// make sure `accepts` is sorted
2323
func (accepts Accepts) GetPreferredValue(availables []string) (index int, value string, ok bool) {
2424
for _, accept := range accepts {
2525
for i, avail := range availables {
26-
if accept.value == avail {
26+
if accept.match(avail) {
2727
return i, avail, true
2828
}
2929
}

src/acceptHeaders/accepts_test.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,73 @@
11
package acceptHeaders
22

3-
import "testing"
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestParseAccept(t *testing.T) {
9+
accept := "text/html, */*;q=0.6, text/plain;q=0.9, application/json;q=0.7, image/*;q=0.8, image/png;q=0.8"
10+
accepts := ParseAccepts(accept)
11+
if len(accepts) != 6 {
12+
t.Error(len(accepts))
13+
}
14+
15+
if !reflect.DeepEqual(accepts[0], acceptItem{"text/html", 1000, 0}) {
16+
t.Error(accepts[0])
17+
}
18+
if !reflect.DeepEqual(accepts[1], acceptItem{"text/plain", 900, 0}) {
19+
t.Error(accepts[1])
20+
}
21+
if !reflect.DeepEqual(accepts[2], acceptItem{"image/png", 800, 0}) {
22+
t.Error(accepts[2])
23+
}
24+
if !reflect.DeepEqual(accepts[3], acceptItem{"image/*", 800, 1}) {
25+
t.Error(accepts[3])
26+
}
27+
if !reflect.DeepEqual(accepts[4], acceptItem{"application/json", 700, 0}) {
28+
t.Error(accepts[4])
29+
}
30+
if !reflect.DeepEqual(accepts[5], acceptItem{"*/*", 600, 2}) {
31+
t.Error(accepts[5])
32+
}
33+
34+
var index int
35+
var preferred string
36+
37+
index, preferred, _ = accepts.GetPreferredValue([]string{"text/plain", "text/html"})
38+
if index != 1 {
39+
t.Error(index)
40+
}
41+
if preferred != "text/html" {
42+
t.Error(preferred)
43+
}
44+
45+
index, preferred, _ = accepts.GetPreferredValue([]string{"image/jpeg", "image/png"})
46+
if index != 1 {
47+
t.Error(index)
48+
}
49+
if preferred != "image/png" {
50+
t.Error(preferred)
51+
}
52+
53+
index, preferred, _ = accepts.GetPreferredValue([]string{"image/png", "image/jpeg"})
54+
if index != 0 {
55+
t.Error(index)
56+
}
57+
if preferred != "image/png" {
58+
t.Error(preferred)
59+
}
60+
61+
index, preferred, _ = accepts.GetPreferredValue([]string{"image/webp", "image/jpeg"})
62+
if index != 0 {
63+
t.Error(index)
64+
}
65+
if preferred != "image/webp" {
66+
t.Error(preferred)
67+
}
68+
}
469

5-
func TestParseAccepts(t *testing.T) {
70+
func TestParseAcceptLanguage(t *testing.T) {
671
acceptLanguage := "zh;q=0.9,zh-CN,en;q=0.7,en-US;q=0.8"
772
accepts := ParseAccepts(acceptLanguage)
873
if len(accepts) != 4 {
@@ -38,7 +103,7 @@ func TestParseAccepts(t *testing.T) {
38103
}
39104
}
40105

41-
func TestParseAccepts2(t *testing.T) {
106+
func TestParseAcceptEncoding(t *testing.T) {
42107
acceptEncoding := "gzip, deflate"
43108
accepts := ParseAccepts(acceptEncoding)
44109

@@ -61,7 +126,7 @@ func TestParseAccepts2(t *testing.T) {
61126
}
62127
}
63128

64-
func TestGetPreferredValue(t *testing.T) {
129+
func TestParseAcceptEncoding2(t *testing.T) {
65130
acceptEncoding := "gzip;v=b3;q=0.9, deflate"
66131
accepts := ParseAccepts(acceptEncoding)
67132

src/serverHandler/sessionData.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ const (
3131
const contentTypeJson = "application/json"
3232

3333
var acceptContentTypes = []string{
34-
contentTypeJson,
3534
"text/html",
3635
"application/xhtml+xml",
3736
"application/xml",
37+
contentTypeJson,
3838
}
39+
var acceptJsonIndex = len(acceptContentTypes) - 1
3940

4041
type pathEntry struct {
4142
Name string `json:"name"`
@@ -410,8 +411,8 @@ func (h *aliasHandler) getSessionData(r *http.Request) (session *sessionContext,
410411
}
411412

412413
accepts := acceptHeaders.ParseAccepts(r.Header.Get("Accept"))
413-
_, preferredContentType, _ := accepts.GetPreferredValue(acceptContentTypes)
414-
wantJson := preferredContentType == contentTypeJson
414+
acceptIndex, _, _ := accepts.GetPreferredValue(acceptContentTypes)
415+
wantJson := acceptIndex == acceptJsonIndex
415416

416417
isRoot := vhostReqPath == "/"
417418

0 commit comments

Comments
 (0)