net.http: add cookie support
parent
080f550625
commit
c6c2fccb23
|
@ -0,0 +1,428 @@
|
||||||
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
module http
|
||||||
|
|
||||||
|
import (
|
||||||
|
time
|
||||||
|
arrays
|
||||||
|
strings
|
||||||
|
)
|
||||||
|
|
||||||
|
pub struct Cookie {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
path string // optional
|
||||||
|
domain string // optional
|
||||||
|
expires time.Time // optional
|
||||||
|
raw_expires string // for reading cookies only. optional.
|
||||||
|
// max_age=0 means no 'Max-Age' attribute specified.
|
||||||
|
// max_age<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||||
|
// max_age>0 means Max-Age attribute present and given in seconds
|
||||||
|
max_age int
|
||||||
|
secure bool
|
||||||
|
http_only bool
|
||||||
|
same_site SameSite
|
||||||
|
raw string
|
||||||
|
unparsed []string // Raw text of unparsed attribute-value pairs
|
||||||
|
}
|
||||||
|
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||||
|
// the browser to send this cookie along with cross-site requests. The main
|
||||||
|
// goal is to mitigate the risk of cross-origin information leakage, and provide
|
||||||
|
// some protection against cross-site request forgery attacks.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
|
||||||
|
pub enum SameSite {
|
||||||
|
same_site_default_mode = 1
|
||||||
|
same_site_lax_mode
|
||||||
|
same_site_strict_mode
|
||||||
|
same_site_none_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses all "Set-Cookie" values from the header `h` and
|
||||||
|
// returns the successfully parsed Cookies.
|
||||||
|
pub fn read_set_cookies(h map[string][]string) []&Cookie {
|
||||||
|
cookies_s := h['Set-Cookie']
|
||||||
|
cookie_count := cookies_s.len
|
||||||
|
if cookie_count == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
mut cookies := []&Cookie
|
||||||
|
for _, line in cookies_s {
|
||||||
|
mut parts := line.trim_space().split(';')
|
||||||
|
if parts.len == 1 && parts[0] == '' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts[0] = parts[0].trim_space()
|
||||||
|
keyval := parts[0].split('=')
|
||||||
|
if keyval.len != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := keyval[0]
|
||||||
|
_value := keyval[1]
|
||||||
|
if !is_cookie_name_valid(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := parse_cookie_value(_value, true) or {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mut c := &Cookie{
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
raw: line
|
||||||
|
}
|
||||||
|
for i, _ in parts {
|
||||||
|
parts[i] = parts[i].trim_space()
|
||||||
|
if parts[i].len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mut attr := parts[i]
|
||||||
|
mut _val := ''
|
||||||
|
if attr.contains('=') {
|
||||||
|
pieces := attr.split('=')
|
||||||
|
attr = pieces[0]
|
||||||
|
_val = pieces[1]
|
||||||
|
}
|
||||||
|
lower_attr := attr.to_lower()
|
||||||
|
val := parse_cookie_value(_val, false) or {
|
||||||
|
c.unparsed << parts[i]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
match lower_attr {
|
||||||
|
'samesite' {
|
||||||
|
lower_val := val.to_lower()
|
||||||
|
match lower_val {
|
||||||
|
'lax' { c.same_site = .same_site_lax_mode }
|
||||||
|
'strict' { c.same_site = .same_site_strict_mode }
|
||||||
|
'none' { c.same_site = .same_site_none_mode }
|
||||||
|
else { c.same_site = .same_site_default_mode }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'secure' {
|
||||||
|
c.secure = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
'httponly' {
|
||||||
|
c.http_only = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
'httponly' {
|
||||||
|
c.http_only = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
'domain' {
|
||||||
|
c.domain = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
'max-age' {
|
||||||
|
mut secs := val.int()
|
||||||
|
if secs != 0 && val[0] != `0` {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if secs <= 0 {
|
||||||
|
secs = -1
|
||||||
|
}
|
||||||
|
c.max_age = secs
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: Fix this once time works better
|
||||||
|
// 'expires' {
|
||||||
|
// c.raw_expires = val
|
||||||
|
// mut exptime := time.parse_iso(val)
|
||||||
|
// if exptime.year == 0 {
|
||||||
|
// exptime = time.parse_iso('Mon, 02-Jan-2006 15:04:05 MST')
|
||||||
|
// }
|
||||||
|
// c.expires = exptime
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
'path' {
|
||||||
|
c.path = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c.unparsed << parts[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookies << c
|
||||||
|
}
|
||||||
|
return cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses all "Cookie" values from the header `h` and
|
||||||
|
// returns the successfully parsed Cookies.
|
||||||
|
//
|
||||||
|
// if `filter` isn't empty, only cookies of that name are returned
|
||||||
|
pub fn read_cookies(h map[string][]string, filter string) []&Cookie {
|
||||||
|
lines := h['Cookie']
|
||||||
|
if lines.len == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
mut cookies := []&Cookie
|
||||||
|
for _, _line in lines {
|
||||||
|
mut line := _line.trim_space()
|
||||||
|
mut part := ''
|
||||||
|
for line.len > 0 {
|
||||||
|
if line.index_any(';') > 0 {
|
||||||
|
_parts := line.split(';')
|
||||||
|
part = _parts[0]
|
||||||
|
line = _parts[1]
|
||||||
|
} else {
|
||||||
|
part = line
|
||||||
|
line = ''
|
||||||
|
}
|
||||||
|
part = part.trim_space()
|
||||||
|
if part.len == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mut name := part
|
||||||
|
mut val := ''
|
||||||
|
if part.contains('=') {
|
||||||
|
_parts := part.split('=')
|
||||||
|
name = _parts[0]
|
||||||
|
val = _parts[1]
|
||||||
|
}
|
||||||
|
if !is_cookie_name_valid(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter != '' && filter != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Circumvent the issue with assigning an `or` expression to an existing value
|
||||||
|
// TODO: Fix when fixed in compiler
|
||||||
|
_val := parse_cookie_value(val, true) or {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val = _val
|
||||||
|
cookies << &Cookie{name: name, value: val}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the serialization of the cookie for use in a Cookie header
|
||||||
|
// (if only Name and Value are set) or a Set-Cookie response
|
||||||
|
// header (if other fields are set).
|
||||||
|
//
|
||||||
|
// If c.name is invalid, the empty string is returned.
|
||||||
|
pub fn (c &Cookie) str() string {
|
||||||
|
if !is_cookie_name_valid(c.name) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
// extra_cookie_length derived from typical length of cookie attributes
|
||||||
|
// see RFC 6265 Sec 4.1.
|
||||||
|
extra_cookie_length := 110
|
||||||
|
mut b := strings.new_builder(c.name.len + c.value.len + c.domain.len + c.path.len + extra_cookie_length)
|
||||||
|
b.write(c.name)
|
||||||
|
b.write('=')
|
||||||
|
b.write(sanitize_cookie_value(c.value))
|
||||||
|
if c.path.len > 0 {
|
||||||
|
b.write('; path=')
|
||||||
|
b.write(sanitize_cookie_path(c.path))
|
||||||
|
}
|
||||||
|
if c.domain.len > 0 {
|
||||||
|
if valid_cookie_domain(c.domain) {
|
||||||
|
// A `domain` containing illegal characters is not
|
||||||
|
// sanitized but simply dropped which turns the cookie
|
||||||
|
// into a host-only cookie. A leading dot is okay
|
||||||
|
// but won't be sent.
|
||||||
|
mut d := c.domain
|
||||||
|
if d[0] == `.` {
|
||||||
|
d = d.substr(1, d.len)
|
||||||
|
}
|
||||||
|
b.write('; domain=')
|
||||||
|
b.write(d)
|
||||||
|
} else {
|
||||||
|
// TODO: Log invalid cookie domain warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.expires.year > 1600 {
|
||||||
|
e := c.expires
|
||||||
|
time_str := '${e.weekday_str()}, ${e.day.str()} ${e.smonth()} ${e.year} ${e.hhmmss()} GMT'
|
||||||
|
b.write('; expires=')
|
||||||
|
b.write(time_str)
|
||||||
|
}
|
||||||
|
// TODO: Fix this. Techically a max age of 0 or less should be 0
|
||||||
|
// We need a way to not have a max age.
|
||||||
|
if c.max_age > 0 {
|
||||||
|
b.write('; Max-Age=')
|
||||||
|
b.write(c.max_age.str())
|
||||||
|
} else if c.max_age < 0 {
|
||||||
|
b.write('; Max-Age=0')
|
||||||
|
}
|
||||||
|
if c.http_only {
|
||||||
|
b.write('; HttpOnly')
|
||||||
|
}
|
||||||
|
if c.secure {
|
||||||
|
b.write('; Secure')
|
||||||
|
}
|
||||||
|
match c.same_site {
|
||||||
|
.same_site_default_mode {
|
||||||
|
b.write('; SameSite')
|
||||||
|
}
|
||||||
|
.same_site_none_mode {
|
||||||
|
b.write('; SameSite=None')
|
||||||
|
}
|
||||||
|
.same_site_lax_mode {
|
||||||
|
b.write('; SameSite=Lax')
|
||||||
|
}
|
||||||
|
.same_site_strict_mode {
|
||||||
|
b.write('; SameSite=Strict')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize(valid fn(byte) bool, v string) string {
|
||||||
|
mut ok := true
|
||||||
|
for i in 0..v.len {
|
||||||
|
if valid(v[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: Warn that we're dropping the invalid byte?
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
// TODO: Use `filter` instead of this nonesense
|
||||||
|
buf := v.bytes()
|
||||||
|
mut bytes := v.bytes()
|
||||||
|
for i, _ in buf {
|
||||||
|
if (!valid(buf[i])) {
|
||||||
|
bytes.delete(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_cookie_name(name string) string {
|
||||||
|
return name.replace_each(['\n', '-', '\r', '-'])
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc6265#section-4.1.1
|
||||||
|
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||||
|
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||||
|
// ; US-ASCII characters excluding CTLs,
|
||||||
|
// ; whitespace DQUOTE, comma, semicolon,
|
||||||
|
// ; and backslash
|
||||||
|
// We loosen this as spaces and commas are common in cookie values
|
||||||
|
// but we produce a quoted cookie-value in when value starts or ends
|
||||||
|
// with a comma or space.
|
||||||
|
pub fn sanitize_cookie_value(v string) string {
|
||||||
|
val := sanitize(valid_cookie_value_byte, v)
|
||||||
|
if v.len == 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
// Check for the existence of a space or comma
|
||||||
|
if val.starts_with(' ') || val.ends_with(' ') || val.starts_with(',') || val.ends_with(',') {
|
||||||
|
return '"$v"'
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_cookie_path(v string) string {
|
||||||
|
return sanitize(valid_cookie_path_byte, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_cookie_value_byte(b byte) bool {
|
||||||
|
return 0x20 <= b && b < 0x7f && b != `"` && b != `;` && b != `\\`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_cookie_path_byte(b byte) bool {
|
||||||
|
return 0x20 <= b && b < 0x7f && b != `!`
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_cookie_domain(v string) bool {
|
||||||
|
if is_cookie_domain_name(v) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// valid_ip := net.parse_ip(v) or {
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
// if valid_ip {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_cookie_domain_name(_s string) bool {
|
||||||
|
mut s := _s
|
||||||
|
if s.len == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.len > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] == `.` {
|
||||||
|
s = s.substr(1, s.len)
|
||||||
|
}
|
||||||
|
mut last := `.`
|
||||||
|
mut ok := false
|
||||||
|
mut part_len := 0
|
||||||
|
for i, _ in s {
|
||||||
|
c := s[i]
|
||||||
|
if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) {
|
||||||
|
// No '_' allowed here (in contrast to package net).
|
||||||
|
ok = true
|
||||||
|
part_len++
|
||||||
|
} else if (`0` <= c && c <= `9`) {
|
||||||
|
// fine
|
||||||
|
part_len++
|
||||||
|
} else if (c == `-`) {
|
||||||
|
// Byte before dash cannot be dot.
|
||||||
|
if last == `.` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
part_len++
|
||||||
|
} else if c == `.` {
|
||||||
|
// Byte before dot cannot be dot, dash.
|
||||||
|
if last == `.` || last == `-` {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if part_len > 63 || part_len == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
part_len = 0
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
last = c
|
||||||
|
}
|
||||||
|
if last == `-` || part_len > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cookie_value(_raw string, allow_double_quote bool) ?string {
|
||||||
|
mut raw := _raw
|
||||||
|
// Strip the quotes, if present
|
||||||
|
if allow_double_quote && raw.len > 1 && raw[0] == `"` && raw[raw.len - 1] == `"` {
|
||||||
|
raw = raw.substr(1, raw.len - 1)
|
||||||
|
}
|
||||||
|
for i in 0..raw.len {
|
||||||
|
if !valid_cookie_value_byte(raw[i]) {
|
||||||
|
return error('http.cookie: invalid cookie value')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_cookie_name_valid(name string) bool {
|
||||||
|
if name == '' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for b in name.bytes() {
|
||||||
|
if !(b in arrays.range<byte>(33, 126)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
import (
|
||||||
|
net.http
|
||||||
|
time
|
||||||
|
)
|
||||||
|
|
||||||
|
struct SetCookieTestCase {
|
||||||
|
cookie &http.Cookie
|
||||||
|
raw string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReadSetCookiesTestCase {
|
||||||
|
header map[string][]string
|
||||||
|
cookies []&http.Cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddCookieTestCase {
|
||||||
|
cookie []&http.Cookie
|
||||||
|
raw string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
write_set_cookie_tests = [
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-1', value: 'v1'},
|
||||||
|
raw: 'cookie-1=v1'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-2', value: 'two', max_age: 3600},
|
||||||
|
raw: 'cookie-2=two; Max-Age=3600'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-3', value: 'three', domain: '.example.com'},
|
||||||
|
raw: 'cookie-3=three; domain=example.com'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-4', value: 'four', path: '/restricted/'},
|
||||||
|
raw: 'cookie-4=four; path=/restricted/'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-5', value: 'five', domain: 'wrong;bad.abc'},
|
||||||
|
raw: 'cookie-5=five'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-6', value: 'six', domain: 'bad-.abc'},
|
||||||
|
raw: 'cookie-6=six'
|
||||||
|
},
|
||||||
|
// SetCookieTestCase{
|
||||||
|
// cookie: &http.Cookie{name: 'cookie-7', value: 'seven', domain: '127.0.0.1'},
|
||||||
|
// raw: 'cookie-7=seven; domain=127.0.0.1'
|
||||||
|
// },
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-8', value: 'eight', domain: '::1'},
|
||||||
|
raw: 'cookie-8=eight'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// cookie: &http.Cookie{name: 'cookie-9', value: 'expiring', expires: time.unix(1257894000, 0)},
|
||||||
|
// 'cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT',
|
||||||
|
// },
|
||||||
|
// According to IETF 6265 Section 5.1.1.5, the year cannot be less than 1601
|
||||||
|
// SetCookieTestCase{
|
||||||
|
// cookie: &http.Cookie{name: 'cookie-10', value: 'expiring-1601', expires: time.parse('Mon, 01 Jan 1601 01:01:01 GMT')},
|
||||||
|
// raw: 'cookie-10=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT'
|
||||||
|
// },
|
||||||
|
// SetCookieTestCase{
|
||||||
|
// cookie: &http.Cookie{name: 'cookie-11', value: 'invalid-expiry', expires: time.parse('Mon, 01 Jan 1600 01:01:01 GMT')},
|
||||||
|
// raw: 'cookie-11=invalid-expiry'
|
||||||
|
// },
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-12', value: 'samesite-default', same_site: .same_site_default_mode},
|
||||||
|
raw: 'cookie-12=samesite-default; SameSite'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-13', value: 'samesite-lax', same_site: .same_site_lax_mode},
|
||||||
|
raw: 'cookie-13=samesite-lax; SameSite=Lax'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-14', value: 'samesite-strict', same_site: .same_site_strict_mode},
|
||||||
|
raw: 'cookie-14=samesite-strict; SameSite=Strict'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'cookie-15', value: 'samesite-none', same_site: .same_site_none_mode},
|
||||||
|
raw: 'cookie-15=samesite-none; SameSite=None'
|
||||||
|
},
|
||||||
|
// The 'special' cookies have values containing commas or spaces which
|
||||||
|
// are disallowed by RFC 6265 but are common in the wild.
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-1', value: 'a z'},
|
||||||
|
raw: 'special-1=a z'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-2', value: ' z'},
|
||||||
|
raw: 'special-2=" z"'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-3', value: 'a '},
|
||||||
|
raw: 'special-3="a "'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-4', value: ' '},
|
||||||
|
raw: 'special-4=" "'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-5', value: 'a,z'},
|
||||||
|
raw: 'special-5=a,z'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-6', value: ',z'},
|
||||||
|
raw: 'special-6=",z"'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-7', value: 'a,'},
|
||||||
|
raw: 'special-7="a,"'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'special-8', value: ','},
|
||||||
|
raw: 'special-8=","'
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'empty-value', value: ''},
|
||||||
|
raw: 'empty-value='
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: ''},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: '\t'},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: '\r'},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'a\nb', value: 'v'},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'a\nb', value: 'v'},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
SetCookieTestCase{
|
||||||
|
cookie: &http.Cookie{name: 'a\rb', value: 'v'},
|
||||||
|
raw: ''
|
||||||
|
},
|
||||||
|
]
|
||||||
|
add_cookies_tests = [
|
||||||
|
AddCookieTestCase{
|
||||||
|
cookie: [],
|
||||||
|
raw: ""
|
||||||
|
},
|
||||||
|
AddCookieTestCase{
|
||||||
|
cookie: [&http.Cookie{name: "cookie-1", value: "v1"}],
|
||||||
|
raw: "cookie-1=v1"
|
||||||
|
},
|
||||||
|
AddCookieTestCase{
|
||||||
|
cookie: [
|
||||||
|
&http.Cookie{name: "cookie-1", value: "v1"},
|
||||||
|
&http.Cookie{name: "cookie-2", value: "v2"},
|
||||||
|
&http.Cookie{name: "cookie-3", value: "v3"}
|
||||||
|
],
|
||||||
|
raw: "cookie-1=v1; cookie-2=v2; cookie-3=v3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
read_set_cookies_tests = [
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["Cookie-1=v1"]},
|
||||||
|
cookies: [&http.Cookie{name: "Cookie-1", value: "v1", raw: "Cookie-1=v1"}]
|
||||||
|
},
|
||||||
|
// ReadSetCookiesTestCase{
|
||||||
|
// header: {"Set-Cookie": ["NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"]},
|
||||||
|
// cookies: [&http.Cookie{
|
||||||
|
// name: "NID",
|
||||||
|
// value: "99=YsDT5i3E-CXax-",
|
||||||
|
// path: "/",
|
||||||
|
// domain: ".google.ch",
|
||||||
|
// http_only: true,
|
||||||
|
// expires: time.parse_iso('Wed, 23-Nov-2011 01:05:03 GMT'),
|
||||||
|
// raw_expires: "Wed, 23-Nov-2011 01:05:03 GMT",
|
||||||
|
// raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// ReadSetCookiesTestCase{
|
||||||
|
// header: {"Set-Cookie": [".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"]},
|
||||||
|
// cookies: [&http.Cookie{
|
||||||
|
// name: ".ASPXAUTH",
|
||||||
|
// value: "7E3AA",
|
||||||
|
// path: "/",
|
||||||
|
// expires: time.parse_iso('Wed, 07-Mar-2012 14:25:06 GMT'),
|
||||||
|
// raw_expires: "Wed, 07-Mar-2012 14:25:06 GMT",
|
||||||
|
// http_only: true,
|
||||||
|
// raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["ASP.NET_SessionId=foo; path=/; HttpOnly"]},
|
||||||
|
cookies: [&http.Cookie{
|
||||||
|
name: "ASP.NET_SessionId",
|
||||||
|
value: "foo",
|
||||||
|
path: "/",
|
||||||
|
http_only: true,
|
||||||
|
raw: "ASP.NET_SessionId=foo; path=/; HttpOnly"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["samesitedefault=foo; SameSite"]},
|
||||||
|
cookies: [&http.Cookie{
|
||||||
|
name: "samesitedefault",
|
||||||
|
value: "foo",
|
||||||
|
same_site: .same_site_default_mode,
|
||||||
|
raw: "samesitedefault=foo; SameSite"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["samesitelax=foo; SameSite=Lax"]},
|
||||||
|
cookies: [&http.Cookie{
|
||||||
|
name: "samesitelax",
|
||||||
|
value: "foo",
|
||||||
|
same_site: .same_site_lax_mode,
|
||||||
|
raw: "samesitelax=foo; SameSite=Lax"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["samesitestrict=foo; SameSite=Strict"]},
|
||||||
|
cookies: [&http.Cookie{
|
||||||
|
name: "samesitestrict",
|
||||||
|
value: "foo",
|
||||||
|
same_site: .same_site_strict_mode,
|
||||||
|
raw: "samesitestrict=foo; SameSite=Strict"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ["samesitenone=foo; SameSite=None"]},
|
||||||
|
cookies: [&http.Cookie{
|
||||||
|
name: "samesitenone",
|
||||||
|
value: "foo",
|
||||||
|
same_site: .same_site_none_mode,
|
||||||
|
raw: "samesitenone=foo; SameSite=None"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
// Make sure we can properly read back the Set-Cookie headers we create
|
||||||
|
// for values containing spaces or commas:
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-1=a z']},
|
||||||
|
cookies: [&http.Cookie{name: "special-1", value: "a z", raw: 'special-1=a z'}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-2=" z"']},
|
||||||
|
cookies: [&http.Cookie{name: "special-2", value: " z", raw: 'special-2=" z"'}]
|
||||||
|
},
|
||||||
|
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-3="a "']},
|
||||||
|
cookies: [&http.Cookie{name: "special-3", value: "a ", raw: 'special-3="a "'}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-4=" "']},
|
||||||
|
cookies: [&http.Cookie{name: "special-4", value: " ", raw: 'special-4=" "'}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-5=a,z']},
|
||||||
|
cookies: [&http.Cookie{name: "special-5", value: "a,z", raw: 'special-5=a,z'}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-6=",z"']},
|
||||||
|
cookies: [&http.Cookie{name: "special-6", value: ",z", raw: 'special-6=",z"'}]
|
||||||
|
},
|
||||||
|
ReadSetCookiesTestCase{
|
||||||
|
header: {"Set-Cookie": ['special-7=","']},
|
||||||
|
cookies: [&http.Cookie{name: "special-7", value: ",", raw: 'special-8=","'}]
|
||||||
|
}
|
||||||
|
// TODO(bradfitz): users have reported seeing this in the
|
||||||
|
// wild, but do browsers handle it? RFC 6265 just says "don't
|
||||||
|
// do that" (section 3) and then never mentions header folding
|
||||||
|
// again.
|
||||||
|
// Header{"Set-Cookie": ["ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"]},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
fn test_write_set_cookies() {
|
||||||
|
for _, tt in write_set_cookie_tests {
|
||||||
|
assert tt.cookie.str() == tt.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_read_set_cookies() {
|
||||||
|
for _, tt in read_set_cookies_tests {
|
||||||
|
h := tt.header['Set-Cookie'][0]
|
||||||
|
c := http.read_set_cookies(tt.header)
|
||||||
|
println(h)
|
||||||
|
println(c[0].str())
|
||||||
|
assert c[0].str() == h
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue