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 pathrfc8288.go
207 lines (190 loc) · 5.43 KB
/
rfc8288.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
package httpheader
import (
"net/http"
"net/url"
"strings"
)
// A LinkElem represents a Web link (RFC 8288).
// Standard target attributes are stored in the corresponding fields;
// any extension attributes are stored in Ext.
type LinkElem struct {
Anchor *url.URL // usually nil
Rel string
Target *url.URL // always non-nil
Title string
Type string
HrefLang []string
Media string
Ext map[string]string
}
// Link parses the Link header from h (RFC 8288), resolving any relative Target
// and Anchor URLs against base, which is the URL that h was obtained from
// (http.Response's Request.URL).
//
// In general, callers should check the Anchor of each returned LinkElem:
// a non-nil Anchor indicates a link pointing "from" another context
// (not the one that supplied the Link header). See RFC 8288 Section 3.2.
//
// Any 'title*' parameter is decoded from RFC 8187 encoding, and overrides 'title'.
// Similarly for any extension attribute whose name ends in an asterisk.
// UTF-8 is not validated in such strings.
//
// When the header contains multiple relation types in one value,
// like rel="next prefetch", multiple LinkElems with different Rel are returned.
// Any 'rev' parameter is discarded.
func Link(h http.Header, base *url.URL) []LinkElem {
values := h["Link"]
if values == nil {
return nil
}
links := make([]LinkElem, 0, estimateElems(values))
LinksLoop:
for v, vs := iterElems("", values); v != ""; v, vs = iterElems(v, vs) {
var link LinkElem
var rawTarget string
var err error
if v[0] != '<' {
continue
}
rawTarget, v = consumeTo(v[1:], '>', false)
link.Target, err = url.Parse(rawTarget)
if err != nil {
continue
}
link.Target = base.ResolveReference(link.Target)
// RFC 8288 requires us to ignore duplicates of certain parameters.
var seenRel, seenMedia, seenTitle, seenTitleStar, seenType bool
ParamsLoop:
for {
var name, value string
name, value, v = consumeParam(v)
switch name {
case "":
break ParamsLoop
case "anchor":
link.Anchor, err = url.Parse(value)
if err != nil {
// An anchor completely changes the meaning of a link,
// better not ignore it.
continue LinksLoop
}
link.Anchor = base.ResolveReference(link.Anchor)
case "rel":
if seenRel {
continue
}
link.Rel = strings.ToLower(value)
seenRel = true
case "rev":
// 'rev' is deprecated by RFC 8288.
// I don't want to add a Rev field to LinkElem,
// and I don't want to treat it as an extension attribute,
// so discard it.
case "title":
if seenTitle {
continue
}
if link.Title == "" { // not filled in from 'title*' yet
link.Title = value
}
seenTitle = true
case "title*":
if seenTitleStar {
continue
}
if decoded, _, err := DecodeExtValue(value); err == nil {
link.Title = decoded
}
seenTitleStar = true
case "type":
if seenType {
continue
}
link.Type = strings.ToLower(value)
seenType = true
case "hreflang":
link.HrefLang = append(link.HrefLang, strings.ToLower(value))
case "media":
if seenMedia {
continue
}
link.Media = value
seenMedia = true
default: // extension attributes
link.Ext = insertVariform(link.Ext, name, value)
}
}
// "Explode" into one LinkElem for each relation type. This has the side
// effect of discarding any value with empty or missing rel, which is
// probably a good idea anyway. "The rel parameter MUST be present".
for _, relType := range strings.Fields(link.Rel) {
links = append(links, link)
links[len(links)-1].Rel = relType
}
}
return links
}
// SetLink replaces the Link header in h (RFC 8288). See also AddLink.
//
// The Title of each LinkElem, if non-empty, is serialized into a 'title'
// parameter in quoted-string form, or a 'title*' parameter in RFC 8187 encoding,
// or both, depending on what characters it contains. Title should be valid UTF-8.
//
// Similarly, if Ext contains a 'qux' or 'qux*' key, it will be serialized into
// a 'qux' and/or 'qux*' parameter depending on its contents; the asterisk
// in the key is ignored.
//
// Any members of Ext named like corresponding fields of LinkElem,
// such as 'title*' or 'hreflang', are skipped.
func SetLink(h http.Header, links []LinkElem) {
if links == nil {
h.Del("Link")
return
}
h.Set("Link", buildLink(links))
}
// AddLink is like SetLink but appends instead of replacing.
func AddLink(h http.Header, links ...LinkElem) {
if len(links) == 0 {
return
}
h.Add("Link", buildLink(links))
}
func buildLink(links []LinkElem) string {
b := &strings.Builder{}
for i, link := range links {
if i > 0 {
write(b, ", ")
}
write(b, "<", link.Target.String(), ">")
if link.Anchor != nil {
write(b, `; anchor="`, link.Anchor.String(), `"`)
}
// "The rel parameter MUST be present".
write(b, "; rel=")
writeTokenOrQuoted(b, link.Rel)
if link.Title != "" {
writeVariform(b, "title", link.Title)
}
if link.Type != "" {
write(b, `; type="`, link.Type, `"`)
}
for _, lang := range link.HrefLang {
write(b, "; hreflang=", lang)
}
if link.Media != "" {
write(b, "; media=")
writeTokenOrQuoted(b, link.Media)
}
for name, value := range link.Ext {
switch strings.ToLower(name) {
case "anchor", "rel", "title", "title*", "type", "hreflang", "media":
continue
default:
name = strings.TrimSuffix(name, "*")
writeVariform(b, name, value)
}
}
}
return b.String()
}