Source file src/pkg/net/http/cookie.go
1
2
3
4
5 package http
6
7 import (
8 "bytes"
9 "fmt"
10 "strconv"
11 "strings"
12 "time"
13 )
14
15
16
17
18
19
20
21 type Cookie struct {
22 Name string
23 Value string
24 Path string
25 Domain string
26 Expires time.Time
27 RawExpires string
28
29
30
31
32 MaxAge int
33 Secure bool
34 HttpOnly bool
35 Raw string
36 Unparsed []string
37 }
38
39
40
41 func readSetCookies(h Header) []*Cookie {
42 cookies := []*Cookie{}
43 for _, line := range h["Set-Cookie"] {
44 parts := strings.Split(strings.TrimSpace(line), ";")
45 if len(parts) == 1 && parts[0] == "" {
46 continue
47 }
48 parts[0] = strings.TrimSpace(parts[0])
49 j := strings.Index(parts[0], "=")
50 if j < 0 {
51 continue
52 }
53 name, value := parts[0][:j], parts[0][j+1:]
54 if !isCookieNameValid(name) {
55 continue
56 }
57 value, success := parseCookieValue(value)
58 if !success {
59 continue
60 }
61 c := &Cookie{
62 Name: name,
63 Value: value,
64 Raw: line,
65 }
66 for i := 1; i < len(parts); i++ {
67 parts[i] = strings.TrimSpace(parts[i])
68 if len(parts[i]) == 0 {
69 continue
70 }
71
72 attr, val := parts[i], ""
73 if j := strings.Index(attr, "="); j >= 0 {
74 attr, val = attr[:j], attr[j+1:]
75 }
76 lowerAttr := strings.ToLower(attr)
77 parseCookieValueFn := parseCookieValue
78 if lowerAttr == "expires" {
79 parseCookieValueFn = parseCookieExpiresValue
80 }
81 val, success = parseCookieValueFn(val)
82 if !success {
83 c.Unparsed = append(c.Unparsed, parts[i])
84 continue
85 }
86 switch lowerAttr {
87 case "secure":
88 c.Secure = true
89 continue
90 case "httponly":
91 c.HttpOnly = true
92 continue
93 case "domain":
94 c.Domain = val
95
96 continue
97 case "max-age":
98 secs, err := strconv.Atoi(val)
99 if err != nil || secs != 0 && val[0] == '0' {
100 break
101 }
102 if secs <= 0 {
103 c.MaxAge = -1
104 } else {
105 c.MaxAge = secs
106 }
107 continue
108 case "expires":
109 c.RawExpires = val
110 exptime, err := time.Parse(time.RFC1123, val)
111 if err != nil {
112 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
113 if err != nil {
114 c.Expires = time.Time{}
115 break
116 }
117 }
118 c.Expires = exptime.UTC()
119 continue
120 case "path":
121 c.Path = val
122
123 continue
124 }
125 c.Unparsed = append(c.Unparsed, parts[i])
126 }
127 cookies = append(cookies, c)
128 }
129 return cookies
130 }
131
132
133 func SetCookie(w ResponseWriter, cookie *Cookie) {
134 w.Header().Add("Set-Cookie", cookie.String())
135 }
136
137
138
139
140 func (c *Cookie) String() string {
141 var b bytes.Buffer
142 fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
143 if len(c.Path) > 0 {
144 fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))
145 }
146 if len(c.Domain) > 0 {
147 fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))
148 }
149 if c.Expires.Unix() > 0 {
150 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))
151 }
152 if c.MaxAge > 0 {
153 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
154 } else if c.MaxAge < 0 {
155 fmt.Fprintf(&b, "; Max-Age=0")
156 }
157 if c.HttpOnly {
158 fmt.Fprintf(&b, "; HttpOnly")
159 }
160 if c.Secure {
161 fmt.Fprintf(&b, "; Secure")
162 }
163 return b.String()
164 }
165
166
167
168
169
170 func readCookies(h Header, filter string) []*Cookie {
171 cookies := []*Cookie{}
172 lines, ok := h["Cookie"]
173 if !ok {
174 return cookies
175 }
176
177 for _, line := range lines {
178 parts := strings.Split(strings.TrimSpace(line), ";")
179 if len(parts) == 1 && parts[0] == "" {
180 continue
181 }
182
183 parsedPairs := 0
184 for i := 0; i < len(parts); i++ {
185 parts[i] = strings.TrimSpace(parts[i])
186 if len(parts[i]) == 0 {
187 continue
188 }
189 name, val := parts[i], ""
190 if j := strings.Index(name, "="); j >= 0 {
191 name, val = name[:j], name[j+1:]
192 }
193 if !isCookieNameValid(name) {
194 continue
195 }
196 if filter != "" && filter != name {
197 continue
198 }
199 val, success := parseCookieValue(val)
200 if !success {
201 continue
202 }
203 cookies = append(cookies, &Cookie{Name: name, Value: val})
204 parsedPairs++
205 }
206 }
207 return cookies
208 }
209
210 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
211
212 func sanitizeName(n string) string {
213 return cookieNameSanitizer.Replace(n)
214 }
215
216 var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
217
218 func sanitizeValue(v string) string {
219 return cookieValueSanitizer.Replace(v)
220 }
221
222 func unquoteCookieValue(v string) string {
223 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
224 return v[1 : len(v)-1]
225 }
226 return v
227 }
228
229 func isCookieByte(c byte) bool {
230 switch {
231 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
232 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
233 return true
234 }
235 return false
236 }
237
238 func isCookieExpiresByte(c byte) (ok bool) {
239 return isCookieByte(c) || c == ',' || c == ' '
240 }
241
242 func parseCookieValue(raw string) (string, bool) {
243 return parseCookieValueUsing(raw, isCookieByte)
244 }
245
246 func parseCookieExpiresValue(raw string) (string, bool) {
247 return parseCookieValueUsing(raw, isCookieExpiresByte)
248 }
249
250 func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {
251 raw = unquoteCookieValue(raw)
252 for i := 0; i < len(raw); i++ {
253 if !validByte(raw[i]) {
254 return "", false
255 }
256 }
257 return raw, true
258 }
259
260 func isCookieNameValid(raw string) bool {
261 return strings.IndexFunc(raw, isNotToken) < 0
262 }
View as plain text