This repository has been archived by the owner on Dec 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrfc7231.go
327 lines (304 loc) · 8.75 KB
/
rfc7231.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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package httpheader
import (
"net/http"
"strconv"
"strings"
"time"
)
// Allow parses the Allow header from h (RFC 7231 Section 7.4.1).
//
// If there is no such header in h, Allow returns nil.
// If the header is present but empty (meaning all methods are disallowed),
// Allow returns a non-nil slice of length 0.
func Allow(h http.Header) []string {
values := h["Allow"]
if values == nil {
return nil
}
methods := make([]string, 0, estimateElems(values))
for v, vs := iterElems("", values); v != ""; v, vs = iterElems(v, vs) {
var method string
method, v = consumeItem(v)
methods = append(methods, method)
}
return methods
}
// SetAllow replaces the Allow header in h (RFC 7231 Section 7.4.1).
func SetAllow(h http.Header, methods []string) {
h.Set("Allow", strings.Join(methods, ", "))
}
// Vary parses the Vary header from h (RFC 7231 Section 7.1.4), returning a map
// where keys are header names, canonicalized with http.CanonicalHeaderKey,
// and values are all true. A wildcard (Vary: *) is returned as map[*:true],
// so it must be checked explicitly.
func Vary(h http.Header) map[string]bool {
values := h["Vary"]
if values == nil {
return nil
}
names := make(map[string]bool)
for v, vs := iterElems("", values); v != ""; v, vs = iterElems(v, vs) {
var name string
name, v = consumeItem(v)
name = http.CanonicalHeaderKey(name)
names[name] = true
}
return names
}
// SetVary replaces the Vary header in h (RFC 7231 Section 7.1.4).
// Names mapping to false are ignored. See also AddVary.
func SetVary(h http.Header, names map[string]bool) {
b := &strings.Builder{}
for name, value := range names {
if !value {
continue
}
if b.Len() > 0 {
write(b, ", ")
}
write(b, name)
}
h.Set("Vary", b.String())
}
// AddVary appends the given names to the Vary header in h
// (RFC 7231 Section 7.1.4).
func AddVary(h http.Header, names ...string) {
if len(names) == 0 {
return
}
h.Add("Vary", strings.Join(names, ", "))
}
// A Product contains software information as found in the User-Agent
// and Server headers (RFC 7231 Section 5.5.3 and Section 7.4.2).
// If multiple comments are associated with a product, they are concatenated
// with a "; " separator.
type Product struct {
Name string
Version string
Comment string
}
// UserAgent parses the User-Agent header from h (RFC 7231 Section 5.5.3).
func UserAgent(h http.Header) []Product {
return parseProducts(h.Get("User-Agent"))
}
// SetUserAgent replaces the User-Agent header in h (RFC 7231 Section 5.5.3).
func SetUserAgent(h http.Header, products []Product) {
if len(products) == 0 {
h.Del("User-Agent")
return
}
h.Set("User-Agent", serializeProducts(products))
}
// Server parses the Server header from h (RFC 7231 Section 7.4.2).
func Server(h http.Header) []Product {
return parseProducts(h.Get("Server"))
}
// SetServer replaces the Server header in h (RFC 7231 Section 7.4.2).
func SetServer(h http.Header, products []Product) {
if len(products) == 0 {
h.Del("Server")
return
}
h.Set("Server", serializeProducts(products))
}
func parseProducts(v string) []Product {
var products []Product
for v != "" {
var product Product
product.Name, v = consumeItem(v)
if product.Name == "" {
// Avoid infinite loop.
v = v[1:]
continue
}
product.Name, product.Version = consumeTo(product.Name, '/', false)
// Collect all comments for this product.
for {
v = skipWS(v)
if peek(v) != '(' {
break
}
var comment string
comment, v = consumeComment(v)
if product.Comment == "" {
product.Comment = comment
} else {
product.Comment += "; " + comment
}
}
products = append(products, product)
}
return products
}
func serializeProducts(products []Product) string {
b := &strings.Builder{}
for i, product := range products {
if i > 0 {
write(b, " ")
}
write(b, product.Name)
if product.Version != "" {
write(b, "/", product.Version)
}
if product.Comment != "" {
write(b, " ")
writeComment(b, product.Comment)
}
}
return b.String()
}
// RetryAfter parses the Retry-After header from h (RFC 7231 Section 7.1.3).
// When it is specified as delay seconds, those are added to the Date header
// if one exists in h, otherwise to the current time. If the header cannot
// be parsed, a zero Time is returned.
func RetryAfter(h http.Header) time.Time {
v := h.Get("Retry-After")
if v == "" {
return time.Time{}
}
if v[0] < '0' || '9' < v[0] {
// HTTP-date
date, err := http.ParseTime(v)
if err != nil {
return time.Time{}
}
return date
}
// delay-seconds
seconds, err := strconv.Atoi(v)
if err != nil {
return time.Time{}
}
// Strictly speaking, RFC 7231 says "number of seconds to delay
// after the response is received", not after it was originated (Date),
// but the response may have been stored or processed for a long time
// before being fed to us, so Date might even be closer than Now().
date, err := http.ParseTime(h.Get("Date"))
if err != nil {
date = time.Now()
}
return date.Add(time.Duration(seconds) * time.Second)
}
// SetRetryAfter replaces the Retry-After header in h (RFC 7231 Section 7.1.3).
func SetRetryAfter(h http.Header, after time.Time) {
h.Set("Retry-After", after.Format(http.TimeFormat))
}
// ContentType parses the Content-Type header from h (RFC 7231 Section 3.1.1.5),
// returning the media type/subtype and any parameters.
func ContentType(h http.Header) (mtype string, params map[string]string) {
v := h.Get("Content-Type")
mtype, v = consumeItem(v)
mtype = strings.ToLower(mtype)
params, _ = consumeParams(v)
return
}
// SetContentType replaces the Content-Type header in h (RFC 7231 Section 3.1.1.5).
func SetContentType(h http.Header, mtype string, params map[string]string) {
b := &strings.Builder{}
write(b, mtype)
writeParams(b, params)
h.Set("Content-Type", b.String())
}
// An AcceptElem represents one element of the Accept header
// (RFC 7231 Section 5.3.2).
type AcceptElem struct {
Type string // media range
Params map[string]string // media type parameters (before q)
Q float32 // quality value
Ext map[string]string // extension parameters (after q)
}
// Accept parses the Accept header from h (RFC 7231 Section 5.3.2).
// The function MatchAccept is useful for working with the returned slice.
func Accept(h http.Header) []AcceptElem {
values := h["Accept"]
if values == nil {
return nil
}
elems := make([]AcceptElem, 0, estimateElems(values))
for v, vs := iterElems("", values); v != ""; v, vs = iterElems(v, vs) {
elem := AcceptElem{Q: 1}
elem.Type, v = consumeItem(v)
elem.Type = strings.ToLower(elem.Type)
afterQ := false
ParamsLoop:
for {
var name, value string
name, value, v = consumeParam(v)
switch {
case name == "":
break ParamsLoop
// 'q' separates media type parameters from extension parameters.
case name == "q":
qvalue, _ := strconv.ParseFloat(value, 32)
elem.Q = float32(qvalue)
afterQ = true
case afterQ:
if elem.Ext == nil {
elem.Ext = make(map[string]string)
}
elem.Ext[name] = value
default:
if elem.Params == nil {
elem.Params = make(map[string]string)
}
elem.Params[name] = value
}
}
elems = append(elems, elem)
}
return elems
}
// SetAccept replaces the Accept header in h (RFC 7231 Section 5.3.2).
//
// Q in elems must be set explicitly to avoid sending "q=0", which would mean
// "not acceptable".
func SetAccept(h http.Header, elems []AcceptElem) {
if elems == nil {
h.Del("Accept")
return
}
b := &strings.Builder{}
for i, elem := range elems {
if i > 0 {
write(b, ", ")
}
write(b, elem.Type)
writeParams(b, elem.Params)
if elem.Q != 1 || len(elem.Ext) > 0 {
write(b, ";q=",
// "A sender of qvalue MUST NOT generate more than three digits
// after the decimal point."
strconv.FormatFloat(float64(elem.Q), 'g', 3, 32))
}
writeNullableParams(b, elem.Ext)
}
h.Set("Accept", b.String())
}
// MatchAccept searches accept for the element that most closely matches
// mediaType, according to precedence rules of RFC 7231 Section 5.3.2.
// Only the bare type/subtype can be matched with this function;
// elements with Params are not considered. If nothing matches mediaType,
// a zero AcceptElem is returned.
func MatchAccept(accept []AcceptElem, mediaType string) AcceptElem {
mediaType = strings.ToLower(mediaType)
prefix, _ := consumeTo(mediaType, '/', true) // "text/plain" -> "text/"
best, bestPrecedence := AcceptElem{}, 0
for _, elem := range accept {
if len(elem.Params) > 0 {
continue
}
precedence := 0
switch {
case elem.Type == mediaType:
precedence = 3
case strings.HasPrefix(elem.Type, prefix) && strings.HasSuffix(elem.Type, "/*"):
precedence = 2
case elem.Type == "*/*":
precedence = 1
}
if precedence > bestPrecedence {
best, bestPrecedence = elem, precedence
}
}
return best
}