github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/net/http/cookie.go (about)

     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 && 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  				// 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 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  // 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  	return strings.IndexFunc(raw, isNotToken) < 0
   262  }