net: vfmt everything

pull/10452/head
Delyan Angelov 2021-06-14 10:08:41 +03:00
parent 535dcac8fa
commit d7d9305d96
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
13 changed files with 387 additions and 215 deletions

View File

@ -44,7 +44,6 @@ const (
] ]
vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [ vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [
'vlib/strconv/' /* prevent conflicts, till the new pure V string interpolation is merged */, 'vlib/strconv/' /* prevent conflicts, till the new pure V string interpolation is merged */,
'vlib/net/http/' /* prevent conflicts, till ipv6 support is merged */,
'vlib/term/ui/input.v' /* comment after a struct embed is removed */, 'vlib/term/ui/input.v' /* comment after a struct embed is removed */,
'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */, 'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */,
'vlib/readline/readline_test.v' /* vfmt eats `{ Readline }` from `import readline { Readline }` */, 'vlib/readline/readline_test.v' /* vfmt eats `{ Readline }` from `import readline { Readline }` */,

View File

@ -100,13 +100,13 @@ const aoffset = __offsetof(Addr, addr)
fn (a Addr) len() u32 { fn (a Addr) len() u32 {
match a.family() { match a.family() {
.ip { .ip {
return sizeof(Ip) + aoffset return sizeof(Ip) + net.aoffset
} }
.ip6 { .ip6 {
return sizeof(Ip6) + aoffset return sizeof(Ip6) + net.aoffset
} }
.unix { .unix {
return sizeof(Unix) + aoffset return sizeof(Unix) + net.aoffset
} }
else { else {
panic('Unknown address family') panic('Unknown address family')

View File

@ -14,7 +14,7 @@ $if windows {
} }
fn test_diagnostics() { fn test_diagnostics() {
dump(net.aoffset) dump(aoffset)
eprintln('--------') eprintln('--------')
in6 := C.sockaddr_in6{} in6 := C.sockaddr_in6{}
our_ip6 := Ip6{} our_ip6 := Ip6{}
@ -72,17 +72,17 @@ fn test_sizes_unix_sun_path() {
} }
fn test_offsets_ipv6() { fn test_offsets_ipv6() {
assert __offsetof(C.sockaddr_in6, sin6_addr) == __offsetof(Ip6, addr) + net.aoffset assert __offsetof(C.sockaddr_in6, sin6_addr) == __offsetof(Ip6, addr) + aoffset
assert __offsetof(C.sockaddr_in6, sin6_port) == __offsetof(Ip6, port) + net.aoffset assert __offsetof(C.sockaddr_in6, sin6_port) == __offsetof(Ip6, port) + aoffset
} }
fn test_offsets_ipv4() { fn test_offsets_ipv4() {
assert __offsetof(C.sockaddr_in, sin_addr) == __offsetof(Ip, addr) + net.aoffset assert __offsetof(C.sockaddr_in, sin_addr) == __offsetof(Ip, addr) + aoffset
assert __offsetof(C.sockaddr_in, sin_port) == __offsetof(Ip, port) + net.aoffset assert __offsetof(C.sockaddr_in, sin_port) == __offsetof(Ip, port) + aoffset
} }
fn test_offsets_unix() { fn test_offsets_unix() {
assert __offsetof(C.sockaddr_un, sun_path) == __offsetof(Unix, path) + net.aoffset assert __offsetof(C.sockaddr_un, sun_path) == __offsetof(Unix, path) + aoffset
} }
fn test_sizes_ipv6() { fn test_sizes_ipv6() {

View File

@ -7,6 +7,7 @@ import strings
// followed by \r\n as a line separator, // followed by \r\n as a line separator,
// followed by a chunk of data of the given size. // followed by a chunk of data of the given size.
// The end is marked with a chunk with size 0. // The end is marked with a chunk with size 0.
struct ChunkScanner { struct ChunkScanner {
mut: mut:
pos int pos int
@ -23,7 +24,7 @@ fn (mut s ChunkScanner) read_chunk_size() int {
if !c.is_hex_digit() { if !c.is_hex_digit() {
break break
} }
n = n<<4 n = n << 4
n += int(unhex(c)) n += int(unhex(c))
s.pos++ s.pos++
} }
@ -33,11 +34,9 @@ fn (mut s ChunkScanner) read_chunk_size() int {
fn unhex(c byte) byte { fn unhex(c byte) byte {
if `0` <= c && c <= `9` { if `0` <= c && c <= `9` {
return c - `0` return c - `0`
} } else if `a` <= c && c <= `f` {
else if `a` <= c && c <= `f` {
return c - `a` + 10 return c - `a` + 10
} } else if `A` <= c && c <= `F` {
else if `A` <= c && c <= `F` {
return c - `A` + 10 return c - `A` + 10
} }
return 0 return 0

View File

@ -10,20 +10,21 @@ pub struct Cookie {
pub mut: pub mut:
name string name string
value string value string
path string // optional path string // optional
domain string // optional domain string // optional
expires time.Time // optional expires time.Time // optional
raw_expires string // for reading cookies only. optional. raw_expires string // for reading cookies only. optional.
// max_age=0 means no 'Max-Age' attribute specified. // max_age=0 means no 'Max-Age' attribute specified.
// max_age<0 means delete cookie now, equivalently 'Max-Age: 0' // 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>0 means Max-Age attribute present and given in seconds
max_age int max_age int
secure bool secure bool
http_only bool http_only bool
same_site SameSite same_site SameSite
raw string raw string
unparsed []string // Raw text of unparsed attribute-value pairs unparsed []string // Raw text of unparsed attribute-value pairs
} }
// SameSite allows a server to define a cookie attribute making it impossible for // 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 // 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 // goal is to mitigate the risk of cross-origin information leakage, and provide
@ -61,12 +62,10 @@ pub fn read_set_cookies(h map[string][]string) []&Cookie {
if !is_cookie_name_valid(name) { if !is_cookie_name_valid(name) {
continue continue
} }
value := parse_cookie_value(raw_value, true) or { value := parse_cookie_value(raw_value, true) or { continue }
continue mut c := &Cookie{
} name: name
mut c := &Cookie{ value: value
name: name,
value: value,
raw: line raw: line
} }
for i, _ in parts { for i, _ in parts {
@ -182,10 +181,11 @@ pub fn read_cookies(h map[string][]string, filter string) []&Cookie {
if filter != '' && filter != name { if filter != '' && filter != name {
continue continue
} }
val = parse_cookie_value(val, true) or { val = parse_cookie_value(val, true) or { continue }
continue cookies << &Cookie{
name: name
value: val
} }
cookies << &Cookie{name: name, value: val}
} }
} }
return cookies return cookies
@ -203,7 +203,8 @@ pub fn (c &Cookie) str() string {
// extra_cookie_length derived from typical length of cookie attributes // extra_cookie_length derived from typical length of cookie attributes
// see RFC 6265 Sec 4.1. // see RFC 6265 Sec 4.1.
extra_cookie_length := 110 extra_cookie_length := 110
mut b := strings.new_builder(c.name.len + c.value.len + c.domain.len + c.path.len + extra_cookie_length) mut b := strings.new_builder(c.name.len + c.value.len + c.domain.len + c.path.len +
extra_cookie_length)
b.write_string(c.name) b.write_string(c.name)
b.write_string('=') b.write_string('=')
b.write_string(sanitize_cookie_value(c.value)) b.write_string(sanitize_cookie_value(c.value))
@ -229,7 +230,7 @@ pub fn (c &Cookie) str() string {
} }
if c.expires.year > 1600 { if c.expires.year > 1600 {
e := c.expires e := c.expires
time_str := '${e.weekday_str()}, ${e.day.str()} ${e.smonth()} ${e.year} ${e.hhmmss()} GMT' time_str := '$e.weekday_str(), $e.day.str() $e.smonth() $e.year $e.hhmmss() GMT'
b.write_string('; expires=') b.write_string('; expires=')
b.write_string(time_str) b.write_string(time_str)
} }
@ -264,9 +265,9 @@ pub fn (c &Cookie) str() string {
return b.str() return b.str()
} }
fn sanitize(valid fn(byte) bool, v string) string { fn sanitize(valid fn (byte) bool, v string) string {
mut ok := true mut ok := true
for i in 0..v.len { for i in 0 .. v.len {
if valid(v[i]) { if valid(v[i]) {
continue continue
} }
@ -370,7 +371,7 @@ pub fn is_cookie_domain_name(_s string) bool {
} }
part_len = 0 part_len = 0
} else { } else {
return false return false
} }
last = c last = c
} }
@ -386,7 +387,7 @@ fn parse_cookie_value(_raw string, allow_double_quote bool) ?string {
if allow_double_quote && raw.len > 1 && raw[0] == `"` && raw[raw.len - 1] == `"` { if allow_double_quote && raw.len > 1 && raw[0] == `"` && raw[raw.len - 1] == `"` {
raw = raw.substr(1, raw.len - 1) raw = raw.substr(1, raw.len - 1)
} }
for i in 0..raw.len { for i in 0 .. raw.len {
if !valid_cookie_value_byte(raw[i]) { if !valid_cookie_value_byte(raw[i]) {
return error('http.cookie: invalid cookie value') return error('http.cookie: invalid cookie value')
} }

View File

@ -2,43 +2,66 @@ import net.http
struct SetCookieTestCase { struct SetCookieTestCase {
cookie &http.Cookie cookie &http.Cookie
raw string raw string
} }
struct ReadSetCookiesTestCase { struct ReadSetCookiesTestCase {
header map[string][]string header map[string][]string
cookies []&http.Cookie cookies []&http.Cookie
} }
struct AddCookieTestCase { struct AddCookieTestCase {
cookie []&http.Cookie cookie []&http.Cookie
raw string raw string
} }
const ( const (
write_set_cookie_tests = [ write_set_cookie_tests = [
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-1', value: 'v1'}, cookie: &http.Cookie{
name: 'cookie-1'
value: 'v1'
}
raw: 'cookie-1=v1' raw: 'cookie-1=v1'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-2', value: 'two', max_age: 3600}, cookie: &http.Cookie{
name: 'cookie-2'
value: 'two'
max_age: 3600
}
raw: 'cookie-2=two; Max-Age=3600' raw: 'cookie-2=two; Max-Age=3600'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-3', value: 'three', domain: '.example.com'}, cookie: &http.Cookie{
name: 'cookie-3'
value: 'three'
domain: '.example.com'
}
raw: 'cookie-3=three; domain=example.com' raw: 'cookie-3=three; domain=example.com'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-4', value: 'four', path: '/restricted/'}, cookie: &http.Cookie{
name: 'cookie-4'
value: 'four'
path: '/restricted/'
}
raw: 'cookie-4=four; path=/restricted/' raw: 'cookie-4=four; path=/restricted/'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-5', value: 'five', domain: 'wrong;bad.abc'}, cookie: &http.Cookie{
name: 'cookie-5'
value: 'five'
domain: 'wrong;bad.abc'
}
raw: 'cookie-5=five' raw: 'cookie-5=five'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-6', value: 'six', domain: 'bad-.abc'}, cookie: &http.Cookie{
name: 'cookie-6'
value: 'six'
domain: 'bad-.abc'
}
raw: 'cookie-6=six' raw: 'cookie-6=six'
}, },
// SetCookieTestCase{ // SetCookieTestCase{
@ -46,7 +69,11 @@ const (
// raw: 'cookie-7=seven; domain=127.0.0.1' // raw: 'cookie-7=seven; domain=127.0.0.1'
// }, // },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-8', value: 'eight', domain: '::1'}, cookie: &http.Cookie{
name: 'cookie-8'
value: 'eight'
domain: '::1'
}
raw: 'cookie-8=eight' raw: 'cookie-8=eight'
}, },
// { // {
@ -63,106 +90,181 @@ const (
// raw: 'cookie-11=invalid-expiry' // raw: 'cookie-11=invalid-expiry'
// }, // },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-12', value: 'samesite-default', same_site: .same_site_default_mode}, cookie: &http.Cookie{
name: 'cookie-12'
value: 'samesite-default'
same_site: .same_site_default_mode
}
raw: 'cookie-12=samesite-default; SameSite' raw: 'cookie-12=samesite-default; SameSite'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-13', value: 'samesite-lax', same_site: .same_site_lax_mode}, cookie: &http.Cookie{
name: 'cookie-13'
value: 'samesite-lax'
same_site: .same_site_lax_mode
}
raw: 'cookie-13=samesite-lax; SameSite=Lax' raw: 'cookie-13=samesite-lax; SameSite=Lax'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-14', value: 'samesite-strict', same_site: .same_site_strict_mode}, cookie: &http.Cookie{
name: 'cookie-14'
value: 'samesite-strict'
same_site: .same_site_strict_mode
}
raw: 'cookie-14=samesite-strict; SameSite=Strict' raw: 'cookie-14=samesite-strict; SameSite=Strict'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'cookie-15', value: 'samesite-none', same_site: .same_site_none_mode}, cookie: &http.Cookie{
name: 'cookie-15'
value: 'samesite-none'
same_site: .same_site_none_mode
}
raw: 'cookie-15=samesite-none; SameSite=None' raw: 'cookie-15=samesite-none; SameSite=None'
}, },
// The 'special' cookies have values containing commas or spaces which // The 'special' cookies have values containing commas or spaces which
// are disallowed by RFC 6265 but are common in the wild. // are disallowed by RFC 6265 but are common in the wild.
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-1', value: 'a z'}, cookie: &http.Cookie{
name: 'special-1'
value: 'a z'
}
raw: 'special-1=a z' raw: 'special-1=a z'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-2', value: ' z'}, cookie: &http.Cookie{
name: 'special-2'
value: ' z'
}
raw: 'special-2=" z"' raw: 'special-2=" z"'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-3', value: 'a '}, cookie: &http.Cookie{
name: 'special-3'
value: 'a '
}
raw: 'special-3="a "' raw: 'special-3="a "'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-4', value: ' '}, cookie: &http.Cookie{
name: 'special-4'
value: ' '
}
raw: 'special-4=" "' raw: 'special-4=" "'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-5', value: 'a,z'}, cookie: &http.Cookie{
name: 'special-5'
value: 'a,z'
}
raw: 'special-5=a,z' raw: 'special-5=a,z'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-6', value: ',z'}, cookie: &http.Cookie{
name: 'special-6'
value: ',z'
}
raw: 'special-6=",z"' raw: 'special-6=",z"'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-7', value: 'a,'}, cookie: &http.Cookie{
name: 'special-7'
value: 'a,'
}
raw: 'special-7="a,"' raw: 'special-7="a,"'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'special-8', value: ','}, cookie: &http.Cookie{
name: 'special-8'
value: ','
}
raw: 'special-8=","' raw: 'special-8=","'
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'empty-value', value: ''}, cookie: &http.Cookie{
name: 'empty-value'
value: ''
}
raw: 'empty-value=' raw: 'empty-value='
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: ''}, cookie: &http.Cookie{
name: ''
}
raw: '' raw: ''
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: '\t'}, cookie: &http.Cookie{
name: '\t'
}
raw: '' raw: ''
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: '\r'}, cookie: &http.Cookie{
name: '\r'
}
raw: '' raw: ''
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'a\nb', value: 'v'}, cookie: &http.Cookie{
name: 'a\nb'
value: 'v'
}
raw: '' raw: ''
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'a\nb', value: 'v'}, cookie: &http.Cookie{
name: 'a\nb'
value: 'v'
}
raw: '' raw: ''
}, },
SetCookieTestCase{ SetCookieTestCase{
cookie: &http.Cookie{name: 'a\rb', value: 'v'}, cookie: &http.Cookie{
name: 'a\rb'
value: 'v'
}
raw: '' raw: ''
}, },
] ]
add_cookies_tests = [ add_cookies_tests = [
AddCookieTestCase{ AddCookieTestCase{
cookie: [], cookie: []
raw: "" raw: ''
}, },
AddCookieTestCase{ AddCookieTestCase{
cookie: [&http.Cookie{name: "cookie-1", value: "v1"}], cookie: [&http.Cookie{
raw: "cookie-1=v1" name: 'cookie-1'
value: 'v1'
}]
raw: 'cookie-1=v1'
}, },
AddCookieTestCase{ AddCookieTestCase{
cookie: [ cookie: [&http.Cookie{
&http.Cookie{name: "cookie-1", value: "v1"}, name: 'cookie-1'
&http.Cookie{name: "cookie-2", value: "v2"}, value: 'v1'
&http.Cookie{name: "cookie-3", value: "v3"} },
], &http.Cookie{
raw: "cookie-1=v1; cookie-2=v2; cookie-3=v3" 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 = [ read_set_cookies_tests = [
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["Cookie-1=v1"]}, header: map{
cookies: [&http.Cookie{name: "Cookie-1", value: "v1", raw: "Cookie-1=v1"}] 'Set-Cookie': ['Cookie-1=v1']
}
cookies: [&http.Cookie{
name: 'Cookie-1'
value: 'v1'
raw: 'Cookie-1=v1'
}]
}, },
// ReadSetCookiesTestCase{ // ReadSetCookiesTestCase{
// header: {"Set-Cookie": ["NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"]}, // header: {"Set-Cookie": ["NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"]},
@ -190,83 +292,158 @@ const (
// }] // }]
// }, // },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["ASP.NET_SessionId=foo; path=/; HttpOnly"]}, header: map{
cookies: [&http.Cookie{ 'Set-Cookie': ['ASP.NET_SessionId=foo; path=/; HttpOnly']
name: "ASP.NET_SessionId", }
value: "foo", cookies: [
path: "/", &http.Cookie{
http_only: true, name: 'ASP.NET_SessionId'
raw: "ASP.NET_SessionId=foo; path=/; HttpOnly" value: 'foo'
}] path: '/'
http_only: true
raw: 'ASP.NET_SessionId=foo; path=/; HttpOnly'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["samesitedefault=foo; SameSite"]}, header: map{
cookies: [&http.Cookie{ 'Set-Cookie': ['samesitedefault=foo; SameSite']
name: "samesitedefault", }
value: "foo", cookies: [
same_site: .same_site_default_mode, &http.Cookie{
raw: "samesitedefault=foo; SameSite" name: 'samesitedefault'
}] value: 'foo'
same_site: .same_site_default_mode
raw: 'samesitedefault=foo; SameSite'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["samesitelax=foo; SameSite=Lax"]}, header: map{
cookies: [&http.Cookie{ 'Set-Cookie': ['samesitelax=foo; SameSite=Lax']
name: "samesitelax", }
value: "foo", cookies: [
same_site: .same_site_lax_mode, &http.Cookie{
raw: "samesitelax=foo; SameSite=Lax" name: 'samesitelax'
}] value: 'foo'
same_site: .same_site_lax_mode
raw: 'samesitelax=foo; SameSite=Lax'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["samesitestrict=foo; SameSite=Strict"]}, header: map{
cookies: [&http.Cookie{ 'Set-Cookie': ['samesitestrict=foo; SameSite=Strict']
name: "samesitestrict", }
value: "foo", cookies: [
same_site: .same_site_strict_mode, &http.Cookie{
raw: "samesitestrict=foo; SameSite=Strict" name: 'samesitestrict'
}] value: 'foo'
same_site: .same_site_strict_mode
raw: 'samesitestrict=foo; SameSite=Strict'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ["samesitenone=foo; SameSite=None"]}, header: map{
cookies: [&http.Cookie{ 'Set-Cookie': ['samesitenone=foo; SameSite=None']
name: "samesitenone", }
value: "foo", cookies: [
same_site: .same_site_none_mode, &http.Cookie{
raw: "samesitenone=foo; SameSite=None" 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 // Make sure we can properly read back the Set-Cookie headers we create
// for values containing spaces or commas: // for values containing spaces or commas:
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-1=a z']}, header: map{
cookies: [&http.Cookie{name: "special-1", value: "a z", raw: 'special-1=a z'}] 'Set-Cookie': ['special-1=a z']
}
cookies: [
&http.Cookie{
name: 'special-1'
value: 'a z'
raw: 'special-1=a z'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-2=" z"']}, header: map{
cookies: [&http.Cookie{name: "special-2", value: " z", raw: 'special-2=" z"'}] 'Set-Cookie': ['special-2=" z"']
}, }
cookies: [
ReadSetCookiesTestCase{ &http.Cookie{
header: {"Set-Cookie": ['special-3="a "']}, name: 'special-2'
cookies: [&http.Cookie{name: "special-3", value: "a ", raw: 'special-3="a "'}] value: ' z'
raw: 'special-2=" z"'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-4=" "']}, header: map{
cookies: [&http.Cookie{name: "special-4", value: " ", raw: 'special-4=" "'}] 'Set-Cookie': ['special-3="a "']
}
cookies: [
&http.Cookie{
name: 'special-3'
value: 'a '
raw: 'special-3="a "'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-5=a,z']}, header: map{
cookies: [&http.Cookie{name: "special-5", value: "a,z", raw: 'special-5=a,z'}] 'Set-Cookie': ['special-4=" "']
}
cookies: [
&http.Cookie{
name: 'special-4'
value: ' '
raw: 'special-4=" "'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-6=",z"']}, header: map{
cookies: [&http.Cookie{name: "special-6", value: ",z", raw: 'special-6=",z"'}] 'Set-Cookie': ['special-5=a,z']
}
cookies: [
&http.Cookie{
name: 'special-5'
value: 'a,z'
raw: 'special-5=a,z'
},
]
}, },
ReadSetCookiesTestCase{ ReadSetCookiesTestCase{
header: {"Set-Cookie": ['special-7=","']}, header: map{
cookies: [&http.Cookie{name: "special-7", value: ",", raw: 'special-8=","'}] 'Set-Cookie': ['special-6=",z"']
}
cookies: [
&http.Cookie{
name: 'special-6'
value: ',z'
raw: 'special-6=",z"'
},
]
},
ReadSetCookiesTestCase{
header: map{
'Set-Cookie': ['special-7=","']
}
cookies: [
&http.Cookie{
name: 'special-7'
value: ','
raw: 'special-8=","'
},
]
} }
// TODO(bradfitz): users have reported seeing this in the // TODO(bradfitz): users have reported seeing this in the,,
// wild, but do browsers handle it? RFC 6265 just says "don't // wild, but do browsers handle it? RFC 6265 just says "don't
// do that" (section 3) and then never mentions header folding // do that" (section 3) and then never mentions header folding
// again. // again.

View File

@ -21,10 +21,10 @@ fn download_cb(ptr voidptr, size size_t, nmemb size_t, userp voidptr) {
data.cb(data.written) data.cb(data.written)
//#data->cb(data->written); // TODO //#data->cb(data->written); // TODO
return written return written
*/ */
} }
pub fn download_file_with_progress(url string, out string, cb DownloadFn, cb_finished fn()) { pub fn download_file_with_progress(url string, out string, cb DownloadFn, cb_finished fn ()) {
/* /*
curl := C.curl_easy_init() curl := C.curl_easy_init()
if isnil(curl) { if isnil(curl) {
@ -45,7 +45,7 @@ pub fn download_file_with_progress(url string, out string, cb DownloadFn, cb_fin
C.curl_easy_cleanup(curl) C.curl_easy_cleanup(curl)
C.fclose(fp) C.fclose(fp)
cb_finished() cb_finished()
*/ */
} }
fn empty() { fn empty() {

View File

@ -578,9 +578,9 @@ fn (mut h Header) add_key(key string) {
// Custom error struct for invalid header tokens // Custom error struct for invalid header tokens
struct HeaderKeyError { struct HeaderKeyError {
msg string msg string
code int code int
header string header string
invalid_char byte invalid_char byte
} }

View File

@ -1,9 +1,9 @@
module http module http
fn test_header_new() { fn test_header_new() {
h := http.new_header( h := new_header({ key: .accept, value: 'nothing' },
{key: .accept, value: 'nothing'}, key: .expires
{key: .expires, value: 'yesterday'} value: 'yesterday'
) )
assert h.contains(.accept) assert h.contains(.accept)
assert h.contains(.expires) assert h.contains(.expires)
@ -14,21 +14,21 @@ fn test_header_new() {
} }
fn test_header_invalid_key() { fn test_header_invalid_key() {
mut h := http.new_header() mut h := new_header()
h.add_custom('space is invalid', ':(') or { return } h.add_custom('space is invalid', ':(') or { return }
panic('should have returned') panic('should have returned')
} }
fn test_header_adds_multiple() { fn test_header_adds_multiple() {
mut h := http.new_header() mut h := new_header()
h.add(.accept, 'one') h.add(.accept, 'one')
h.add(.accept, 'two') h.add(.accept, 'two')
assert h.values(.accept) == ['one' 'two'] assert h.values(.accept) == ['one', 'two']
} }
fn test_header_get() ? { fn test_header_get() ? {
mut h := http.new_header(key: .dnt, value: 'one') mut h := new_header(key: .dnt, value: 'one')
h.add_custom('dnt', 'two') ? h.add_custom('dnt', 'two') ?
dnt := h.get_custom('dnt') or { '' } dnt := h.get_custom('dnt') or { '' }
exact := h.get_custom('dnt', exact: true) or { '' } exact := h.get_custom('dnt', exact: true) or { '' }
@ -37,27 +37,27 @@ fn test_header_get() ? {
} }
fn test_header_set() ? { fn test_header_set() ? {
mut h := http.new_header( mut h := new_header({ key: .dnt, value: 'one' },
{key: .dnt, value: 'one'}, key: .dnt
{key: .dnt, value: 'two'} value: 'two'
) )
assert h.values(.dnt) == ['one' 'two'] assert h.values(.dnt) == ['one', 'two']
h.set_custom('DNT', 'three') ? h.set_custom('DNT', 'three') ?
assert h.values(.dnt) == ['three'] assert h.values(.dnt) == ['three']
} }
fn test_header_delete() { fn test_header_delete() {
mut h := http.new_header( mut h := new_header({ key: .dnt, value: 'one' },
{key: .dnt, value: 'one'}, key: .dnt
{key: .dnt, value: 'two'} value: 'two'
) )
assert h.values(.dnt) == ['one' 'two'] assert h.values(.dnt) == ['one', 'two']
h.delete(.dnt) h.delete(.dnt)
assert h.values(.dnt) == [] assert h.values(.dnt) == []
} }
fn test_header_delete_not_existing() { fn test_header_delete_not_existing() {
mut h := http.new_header() mut h := new_header()
assert h.data.len == 0 assert h.data.len == 0
assert h.keys.len == 0 assert h.keys.len == 0
h.delete(.dnt) h.delete(.dnt)
@ -66,7 +66,7 @@ fn test_header_delete_not_existing() {
} }
fn test_custom_header() ? { fn test_custom_header() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('AbC', 'dEf') ? h.add_custom('AbC', 'dEf') ?
h.add_custom('aBc', 'GhI') ? h.add_custom('aBc', 'GhI') ?
assert h.custom_values('AbC', exact: true) == ['dEf'] assert h.custom_values('AbC', exact: true) == ['dEf']
@ -90,7 +90,7 @@ fn test_custom_header() ? {
} }
fn test_contains_custom() ? { fn test_contains_custom() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('Hello', 'world') ? h.add_custom('Hello', 'world') ?
assert h.contains_custom('hello') assert h.contains_custom('hello')
assert h.contains_custom('HELLO') assert h.contains_custom('HELLO')
@ -100,7 +100,7 @@ fn test_contains_custom() ? {
} }
fn test_get_custom() ? { fn test_get_custom() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('Hello', 'world') ? h.add_custom('Hello', 'world') ?
assert h.get_custom('hello') ? == 'world' assert h.get_custom('hello') ? == 'world'
assert h.get_custom('HELLO') ? == 'world' assert h.get_custom('HELLO') ? == 'world'
@ -116,7 +116,7 @@ fn test_get_custom() ? {
} }
fn test_starting_with() ? { fn test_starting_with() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('Hello-1', 'world') ? h.add_custom('Hello-1', 'world') ?
h.add_custom('Hello-21', 'world') ? h.add_custom('Hello-21', 'world') ?
assert h.starting_with('Hello-') ? == 'Hello-1' assert h.starting_with('Hello-') ? == 'Hello-1'
@ -124,7 +124,7 @@ fn test_starting_with() ? {
} }
fn test_custom_values() ? { fn test_custom_values() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('Hello', 'world') ? h.add_custom('Hello', 'world') ?
assert h.custom_values('hello') == ['world'] assert h.custom_values('hello') == ['world']
assert h.custom_values('HELLO') == ['world'] assert h.custom_values('HELLO') == ['world']
@ -134,7 +134,7 @@ fn test_custom_values() ? {
} }
fn test_coerce() ? { fn test_coerce() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add(.accept, 'bar') h.add(.accept, 'bar')
assert h.values(.accept) == ['foo', 'bar'] assert h.values(.accept) == ['foo', 'bar']
@ -146,7 +146,7 @@ fn test_coerce() ? {
} }
fn test_coerce_canonicalize() ? { fn test_coerce_canonicalize() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add(.accept, 'bar') h.add(.accept, 'bar')
assert h.values(.accept) == ['foo', 'bar'] assert h.values(.accept) == ['foo', 'bar']
@ -158,7 +158,7 @@ fn test_coerce_canonicalize() ? {
} }
fn test_coerce_custom() ? { fn test_coerce_custom() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('Hello', 'foo') ? h.add_custom('Hello', 'foo') ?
h.add_custom('hello', 'bar') ? h.add_custom('hello', 'bar') ?
h.add_custom('HELLO', 'baz') ? h.add_custom('HELLO', 'baz') ?
@ -171,7 +171,7 @@ fn test_coerce_custom() ? {
} }
fn test_coerce_canonicalize_custom() ? { fn test_coerce_canonicalize_custom() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('foo-BAR', 'foo') ? h.add_custom('foo-BAR', 'foo') ?
h.add_custom('FOO-bar', 'bar') ? h.add_custom('FOO-bar', 'bar') ?
assert h.custom_values('foo-bar') == ['foo', 'bar'] assert h.custom_values('foo-bar') == ['foo', 'bar']
@ -183,7 +183,7 @@ fn test_coerce_canonicalize_custom() ? {
} }
fn test_render_version() ? { fn test_render_version() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add_custom('Accept', 'bar') ? h.add_custom('Accept', 'bar') ?
h.add(.accept, 'baz') h.add(.accept, 'baz')
@ -202,7 +202,7 @@ fn test_render_version() ? {
} }
fn test_render_coerce() ? { fn test_render_coerce() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add_custom('Accept', 'bar') ? h.add_custom('Accept', 'bar') ?
h.add(.accept, 'baz') h.add(.accept, 'baz')
@ -222,7 +222,7 @@ fn test_render_coerce() ? {
} }
fn test_render_canonicalize() ? { fn test_render_canonicalize() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add_custom('Accept', 'bar') ? h.add_custom('Accept', 'bar') ?
h.add(.accept, 'baz') h.add(.accept, 'baz')
@ -245,7 +245,7 @@ fn test_render_canonicalize() ? {
} }
fn test_render_coerce_canonicalize() ? { fn test_render_coerce_canonicalize() ? {
mut h := http.new_header() mut h := new_header()
h.add_custom('accept', 'foo') ? h.add_custom('accept', 'foo') ?
h.add_custom('Accept', 'bar') ? h.add_custom('Accept', 'bar') ?
h.add(.accept, 'baz') h.add(.accept, 'baz')
@ -265,7 +265,7 @@ fn test_render_coerce_canonicalize() ? {
} }
fn test_str() ? { fn test_str() ? {
mut h := http.new_header() mut h := new_header()
h.add(.accept, 'text/html') h.add(.accept, 'text/html')
h.add_custom('Accept', 'image/jpeg') ? h.add_custom('Accept', 'image/jpeg') ?
h.add_custom('X-custom', 'Hello') ? h.add_custom('X-custom', 'Hello') ?

View File

@ -1,5 +1,6 @@
module http //internal tests have access to *everything in the module* module http
// internal tests have access to *everything in the module*
import json import json
struct HttpbinResponseBody { struct HttpbinResponseBody {
@ -13,7 +14,6 @@ struct HttpbinResponseBody {
url string url string
} }
fn http_fetch_mock(_methods []string, _config FetchConfig) ?[]Response { fn http_fetch_mock(_methods []string, _config FetchConfig) ?[]Response {
url := 'https://httpbin.org/' url := 'https://httpbin.org/'
methods := if _methods.len == 0 { ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] } else { _methods } methods := if _methods.len == 0 { ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] } else { _methods }
@ -23,7 +23,7 @@ fn http_fetch_mock(_methods []string, _config FetchConfig) ?[]Response {
for method in methods { for method in methods {
lmethod := method.to_lower() lmethod := method.to_lower()
config.method = method_from_str(method) config.method = method_from_str(method)
res := fetch(url + lmethod, config)? res := fetch(url + lmethod, config) ?
// TODO // TODO
// body := json.decode(HttpbinResponseBody,res.text)? // body := json.decode(HttpbinResponseBody,res.text)?
result << res result << res
@ -32,40 +32,38 @@ fn http_fetch_mock(_methods []string, _config FetchConfig) ?[]Response {
} }
fn test_http_fetch_bare() { fn test_http_fetch_bare() {
$if !network ? { return } $if !network ? {
responses := http_fetch_mock([], FetchConfig{}) or { return
panic(err)
} }
responses := http_fetch_mock([], FetchConfig{}) or { panic(err) }
for response in responses { for response in responses {
assert response.status_code == 200 assert response.status_code == 200
} }
} }
fn test_http_fetch_with_data() { fn test_http_fetch_with_data() {
$if !network ? { return } $if !network ? {
responses := http_fetch_mock(['POST', 'PUT', 'PATCH', 'DELETE'], { return
data: 'hello world'
}) or {
panic(err)
} }
responses := http_fetch_mock(['POST', 'PUT', 'PATCH', 'DELETE'],
data: 'hello world'
) or { panic(err) }
for response in responses { for response in responses {
payload := json.decode(HttpbinResponseBody,response.text) or { payload := json.decode(HttpbinResponseBody, response.text) or { panic(err) }
panic(err)
}
assert payload.data == 'hello world' assert payload.data == 'hello world'
} }
} }
fn test_http_fetch_with_params() { fn test_http_fetch_with_params() {
$if !network ? { return } $if !network ? {
responses := http_fetch_mock([], { return
params: { }
'a': 'b', responses := http_fetch_mock([],
params: map{
'a': 'b'
'c': 'd' 'c': 'd'
} }
}) or { ) or { panic(err) }
panic(err)
}
for response in responses { for response in responses {
// payload := json.decode(HttpbinResponseBody,response.text) or { // payload := json.decode(HttpbinResponseBody,response.text) or {
// panic(err) // panic(err)
@ -78,14 +76,14 @@ fn test_http_fetch_with_params() {
} }
fn test_http_fetch_with_headers() ? { fn test_http_fetch_with_headers() ? {
$if !network ? { return } $if !network ? {
return
}
mut header := new_header() mut header := new_header()
header.add_custom('Test-Header', 'hello world') ? header.add_custom('Test-Header', 'hello world') ?
responses := http_fetch_mock([], { responses := http_fetch_mock([],
header: header header: header
}) or { ) or { panic(err) }
panic(err)
}
for response in responses { for response in responses {
// payload := json.decode(HttpbinResponseBody,response.text) or { // payload := json.decode(HttpbinResponseBody,response.text) or {
// panic(err) // panic(err)

View File

@ -15,9 +15,7 @@ fn test_http_get_from_vlang_utc_now() {
urls := ['http://vlang.io/utc_now', 'https://vlang.io/utc_now'] urls := ['http://vlang.io/utc_now', 'https://vlang.io/utc_now']
for url in urls { for url in urls {
println('Test getting current time from $url by http.get') println('Test getting current time from $url by http.get')
res := http.get(url) or { res := http.get(url) or { panic(err) }
panic(err)
}
assert 200 == res.status_code assert 200 == res.status_code
assert res.text.len > 0 assert res.text.len > 0
assert res.text.int() > 1566403696 assert res.text.int() > 1566403696
@ -39,9 +37,7 @@ fn test_public_servers() {
] ]
for url in urls { for url in urls {
println('Testing http.get on public url: $url ') println('Testing http.get on public url: $url ')
res := http.get(url) or { res := http.get(url) or { panic(err) }
panic(err)
}
assert 200 == res.status_code assert 200 == res.status_code
assert res.text.len > 0 assert res.text.len > 0
} }
@ -53,9 +49,7 @@ fn test_relative_redirects() {
} $else { } $else {
return return
} // tempfix periodic: httpbin relative redirects are broken } // tempfix periodic: httpbin relative redirects are broken
res := http.get('https://httpbin.org/relative-redirect/3?abc=xyz') or { res := http.get('https://httpbin.org/relative-redirect/3?abc=xyz') or { panic(err) }
panic(err)
}
assert 200 == res.status_code assert 200 == res.status_code
assert res.text.len > 0 assert res.text.len > 0
assert res.text.contains('"abc": "xyz"') assert res.text.contains('"abc": "xyz"')

View File

@ -3,7 +3,7 @@
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
module http module http
import chunked import net.http.chunked
// Response represents the result of the request // Response represents the result of the request
pub struct Response { pub struct Response {
@ -23,9 +23,9 @@ fn (mut resp Response) free() {
pub fn (resp Response) bytes() []byte { pub fn (resp Response) bytes() []byte {
// TODO: build []byte directly; this uses two allocations // TODO: build []byte directly; this uses two allocations
// TODO: cookies // TODO: cookies
return ('$resp.version $resp.status_code ${status_from_int(resp.status_code).str()}\r\n' + return ('$resp.version $resp.status_code ${status_from_int(resp.status_code).str()}\r\n' + '${resp.header.render(
'${resp.header.render(version: resp.version)}\r\n' + version: resp.version
'$resp.text').bytes() )}\r\n' + '$resp.text').bytes()
} }
// Parse a raw HTTP response into a Response object // Parse a raw HTTP response into a Response object
@ -66,7 +66,9 @@ pub fn parse_response(resp string) Response {
parts := cookie.split_nth('=', 2) parts := cookie.split_nth('=', 2)
cookies[parts[0]] = parts[1] cookies[parts[0]] = parts[1]
} }
if header.get(.transfer_encoding) or { '' } == 'chunked' || header.get(.content_length) or { '' } == '' { if header.get(.transfer_encoding) or { '' } == 'chunked' || header.get(.content_length) or {
''
} == '' {
text = chunked.decode(text) text = chunked.decode(text)
} }
return Response{ return Response{

View File

@ -228,7 +228,9 @@ pub fn (code Status) str() string {
// int converts an assigned and known Status to its integral equivalent. // int converts an assigned and known Status to its integral equivalent.
// if a Status is unknown or unassigned, this method will return zero // if a Status is unknown or unassigned, this method will return zero
pub fn (code Status) int() int { pub fn (code Status) int() int {
if code in [.unknown, .unassigned] { return 0 } if code in [.unknown, .unassigned] {
return 0
}
return int(code) return int(code)
} }