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 }