src/pkg/net/http/cookie.go - The Go Programming Language

Golang

Source file src/pkg/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 && 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		for _, c := range raw {
   262			if !isToken(byte(c)) {
   263				return false
   264			}
   265		}
   266		return true
   267	}