github.com/blend/go-sdk@v1.20220411.3/web/read_set_cookies.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"net/http"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  // ReadSetCookies parses all "Set-Cookie" values from
    18  // the header h and returns the successfully parsed Cookies.
    19  /*
    20  It is a verbatim copy of the one found in `net/http` but
    21  exported so you can use it to.
    22  */
    23  func ReadSetCookies(h http.Header) []*http.Cookie {
    24  	cookieCount := len(h["Set-Cookie"])
    25  	if cookieCount == 0 {
    26  		return []*http.Cookie{}
    27  	}
    28  	cookies := make([]*http.Cookie, 0, cookieCount)
    29  	for _, line := range h["Set-Cookie"] {
    30  		parts := strings.Split(strings.TrimSpace(line), ";")
    31  		if len(parts) == 1 && parts[0] == "" {
    32  			continue
    33  		}
    34  		parts[0] = strings.TrimSpace(parts[0])
    35  		j := strings.Index(parts[0], "=")
    36  		if j < 0 {
    37  			continue
    38  		}
    39  		name, value := parts[0][:j], parts[0][j+1:]
    40  		if !isCookieNameValid(name) {
    41  			continue
    42  		}
    43  		value, ok := parseCookieValue(value, true)
    44  		if !ok {
    45  			continue
    46  		}
    47  		c := &http.Cookie{
    48  			Name:  name,
    49  			Value: value,
    50  			Raw:   line,
    51  		}
    52  		for i := 1; i < len(parts); i++ {
    53  			parts[i] = strings.TrimSpace(parts[i])
    54  			if len(parts[i]) == 0 {
    55  				continue
    56  			}
    57  
    58  			attr, val := parts[i], ""
    59  			if j := strings.Index(attr, "="); j >= 0 {
    60  				attr, val = attr[:j], attr[j+1:]
    61  			}
    62  			lowerAttr := strings.ToLower(attr)
    63  			val, ok = parseCookieValue(val, false)
    64  			if !ok {
    65  				c.Unparsed = append(c.Unparsed, parts[i])
    66  				continue
    67  			}
    68  			switch lowerAttr {
    69  			case "samesite":
    70  				lowerVal := strings.ToLower(val)
    71  				switch lowerVal {
    72  				case "lax":
    73  					c.SameSite = http.SameSiteLaxMode
    74  				case "strict":
    75  					c.SameSite = http.SameSiteStrictMode
    76  				default:
    77  					c.SameSite = http.SameSiteDefaultMode
    78  				}
    79  				continue
    80  			case "secure":
    81  				c.Secure = true
    82  				continue
    83  			case "httponly":
    84  				c.HttpOnly = true
    85  				continue
    86  			case "domain":
    87  				c.Domain = val
    88  				continue
    89  			case "max-age":
    90  				secs, err := strconv.Atoi(val)
    91  				if err != nil || secs != 0 && val[0] == '0' {
    92  					break
    93  				}
    94  				if secs <= 0 {
    95  					secs = -1
    96  				}
    97  				c.MaxAge = secs
    98  				continue
    99  			case "expires":
   100  				c.RawExpires = val
   101  				exptime, err := time.Parse(time.RFC1123, val)
   102  				if err != nil {
   103  					exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
   104  					if err != nil {
   105  						c.Expires = time.Time{}
   106  						break
   107  					}
   108  				}
   109  				c.Expires = exptime.UTC()
   110  				continue
   111  			case "path":
   112  				c.Path = val
   113  				continue
   114  			}
   115  			c.Unparsed = append(c.Unparsed, parts[i])
   116  		}
   117  		cookies = append(cookies, c)
   118  	}
   119  	return cookies
   120  }
   121  
   122  func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
   123  	// Strip the quotes, if present.
   124  	if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
   125  		raw = raw[1 : len(raw)-1]
   126  	}
   127  	for i := 0; i < len(raw); i++ {
   128  		if !validCookieValueByte(raw[i]) {
   129  			return "", false
   130  		}
   131  	}
   132  	return raw, true
   133  }
   134  
   135  func isCookieNameValid(raw string) bool {
   136  	if raw == "" {
   137  		return false
   138  	}
   139  	return strings.IndexFunc(raw, isNotToken) < 0
   140  }
   141  
   142  func validCookieValueByte(b byte) bool {
   143  	return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
   144  }
   145  
   146  func isNotToken(r rune) bool {
   147  	return !isTokenRune(r)
   148  }
   149  
   150  var isTokenTable = [127]bool{
   151  	'!':  true,
   152  	'#':  true,
   153  	'$':  true,
   154  	'%':  true,
   155  	'&':  true,
   156  	'\'': true,
   157  	'*':  true,
   158  	'+':  true,
   159  	'-':  true,
   160  	'.':  true,
   161  	'0':  true,
   162  	'1':  true,
   163  	'2':  true,
   164  	'3':  true,
   165  	'4':  true,
   166  	'5':  true,
   167  	'6':  true,
   168  	'7':  true,
   169  	'8':  true,
   170  	'9':  true,
   171  	'A':  true,
   172  	'B':  true,
   173  	'C':  true,
   174  	'D':  true,
   175  	'E':  true,
   176  	'F':  true,
   177  	'G':  true,
   178  	'H':  true,
   179  	'I':  true,
   180  	'J':  true,
   181  	'K':  true,
   182  	'L':  true,
   183  	'M':  true,
   184  	'N':  true,
   185  	'O':  true,
   186  	'P':  true,
   187  	'Q':  true,
   188  	'R':  true,
   189  	'S':  true,
   190  	'T':  true,
   191  	'U':  true,
   192  	'W':  true,
   193  	'V':  true,
   194  	'X':  true,
   195  	'Y':  true,
   196  	'Z':  true,
   197  	'^':  true,
   198  	'_':  true,
   199  	'`':  true,
   200  	'a':  true,
   201  	'b':  true,
   202  	'c':  true,
   203  	'd':  true,
   204  	'e':  true,
   205  	'f':  true,
   206  	'g':  true,
   207  	'h':  true,
   208  	'i':  true,
   209  	'j':  true,
   210  	'k':  true,
   211  	'l':  true,
   212  	'm':  true,
   213  	'n':  true,
   214  	'o':  true,
   215  	'p':  true,
   216  	'q':  true,
   217  	'r':  true,
   218  	's':  true,
   219  	't':  true,
   220  	'u':  true,
   221  	'v':  true,
   222  	'w':  true,
   223  	'x':  true,
   224  	'y':  true,
   225  	'z':  true,
   226  	'|':  true,
   227  	'~':  true,
   228  }
   229  
   230  func isTokenRune(r rune) bool {
   231  	i := int(r)
   232  	return i < len(isTokenTable) && isTokenTable[i]
   233  }