Created
October 12, 2025 09:32
-
-
Save Gurpartap/82fa91bbe45f439beb4e5b08894225d6 to your computer and use it in GitHub Desktop.
Revisions
-
Gurpartap created this gist
Oct 12, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,183 @@ package httputil import ( "errors" "net/http" "net/textproto" "strconv" "strings" "time" "unicode/utf8" ) var ( errBlankCookie = errors.New("http: blank cookie") errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie") errInvalidCookieName = errors.New("http: invalid cookie name") errInvalidCookieValue = errors.New("http: invalid cookie value") ) // ParseSetCookieRelaxed mirrors net/http.ParseSetCookie but allows quoted values // containing characters that ParseSetCookie would reject (notably '"'). // The rest of the parsing logic matches the standard library implementation. func ParseSetCookieRelaxed(line string) (*http.Cookie, error) { parts := strings.Split(textproto.TrimString(line), ";") if len(parts) == 1 && parts[0] == "" { return nil, errBlankCookie } parts[0] = textproto.TrimString(parts[0]) name, value, ok := strings.Cut(parts[0], "=") if !ok { return nil, errEqualNotFoundInCookie } name = textproto.TrimString(name) if !isTokenRelaxed(name) { return nil, errInvalidCookieName } value, quoted, ok := parseCookieValueRelaxed(value, true) if !ok { return nil, errInvalidCookieValue } cookie := &http.Cookie{ Name: name, Value: value, Quoted: quoted, Raw: line, } for i := 1; i < len(parts); i++ { parts[i] = textproto.TrimString(parts[i]) if parts[i] == "" { continue } attr, val, _ := strings.Cut(parts[i], "=") lowerAttr, asciiOnly := toLowerASCII(attr) if !asciiOnly { continue } parsedVal, _, ok := parseCookieValueRelaxed(val, false) if !ok { cookie.Unparsed = append(cookie.Unparsed, parts[i]) continue } val = parsedVal handled := true switch lowerAttr { case "samesite": lowerVal, ascii := toLowerASCII(val) if !ascii { cookie.SameSite = http.SameSiteDefaultMode continue } switch lowerVal { case "lax": cookie.SameSite = http.SameSiteLaxMode case "strict": cookie.SameSite = http.SameSiteStrictMode case "none": cookie.SameSite = http.SameSiteNoneMode default: cookie.SameSite = http.SameSiteDefaultMode } continue case "secure": cookie.Secure = true continue case "httponly": cookie.HttpOnly = true continue case "domain": cookie.Domain = val continue case "max-age": secs, parseErr := strconv.Atoi(val) if parseErr != nil || (secs != 0 && len(val) > 0 && val[0] == '0') { handled = false break } if secs <= 0 { secs = -1 } cookie.MaxAge = secs continue case "expires": cookie.RawExpires = val exptime, parseErr := time.Parse(time.RFC1123, val) if parseErr != nil { exptime, parseErr = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) if parseErr != nil { cookie.Expires = time.Time{} handled = false break } } cookie.Expires = exptime.UTC() continue case "path": cookie.Path = val continue case "partitioned": cookie.Partitioned = true continue default: handled = false } if !handled { cookie.Unparsed = append(cookie.Unparsed, parts[i]) } } return cookie, nil } func parseCookieValueRelaxed(raw string, allowDoubleQuote bool) (value string, quoted, ok bool) { if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { raw = raw[1 : len(raw)-1] quoted = true } for i := 0; i < len(raw); i++ { if !validCookieValueByteRelaxed(raw[i]) { return "", quoted, false } } return raw, quoted, true } func validCookieValueByteRelaxed(b byte) bool { return 0x20 <= b && b < 0x7f && b != ';' && b != '\\' } func toLowerASCII(s string) (string, bool) { for i := 0; i < len(s); i++ { if s[i] >= utf8.RuneSelf { return s, false } } return strings.ToLower(s), true } func isTokenRelaxed(v string) bool { if v == "" { return false } for i := 0; i < len(v); i++ { b := v[i] if b >= utf8.RuneSelf { return false } if 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' || '0' <= b && b <= '9' { continue } switch b { case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~': continue } return false } return true }