2021-06-06 00:43:14 +03:00
|
|
|
module http
|
|
|
|
|
|
|
|
import io
|
|
|
|
|
|
|
|
struct StringReader {
|
|
|
|
text string
|
|
|
|
mut:
|
|
|
|
place int
|
|
|
|
}
|
|
|
|
|
2022-08-08 02:33:25 +03:00
|
|
|
fn (mut s StringReader) read(mut buf []u8) !int {
|
2021-06-06 00:43:14 +03:00
|
|
|
if s.place >= s.text.len {
|
2022-10-27 11:30:08 +03:00
|
|
|
return io.Eof{}
|
2021-06-06 00:43:14 +03:00
|
|
|
}
|
|
|
|
max_bytes := 100
|
|
|
|
end := if s.place + max_bytes >= s.text.len { s.text.len } else { s.place + max_bytes }
|
2022-03-09 21:26:00 +03:00
|
|
|
n := copy(mut buf, s.text[s.place..end].bytes())
|
2021-06-06 00:43:14 +03:00
|
|
|
s.place += n
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
fn reader(s string) &io.BufferedReader {
|
|
|
|
return io.new_buffered_reader(
|
|
|
|
reader: &StringReader{
|
|
|
|
text: s
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_not_http() {
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader__ := reader('hello')
|
|
|
|
parse_request(mut reader__) or { return }
|
2021-06-06 00:43:14 +03:00
|
|
|
panic('should not have parsed')
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_no_headers() {
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader_ := reader('GET / HTTP/1.1\r\n\r\n')
|
2022-11-15 16:53:13 +03:00
|
|
|
req := parse_request(mut reader_) or { panic('did not parse: ${err}') }
|
2021-06-06 00:43:14 +03:00
|
|
|
assert req.method == .get
|
|
|
|
assert req.url == '/'
|
|
|
|
assert req.version == .v1_1
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_two_headers() {
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a\r\nTest2: B\r\n\r\n')
|
2022-11-15 16:53:13 +03:00
|
|
|
req := parse_request(mut reader_) or { panic('did not parse: ${err}') }
|
2021-06-06 00:43:14 +03:00
|
|
|
assert req.header.custom_values('Test1') == ['a']
|
|
|
|
assert req.header.custom_values('Test2') == ['B']
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_two_header_values() {
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a; b\r\nTest2: c\r\nTest2: d\r\n\r\n')
|
2022-11-15 16:53:13 +03:00
|
|
|
req := parse_request(mut reader_) or { panic('did not parse: ${err}') }
|
2021-06-06 00:43:14 +03:00
|
|
|
assert req.header.custom_values('Test1') == ['a; b']
|
|
|
|
assert req.header.custom_values('Test2') == ['c', 'd']
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_body() {
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader_ := reader('GET / HTTP/1.1\r\nTest1: a\r\nTest2: b\r\nContent-Length: 4\r\n\r\nbodyabc')
|
2022-11-15 16:53:13 +03:00
|
|
|
req := parse_request(mut reader_) or { panic('did not parse: ${err}') }
|
2021-06-06 00:43:14 +03:00
|
|
|
assert req.data == 'body'
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_request_line() {
|
|
|
|
method, target, version := parse_request_line('GET /target HTTP/1.1') or {
|
2022-11-15 16:53:13 +03:00
|
|
|
panic('did not parse: ${err}')
|
2021-06-06 00:43:14 +03:00
|
|
|
}
|
|
|
|
assert method == .get
|
|
|
|
assert target.str() == '/target'
|
|
|
|
assert version == .v1_1
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_form() {
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('foo=bar&bar=baz') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'foo': 'bar'
|
|
|
|
'bar': 'baz'
|
|
|
|
}
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('foo=bar=&bar=baz') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'foo': 'bar='
|
|
|
|
'bar': 'baz'
|
|
|
|
}
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('foo=bar%3D&bar=baz') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'foo': 'bar='
|
|
|
|
'bar': 'baz'
|
|
|
|
}
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('foo=b%26ar&bar=baz') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'foo': 'b&ar'
|
|
|
|
'bar': 'baz'
|
|
|
|
}
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('a=b& c=d') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'a': 'b'
|
|
|
|
' c': 'd'
|
|
|
|
}
|
2021-08-04 12:44:41 +03:00
|
|
|
assert parse_form('a=b&c= d ') == {
|
2021-06-06 00:43:14 +03:00
|
|
|
'a': 'b'
|
|
|
|
'c': ' d '
|
|
|
|
}
|
2022-11-10 15:05:34 +03:00
|
|
|
assert parse_form('{json}') == {
|
|
|
|
'json': '{json}'
|
2022-05-06 20:23:36 +03:00
|
|
|
}
|
|
|
|
assert parse_form('{
|
|
|
|
"_id": "76c",
|
|
|
|
"friends": [
|
|
|
|
{
|
|
|
|
"id": 0,
|
|
|
|
"name": "Mason Luna"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"greeting": "Hello."
|
|
|
|
}') == {
|
|
|
|
'json': '{
|
|
|
|
"_id": "76c",
|
|
|
|
"friends": [
|
|
|
|
{
|
|
|
|
"id": 0,
|
|
|
|
"name": "Mason Luna"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"greeting": "Hello."
|
|
|
|
}'
|
|
|
|
}
|
2021-06-06 00:43:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn test_parse_multipart_form() {
|
|
|
|
boundary := '6844a625b1f0b299'
|
|
|
|
names := ['foo', 'fooz']
|
|
|
|
file := 'bar.v'
|
|
|
|
ct := 'application/octet-stream'
|
|
|
|
contents := ['baz', 'buzz']
|
2022-11-15 16:53:13 +03:00
|
|
|
data := "--${boundary}
|
|
|
|
Content-Disposition: form-data; name=\"${names[0]}\"; filename=\"${file}\"\r
|
|
|
|
Content-Type: ${ct}\r
|
2021-11-10 19:15:39 +03:00
|
|
|
\r
|
|
|
|
${contents[0]}\r
|
2022-11-15 16:53:13 +03:00
|
|
|
--${boundary}\r
|
2021-11-10 19:15:39 +03:00
|
|
|
Content-Disposition: form-data; name=\"${names[1]}\"\r
|
|
|
|
\r
|
|
|
|
${contents[1]}\r
|
2022-11-15 16:53:13 +03:00
|
|
|
--${boundary}--\r
|
2021-06-06 00:43:14 +03:00
|
|
|
"
|
|
|
|
form, files := parse_multipart_form(data, boundary)
|
2021-08-04 12:44:41 +03:00
|
|
|
assert files == {
|
2021-09-21 16:20:09 +03:00
|
|
|
names[0]: [
|
|
|
|
FileData{
|
|
|
|
filename: file
|
|
|
|
content_type: ct
|
|
|
|
data: contents[0]
|
|
|
|
},
|
|
|
|
]
|
2021-06-06 00:43:14 +03:00
|
|
|
}
|
|
|
|
|
2021-08-04 12:44:41 +03:00
|
|
|
assert form == {
|
2021-06-06 00:43:14 +03:00
|
|
|
names[1]: contents[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:07:52 +03:00
|
|
|
fn test_parse_multipart_form2() {
|
|
|
|
boundary := '---------------------------27472781931927549291906391339'
|
|
|
|
data := '--${boundary}\r
|
|
|
|
Content-Disposition: form-data; name="username"\r
|
|
|
|
\r
|
|
|
|
admin\r
|
|
|
|
--${boundary}\r
|
|
|
|
Content-Disposition: form-data; name="password"\r
|
|
|
|
\r
|
|
|
|
admin123\r
|
|
|
|
--${boundary}--\r
|
|
|
|
'
|
|
|
|
form, files := parse_multipart_form(data, boundary)
|
|
|
|
for k, v in form {
|
|
|
|
eprintln('> k: ${k} | v: ${v}')
|
|
|
|
eprintln('>> k.bytes(): ${k.bytes()}')
|
|
|
|
eprintln('>> v.bytes(): ${v.bytes()}')
|
|
|
|
}
|
|
|
|
assert form['username'] == 'admin'
|
|
|
|
assert form['password'] == 'admin123'
|
|
|
|
}
|
|
|
|
|
2021-09-16 07:34:07 +03:00
|
|
|
fn test_multipart_form_body() {
|
|
|
|
files := {
|
2021-09-21 16:20:09 +03:00
|
|
|
'foo': [
|
|
|
|
FileData{
|
|
|
|
filename: 'bar.v'
|
|
|
|
content_type: 'application/octet-stream'
|
|
|
|
data: 'baz'
|
|
|
|
},
|
|
|
|
]
|
2021-09-16 07:34:07 +03:00
|
|
|
}
|
|
|
|
form := {
|
|
|
|
'fooz': 'buzz'
|
|
|
|
}
|
|
|
|
|
|
|
|
body, boundary := multipart_form_body(form, files)
|
|
|
|
parsed_form, parsed_files := parse_multipart_form(body, boundary)
|
|
|
|
assert parsed_files == files
|
|
|
|
assert parsed_form == form
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:45:43 +03:00
|
|
|
fn test_parse_large_body() {
|
2023-07-14 16:07:52 +03:00
|
|
|
body := 'A'.repeat(10_001) // greater than max_bytes
|
2022-11-15 16:53:13 +03:00
|
|
|
req := 'GET / HTTP/1.1\r\nContent-Length: ${body.len}\r\n\r\n${body}'
|
2021-07-13 08:06:39 +03:00
|
|
|
mut reader_ := reader(req)
|
2022-10-16 09:28:57 +03:00
|
|
|
result := parse_request(mut reader_)!
|
2021-06-06 00:43:14 +03:00
|
|
|
assert result.data.len == body.len
|
|
|
|
assert result.data == body
|
|
|
|
}
|