-
Notifications
You must be signed in to change notification settings - Fork 35
/
autoneg.go
167 lines (147 loc) · 4.38 KB
/
autoneg.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
161
162
163
164
165
166
167
// Package gold implements several LD standards
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The functions in this package implement the behaviour specified in
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
//
// This deviates from RFC2616 in one respect. When a client sets their
// Accept header to "*" (which is illegal) it will be interpreted as "*/*".
// This has been observed in the wild, and the choice was made in the
// spirit of being liberal in values that are accepted from the 'net.
package gold
import (
"errors"
"sort"
"strconv"
"strings"
)
// Accept structure is used to represent a clause in an HTTP Accept Header.
type Accept struct {
Type, SubType string
Q float32
Params map[string]string
}
// For internal use, so that we can use the sort interface.
type acceptSorter []Accept
func (accept acceptSorter) Len() int {
return len(accept)
}
// purposely sorts "backwards" so we have the most appropriate
// (largest q-value) at the beginning of the list.
func (accept acceptSorter) Less(i, j int) bool {
ai, aj := accept[i], accept[j]
if ai.Q > aj.Q {
return true
}
if ai.Type != "*" && aj.Type == "*" {
return true
}
if ai.SubType != "*" && aj.SubType == "*" {
return true
}
return false
}
func (accept acceptSorter) Swap(i, j int) {
accept[i], accept[j] = accept[j], accept[i]
}
// AcceptList is a sorted list of clauses from an Accept header.
type AcceptList []Accept
// Negotiate the most appropriate contentType given the list of alternatives.
// Returns an error if no alternative is acceptable.
func (al AcceptList) Negotiate(alternatives ...string) (contentType string, err error) {
asp := make([][]string, 0, len(alternatives))
for _, ctype := range alternatives {
asp = append(asp, strings.SplitN(ctype, "/", 2))
}
for _, clause := range al {
for i, ctsp := range asp {
if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
contentType = alternatives[i]
return
}
if clause.Type == ctsp[0] && clause.SubType == "*" {
contentType = alternatives[i]
return
}
if clause.Type == "*" && clause.SubType == "*" {
contentType = alternatives[i]
return
}
}
}
err = errors.New("No acceptable alternatives")
return
}
// Parse an Accept Header string returning a sorted list of clauses.
func parseAccept(header string) (accept []Accept, err error) {
header = strings.Trim(header, " ")
if len(header) == 0 {
accept = make([]Accept, 0)
return
}
parts := strings.SplitN(header, ",", -1)
accept = make([]Accept, 0, len(parts))
for _, part := range parts {
part := strings.Trim(part, " ")
a := Accept{}
a.Params = make(map[string]string)
a.Q = 1.0
mrp := strings.SplitN(part, ";", -1)
mediaRange := mrp[0]
sp := strings.SplitN(mediaRange, "/", -1)
a.Type = strings.Trim(sp[0], " ")
switch {
case len(sp) == 1 && a.Type == "*":
// The case where the Accept header is just "*" is strictly speaking
// invalid but is seen in the wild. We take it to be equivalent to
// "*/*"
a.SubType = "*"
case len(sp) == 2:
a.SubType = strings.Trim(sp[1], " ")
default:
err = errors.New("Invalid media range in " + part)
return
}
if len(mrp) == 1 {
accept = append(accept, a)
continue
}
for _, param := range mrp[1:] {
sp := strings.SplitN(param, "=", 2)
if len(sp) != 2 {
err = errors.New("Invalid parameter in " + part)
return
}
token := strings.Trim(sp[0], " ")
if token == "q" {
q, _ := strconv.ParseFloat(sp[1], 32)
a.Q = float32(q)
} else {
a.Params[token] = strings.Trim(sp[1], " ")
}
}
accept = append(accept, a)
}
sorter := acceptSorter(accept)
sort.Sort(sorter)
return
}
// Parse the Accept header and return a sorted list of clauses. If the Accept header
// is present but empty this will be an empty list. If the header is not present it will
// default to a wildcard: */*. Returns an error if the Accept header is ill-formed.
func (req *httpRequest) Accept() (al AcceptList, err error) {
var accept string
headers, ok := req.Header["Accept"]
if ok && len(headers) > 0 {
// if multiple Accept headers are specified just take the first one
// such a client would be quite broken...
accept = headers[0]
} else {
// default if not present
accept = "*/*"
}
al, err = parseAccept(accept)
return
}