OSDN Git Service

libgo: Update to weekly 2011-11-09.
[pf3gnuchains/gcc-fork.git] / libgo / go / net / http / cookie.go
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package http
6
7 import (
8         "bytes"
9         "fmt"
10         "strconv"
11         "strings"
12         "time"
13 )
14
15 // This implementation is done according to RFC 6265:
16 //
17 //    http://tools.ietf.org/html/rfc6265
18
19 // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
20 // HTTP response or the Cookie header of an HTTP request.
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         // MaxAge=0 means no 'Max-Age' attribute specified. 
30         // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
31         // MaxAge>0 means Max-Age attribute present and given in seconds
32         MaxAge   int
33         Secure   bool
34         HttpOnly bool
35         Raw      string
36         Unparsed []string // Raw text of unparsed attribute-value pairs
37 }
38
39 // readSetCookies parses all "Set-Cookie" values from
40 // the header h and returns the successfully parsed Cookies.
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                                 // TODO: Add domain parsing
96                                 continue
97                         case "max-age":
98                                 secs, err := strconv.Atoi(val)
99                                 if err != nil || secs < 0 || 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
119                                 continue
120                         case "path":
121                                 c.Path = val
122                                 // TODO: Add path parsing
123                                 continue
124                         }
125                         c.Unparsed = append(c.Unparsed, parts[i])
126                 }
127                 cookies = append(cookies, c)
128         }
129         return cookies
130 }
131
132 // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
133 func SetCookie(w ResponseWriter, cookie *Cookie) {
134         w.Header().Add("Set-Cookie", cookie.String())
135 }
136
137 // String returns the serialization of the cookie for use in a Cookie
138 // header (if only Name and Value are set) or a Set-Cookie response
139 // header (if other fields are set).
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 len(c.Expires.Zone) > 0 {
150                 fmt.Fprintf(&b, "; Expires=%s", c.Expires.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 // readCookies parses all "Cookie" values from the header h and
167 // returns the successfully parsed Cookies.
168 //
169 // if filter isn't empty, only cookies of that name are returned
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                 // Per-line attributes
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         for _, c := range raw {
262                 if !isToken(byte(c)) {
263                         return false
264                 }
265         }
266         return true
267 }