link.go raw
1 package link
2
3 import (
4 "net/http"
5 "regexp"
6 "strings"
7 )
8
9 var (
10 commaRegexp = regexp.MustCompile(`,\s{0,}`)
11 valueCommaRegexp = regexp.MustCompile(`([^"]),`)
12 equalRegexp = regexp.MustCompile(` *= *`)
13 keyRegexp = regexp.MustCompile(`[a-z*]+`)
14 linkRegexp = regexp.MustCompile(`\A<(.+)>;(.+)\z`)
15 semiRegexp = regexp.MustCompile(`;\s{0,}`)
16 valRegexp = regexp.MustCompile(`"+([^"]+)"+`)
17 )
18
19 // Group returned by Parse, contains multiple links indexed by "rel".
20 type Group map[string]*Link
21
22 // Link contains a Link item with URI, Rel, and other non-URI components in Extra.
23 type Link struct {
24 URI string
25 Rel string
26 Extra map[string]string
27 }
28
29 // String returns the URI.
30 func (l *Link) String() string {
31 return l.URI
32 }
33
34 // ParseRequest parses the provided *http.Request into a Group.
35 func ParseRequest(req *http.Request) Group {
36 if req == nil {
37 return nil
38 }
39
40 return ParseHeader(req.Header)
41 }
42
43 // ParseResponse parses the provided *http.Response into a Group.
44 func ParseResponse(resp *http.Response) Group {
45 if resp == nil {
46 return nil
47 }
48
49 return ParseHeader(resp.Header)
50 }
51
52 // ParseHeader retrieves the Link header from the provided http.Header and parses it into a Group.
53 func ParseHeader(h http.Header) Group {
54 if headers, found := h["Link"]; found {
55 return Parse(strings.Join(headers, ", "))
56 }
57
58 return nil
59 }
60
61 // Parse parses the provided string into a Group.
62 func Parse(s string) Group {
63 if s == "" {
64 return nil
65 }
66
67 s = valueCommaRegexp.ReplaceAllString(s, "$1")
68
69 group := Group{}
70
71 for _, l := range commaRegexp.Split(s, -1) {
72 linkMatches := linkRegexp.FindAllStringSubmatch(l, -1)
73
74 if len(linkMatches) == 0 {
75 return nil
76 }
77
78 pieces := linkMatches[0]
79
80 link := &Link{URI: pieces[1], Extra: map[string]string{}}
81
82 for _, extra := range semiRegexp.Split(pieces[2], -1) {
83 vals := equalRegexp.Split(extra, -1)
84
85 if len(vals) != 2 {
86 continue
87 }
88
89 val := strings.TrimSpace(vals[1])
90 key := keyRegexp.FindString(vals[0])
91 vsm := valRegexp.FindStringSubmatch(vals[1])
92
93 if len(vsm) == 2 {
94 val = vsm[1]
95 }
96
97 if key == "rel" {
98 vals := strings.Split(val, " ")
99 rels := []string{vals[0]}
100
101 if len(vals) > 1 {
102 for _, v := range vals[1:] {
103 if !strings.HasPrefix(v, "http") {
104 rels = append(rels, v)
105 }
106 }
107 }
108
109 rel := strings.Join(rels, " ")
110
111 link.Rel = rel
112 group[rel] = link
113 } else {
114 link.Extra[key] = val
115 }
116 }
117 }
118
119 return group
120 }
121