Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,8 @@
|
||||
## Description
|
||||
|
||||
Prefer `import pico_http_parser` for new code. The original
|
||||
`import picohttpparser` path remains available for compatibility.
|
||||
|
||||
`picohttpparser` is V implementation of
|
||||
[picohttpparser](https://github.com/h2o/picohttpparser),
|
||||
which in turn is "a tiny, primitive, fast HTTP request/response parser."
|
||||
@@ -0,0 +1,75 @@
|
||||
module picohttpparser
|
||||
|
||||
// vfmt off
|
||||
const g_digits_lut = [
|
||||
`0`, `0`, `0`, `1`, `0`, `2`, `0`, `3`, `0`, `4`, `0`, `5`, `0`, `6`, `0`, `7`, `0`, `8`, `0`, `9`,
|
||||
`1`, `0`, `1`, `1`, `1`, `2`, `1`, `3`, `1`, `4`, `1`, `5`, `1`, `6`, `1`, `7`, `1`, `8`, `1`, `9`,
|
||||
`2`, `0`, `2`, `1`, `2`, `2`, `2`, `3`, `2`, `4`, `2`, `5`, `2`, `6`, `2`, `7`, `2`, `8`, `2`, `9`,
|
||||
`3`, `0`, `3`, `1`, `3`, `2`, `3`, `3`, `3`, `4`, `3`, `5`, `3`, `6`, `3`, `7`, `3`, `8`, `3`, `9`,
|
||||
`4`, `0`, `4`, `1`, `4`, `2`, `4`, `3`, `4`, `4`, `4`, `5`, `4`, `6`, `4`, `7`, `4`, `8`, `4`, `9`,
|
||||
`5`, `0`, `5`, `1`, `5`, `2`, `5`, `3`, `5`, `4`, `5`, `5`, `5`, `6`, `5`, `7`, `5`, `8`, `5`, `9`,
|
||||
`6`, `0`, `6`, `1`, `6`, `2`, `6`, `3`, `6`, `4`, `6`, `5`, `6`, `6`, `6`, `7`, `6`, `8`, `6`, `9`,
|
||||
`7`, `0`, `7`, `1`, `7`, `2`, `7`, `3`, `7`, `4`, `7`, `5`, `7`, `6`, `7`, `7`, `7`, `8`, `7`, `9`,
|
||||
`8`, `0`, `8`, `1`, `8`, `2`, `8`, `3`, `8`, `4`, `8`, `5`, `8`, `6`, `8`, `7`, `8`, `8`, `8`, `9`,
|
||||
`9`, `0`, `9`, `1`, `9`, `2`, `9`, `3`, `9`, `4`, `9`, `5`, `9`, `6`, `9`, `7`, `9`, `8`, `9`, `9`,
|
||||
]!
|
||||
// vfmt on
|
||||
|
||||
// u64toa converts `value` to an ASCII string and stores it at `buf_start`.
|
||||
// It returns the length of the ASCII string (branch lookup table implementation).
|
||||
// Note: `buf_start` should point to a memory buffer, that is *at least* 9 bytes long.
|
||||
@[direct_array_access; unsafe]
|
||||
pub fn u64toa(buf_start &u8, value u64) !int {
|
||||
mut buf := unsafe { buf_start }
|
||||
// set maximum length to 100MB
|
||||
if value >= 100_000_000 {
|
||||
return error('Maximum size of 100MB exceeded!')
|
||||
}
|
||||
|
||||
v := u32(value)
|
||||
if v < 10_000 {
|
||||
d1 := u32((v / 100) << 1)
|
||||
d2 := u32((v % 100) << 1)
|
||||
unsafe {
|
||||
if v >= 1000 {
|
||||
*buf++ = g_digits_lut[d1]
|
||||
}
|
||||
if v >= 100 {
|
||||
*buf++ = g_digits_lut[d1 + 1]
|
||||
}
|
||||
if v >= 10 {
|
||||
*buf++ = g_digits_lut[d2]
|
||||
}
|
||||
*buf++ = g_digits_lut[d2 + 1]
|
||||
}
|
||||
} else {
|
||||
b := v / 10_000
|
||||
c := v % 10_000
|
||||
|
||||
d1 := u32((b / 100) << 1)
|
||||
d2 := u32((b % 100) << 1)
|
||||
|
||||
d3 := u32((c / 100) << 1)
|
||||
d4 := u32((c % 100) << 1)
|
||||
|
||||
unsafe {
|
||||
if value >= 10_000_000 {
|
||||
*buf++ = g_digits_lut[d1]
|
||||
}
|
||||
if value >= 1_000_000 {
|
||||
*buf++ = g_digits_lut[d1 + 1]
|
||||
}
|
||||
if value >= 100_000 {
|
||||
*buf++ = g_digits_lut[d2]
|
||||
}
|
||||
*buf++ = g_digits_lut[d2 + 1]
|
||||
|
||||
*buf++ = g_digits_lut[d3]
|
||||
*buf++ = g_digits_lut[d3 + 1]
|
||||
*buf++ = g_digits_lut[d4]
|
||||
*buf++ = g_digits_lut[d4 + 1]
|
||||
}
|
||||
}
|
||||
|
||||
return unsafe { buf - buf_start }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
module picohttpparser
|
||||
|
||||
// Test various values under 10,000
|
||||
pub fn test_u64toa_small_values() {
|
||||
for v in [u64(0), 1, 10, 99, 100, 999, 1000, 9999] {
|
||||
mut buf := [10]u8{}
|
||||
len := unsafe { u64toa(&buf[0], v) or { 0 } }
|
||||
|
||||
assert len == expected_len(v)
|
||||
|
||||
// Check the actual string for accuracy
|
||||
assert buf[0..len] == v.str().bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Test various values above 10,000 and error handling
|
||||
pub fn test_u64toa_large_values() {
|
||||
for i, v in [u64(10000), 12345, 99999, 100000, 999999, 12345678, 99_999_999, 100_000_000] {
|
||||
mut buf := [20]u8{}
|
||||
|
||||
len := unsafe {
|
||||
u64toa(&buf[0], v) or {
|
||||
assert err.msg() == 'Maximum size of 100MB exceeded!'
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
if v < 100_000_000 {
|
||||
assert len == expected_len(v)
|
||||
|
||||
assert buf[0..len] == v.str().bytes()
|
||||
} else {
|
||||
assert len == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test edge cases
|
||||
pub fn test_u64toa_edge_cases() {
|
||||
mut buf := [10]u8{}
|
||||
|
||||
// Test zero value
|
||||
len := unsafe {
|
||||
u64toa(&buf[0], 0) or {
|
||||
assert false
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
assert len == 1
|
||||
assert buf[0] == `0`
|
||||
}
|
||||
|
||||
// Helper functions for expected values
|
||||
fn expected_len(v u64) int {
|
||||
if v == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// return int(math.ceil(math.log10(f64(v + 1))))
|
||||
|
||||
mut count := 0
|
||||
mut temp := v
|
||||
|
||||
for temp > 0 {
|
||||
temp /= 10
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
module picohttpparser
|
||||
|
||||
// NOTE: picohttpparser is designed for speed. Please do some benchmarks when
|
||||
// you change something in this file
|
||||
|
||||
// token_char_map contains all allowed characters in HTTP headers
|
||||
const token_char_map = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||
'\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0' +
|
||||
'\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1' +
|
||||
'\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0' +
|
||||
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' +
|
||||
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
|
||||
|
||||
fn (mut r Request) phr_parse_request_path(buf_start &u8, buf_end &u8, mut pret Pret) {
|
||||
mut buf := unsafe { buf_start + 0 }
|
||||
|
||||
// ADVANCE_TOKEN
|
||||
method := advance_token(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
$if trace_parse ? {
|
||||
eprintln('method: ${method}')
|
||||
}
|
||||
// skip spaces
|
||||
for {
|
||||
unsafe { buf++ }
|
||||
if *buf != ` ` {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
path := advance_token(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return
|
||||
}
|
||||
$if trace_parse ? {
|
||||
eprintln('path: ${path}')
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
// skip spaces
|
||||
for {
|
||||
unsafe { buf++ }
|
||||
if *buf != ` ` {
|
||||
break
|
||||
}
|
||||
}
|
||||
// validate
|
||||
if method == '' || path == '' {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid method or path'
|
||||
return
|
||||
}
|
||||
r.method = method
|
||||
r.path = path
|
||||
|
||||
pret.ret = unsafe { buf - buf_start }
|
||||
}
|
||||
|
||||
fn (mut r Request) phr_parse_request_path_pipeline(buf_start &u8, buf_end &u8, mut pret Pret) {
|
||||
mut buf := unsafe { buf_start }
|
||||
method := advance_token2(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
path := advance_token2(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
// validate
|
||||
if method == '' || path == '' {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid method or path'
|
||||
return
|
||||
}
|
||||
r.method = method
|
||||
r.path = path
|
||||
|
||||
for buf < buf_end {
|
||||
unsafe { buf++ }
|
||||
// check if following 4 characters are '\r\n\r\n' indicating a new request line
|
||||
if unsafe { *(&u32(buf)) == 0x0a0d0a0d } {
|
||||
unsafe {
|
||||
buf += 4
|
||||
}
|
||||
pret.ret = unsafe { buf - buf_start }
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: no request found'
|
||||
}
|
||||
|
||||
fn (mut r Request) phr_parse_request(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
|
||||
// make copy of `buf_start` that can be mutated
|
||||
mut buf := unsafe { buf_start }
|
||||
|
||||
// skip first empty line (some clients add CRLF after POST content)
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf == `\r` {
|
||||
unsafe { buf++ }
|
||||
// EXPECT_CHAR
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf != `\n` {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||
return unsafe { nil }
|
||||
}
|
||||
}
|
||||
|
||||
// parse request line
|
||||
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
minor_version := parse_http_version(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return unsafe { nil }
|
||||
}
|
||||
$if trace_parse ? {
|
||||
eprintln('minor_version: ${minor_version}')
|
||||
}
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf == `\r` {
|
||||
unsafe { buf++ }
|
||||
// EXPECT_CHAR
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf != `\n` {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe { buf++ }
|
||||
} else if *buf == `\n` {
|
||||
unsafe { buf++ }
|
||||
} else {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expecting "\r\n" after HTTP version'
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
return r.parse_headers(buf, buf_end, mut pret)
|
||||
}
|
||||
|
||||
@[direct_array_access]
|
||||
fn (mut r Request) parse_headers(buf_start &u8, buf_end &u8, mut pret Pret) &u8 {
|
||||
mut buf := unsafe { buf_start }
|
||||
|
||||
mut i := 0
|
||||
|
||||
for i = r.num_headers; i < max_headers; i++ {
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf == `\r` {
|
||||
unsafe { buf++ }
|
||||
// EXPECT_CHAR
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if *buf != `\n` {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe { buf++ }
|
||||
|
||||
break
|
||||
} else if *buf == `\n` {
|
||||
unsafe { buf++ }
|
||||
break
|
||||
}
|
||||
|
||||
if !(*buf == ` ` || *buf == `\t`) {
|
||||
name_start := buf
|
||||
// parsing name, but do not discard SP before colon, see
|
||||
// http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
|
||||
for *buf != `:` {
|
||||
// check if the current character is allowed in an HTTP header
|
||||
if token_char_map[*buf] == 0 {
|
||||
$if trace_parse ? {
|
||||
eprintln('invalid character! ${*buf}')
|
||||
}
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid character in header "${*buf}"'
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe { buf++ }
|
||||
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
}
|
||||
|
||||
name_len := unsafe { buf - name_start }
|
||||
if name_len == 0 {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid header name'
|
||||
return unsafe { nil }
|
||||
}
|
||||
r.headers[i].name = unsafe { tos(name_start, name_len) }
|
||||
|
||||
unsafe { buf++ }
|
||||
for { // CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
if !(*buf == ` ` || *buf == `\t`) {
|
||||
break
|
||||
}
|
||||
unsafe { buf++ }
|
||||
}
|
||||
} else {
|
||||
r.headers[i].name = ''
|
||||
}
|
||||
|
||||
mut value_len := get_token_length_to_eol(buf, buf_end, mut pret)
|
||||
if pret.ret < 0 {
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
// TODO: strip characters
|
||||
value_end := unsafe { buf + value_len }
|
||||
for value_end != buf {
|
||||
c := unsafe { *(value_end - 1) }
|
||||
if !(c == ` ` || c == `\t`) {
|
||||
break
|
||||
}
|
||||
unsafe { value_end-- }
|
||||
}
|
||||
|
||||
r.headers[i].value = unsafe { tos(buf, value_end - buf) }
|
||||
r.num_headers++
|
||||
|
||||
unsafe {
|
||||
buf += pret.ret
|
||||
}
|
||||
}
|
||||
|
||||
if i == max_headers {
|
||||
// too many headers
|
||||
eprintln('Too many headers!')
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: too many headers!'
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
pret.ret = unsafe { buf - buf_start }
|
||||
return buf
|
||||
}
|
||||
|
||||
// is_complete checks if an http request is done
|
||||
fn is_complete(buf_start &u8, buf_end &u8, last_len int, mut pret Pret) &u8 {
|
||||
mut ret_cnt := 0
|
||||
// get the last 3 characters of the request buffer
|
||||
buf := if last_len < 3 { buf_start } else { unsafe { buf_start + last_len - 3 } }
|
||||
|
||||
for {
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
// We expect a line of an http request to end with '\r\n'
|
||||
if *buf == `\r` {
|
||||
unsafe { buf++ }
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
// EXPECT_CHAR_NO_CHECK
|
||||
if *buf != `\n` {
|
||||
// no '\n' after '\r' indicates a parse error
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe { buf++ }
|
||||
|
||||
ret_cnt++
|
||||
} else if *buf == `\n` {
|
||||
unsafe { buf++ }
|
||||
ret_cnt++
|
||||
} else {
|
||||
// other character
|
||||
unsafe { buf++ }
|
||||
ret_cnt = 0
|
||||
}
|
||||
if ret_cnt == 2 {
|
||||
return buf
|
||||
}
|
||||
}
|
||||
|
||||
pret.ret = -2
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
fn parse_http_version(buf_start &u8, buf_end &u8, mut pret Pret) int {
|
||||
// we want at least [HTTP/1.<two chars>] to try to parse
|
||||
if unsafe { buf_end - buf_start } < 9 {
|
||||
pret.ret = -2
|
||||
return 0
|
||||
}
|
||||
if unsafe { tos(buf_start, 7) != 'HTTP/1.' } {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: picohttpparser only supports HTTP/1.x'
|
||||
return 0
|
||||
}
|
||||
|
||||
// PARSE_INT
|
||||
c := unsafe { *(buf_start + 7) }
|
||||
if c < `0` || c > `9` {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid HTTP version'
|
||||
return 0
|
||||
}
|
||||
pret.ret = 8
|
||||
return int(c - `0`)
|
||||
}
|
||||
|
||||
fn get_token_length_to_eol(buf_start &u8, buf_end &u8, mut pret Pret) int {
|
||||
mut buf := unsafe { buf_start }
|
||||
mut token_len := 0
|
||||
|
||||
// find non-printable char within the next 8 bytes
|
||||
// HOT code: (TODO: should be manually inlined)
|
||||
for _likely_(unsafe { buf_end - buf >= 8 }) {
|
||||
for _ in 0 .. 8 {
|
||||
if _unlikely_(!is_printable_ascii(*buf)) {
|
||||
// non printable
|
||||
unsafe {
|
||||
goto non_printable
|
||||
}
|
||||
}
|
||||
unsafe { buf++ }
|
||||
continue
|
||||
|
||||
non_printable:
|
||||
// allow space and horizontal tab
|
||||
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
|
||||
// found clear the line (CTL)
|
||||
unsafe {
|
||||
goto found_ctl
|
||||
}
|
||||
}
|
||||
unsafe { buf++ }
|
||||
}
|
||||
}
|
||||
// remaining characters
|
||||
for {
|
||||
// CHECK_EOF
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return 0
|
||||
}
|
||||
if _likely_(*buf < ` ` && *buf != 9) || _unlikely_(*buf == 127) {
|
||||
// found clear the line (CTL)
|
||||
unsafe {
|
||||
goto found_ctl
|
||||
}
|
||||
}
|
||||
unsafe { buf++ }
|
||||
}
|
||||
|
||||
found_ctl:
|
||||
if _likely_(*buf == `\r`) {
|
||||
unsafe { buf++ }
|
||||
// EXPECT_CHAR
|
||||
if buf == buf_end {
|
||||
pret.ret = -2
|
||||
return 0
|
||||
}
|
||||
if *buf != `\n` {
|
||||
// no '\n' after '\r' indicates a parse error
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expected "\n" after "\r"'
|
||||
return 0
|
||||
}
|
||||
unsafe { buf++ }
|
||||
token_len = unsafe { buf - 2 - buf_start }
|
||||
} else if *buf == `\n` {
|
||||
token_len = unsafe { buf - buf_start }
|
||||
unsafe { buf++ }
|
||||
} else {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: expecting "\r\n" after header'
|
||||
return 0
|
||||
}
|
||||
|
||||
if token_len == 0 {
|
||||
pret.ret = 0
|
||||
return 0
|
||||
}
|
||||
|
||||
pret.ret = unsafe { buf - buf_start }
|
||||
return token_len
|
||||
}
|
||||
|
||||
// following functions are #define in the C version, but inline here for better readability
|
||||
|
||||
@[inline]
|
||||
fn advance_token(tok_start &u8, tok_end &u8, mut pret Pret) string {
|
||||
mut buf := unsafe { tok_start }
|
||||
for *buf != ` ` {
|
||||
if _unlikely_(!is_printable_ascii(*buf)) {
|
||||
if *buf < ` ` || *buf == 127 {
|
||||
pret.ret = -1
|
||||
pret.err = 'error parsing request: invalid character "${*buf}"'
|
||||
return ''
|
||||
}
|
||||
}
|
||||
unsafe { buf++ }
|
||||
// CHECK_EOF
|
||||
if buf == tok_end {
|
||||
pret.ret = -2
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
pret.ret = unsafe { buf - tok_start }
|
||||
return unsafe { tos(tok_start, pret.ret) }
|
||||
}
|
||||
|
||||
// advance_token2 is a less safe version of advance_token
|
||||
@[inline]
|
||||
fn advance_token2(tok_start &u8, _tok_end &u8, mut pret Pret) string {
|
||||
mut len := 0
|
||||
mut i := 0
|
||||
for {
|
||||
if unsafe { *(tok_start + i) == ` ` } {
|
||||
len = i
|
||||
for unsafe { *(tok_start + i) == ` ` } {
|
||||
i++
|
||||
}
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
pret.ret = i
|
||||
return unsafe { tos(tok_start, len) }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn is_printable_ascii(c u8) bool {
|
||||
return u32(c - 32) < 95
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
module picohttpparser
|
||||
|
||||
const max_headers = 100
|
||||
|
||||
pub struct Header {
|
||||
pub mut:
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
pub struct Request {
|
||||
mut:
|
||||
prev_len int
|
||||
pub mut:
|
||||
method string
|
||||
path string
|
||||
headers [max_headers]Header
|
||||
num_headers int
|
||||
body string
|
||||
}
|
||||
|
||||
// Pret contains the nr of bytes read, a negative number indicates an error
|
||||
struct Pret {
|
||||
pub mut:
|
||||
err string
|
||||
// -1 indicates a parse error and -2 means the request is parsed
|
||||
ret int
|
||||
}
|
||||
|
||||
// parse_request parses a raw HTTP request and returns the number of bytes read.
|
||||
// -1 indicates a parse error and -2 means the request is parsed
|
||||
@[inline]
|
||||
pub fn (mut r Request) parse_request(s string) !int {
|
||||
mut buf := s.str
|
||||
buf_end := unsafe { s.str + s.len }
|
||||
|
||||
mut pret := Pret{}
|
||||
// if prev_len != 0, check if the request is complete
|
||||
// (a fast countermeasure against slowloris)
|
||||
if r.prev_len != 0 && unsafe { is_complete(buf, buf_end, r.prev_len, mut pret) == nil } {
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
return pret.ret
|
||||
}
|
||||
|
||||
buf = r.phr_parse_request(buf, buf_end, mut pret)
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
|
||||
if unsafe { buf == nil } {
|
||||
return pret.ret
|
||||
}
|
||||
|
||||
pret.ret = unsafe { buf - s.str }
|
||||
|
||||
r.body = unsafe { (&s.str[pret.ret]).vstring_literal_with_len(s.len - pret.ret) }
|
||||
r.prev_len = s.len
|
||||
|
||||
// return nr of bytes
|
||||
return pret.ret
|
||||
}
|
||||
|
||||
// parse_request_path sets the `path` and `method` fields
|
||||
@[inline]
|
||||
pub fn (mut r Request) parse_request_path(s string) !int {
|
||||
mut buf := s.str
|
||||
buf_end := unsafe { s.str + s.len }
|
||||
|
||||
mut pret := Pret{}
|
||||
r.phr_parse_request_path(buf, buf_end, mut pret)
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
|
||||
return pret.ret
|
||||
}
|
||||
|
||||
// parse_request_path_pipeline can parse the `path` and `method` of HTTP/1.1 pipelines.
|
||||
// Call it again to parse the next request
|
||||
@[inline]
|
||||
pub fn (mut r Request) parse_request_path_pipeline(s string) !int {
|
||||
mut buf := unsafe { s.str + r.prev_len }
|
||||
buf_end := unsafe { s.str + s.len }
|
||||
|
||||
mut pret := Pret{}
|
||||
r.phr_parse_request_path_pipeline(buf, buf_end, mut pret)
|
||||
if pret.ret == -1 {
|
||||
return error(pret.err)
|
||||
}
|
||||
|
||||
if pret.ret > 0 {
|
||||
r.prev_len = pret.ret
|
||||
}
|
||||
return pret.ret
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
module picohttpparser
|
||||
|
||||
pub fn test_parses_a_simple_get_request() {
|
||||
mut req := Request{}
|
||||
parsed := req.parse_request('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') or {
|
||||
assert false, 'error while parse request: ${err}'
|
||||
0
|
||||
}
|
||||
|
||||
assert parsed == 37
|
||||
assert req.method == 'GET'
|
||||
assert req.path == '/'
|
||||
assert req.headers[0].name == 'Host'
|
||||
assert req.headers[0].value == 'example.com'
|
||||
}
|
||||
|
||||
pub fn test_parses_multiple_headers() {
|
||||
mut req := Request{}
|
||||
parsed := req.parse_request('GET /foo?bar=baz HTTP/1.1\r\nHeader1: value1\r\nHeader2: value2\r\n\r\n') or {
|
||||
assert false, 'error while parse request: ${err}'
|
||||
0
|
||||
}
|
||||
assert parsed == 63
|
||||
assert req.headers[1].name == 'Header2'
|
||||
assert req.headers[1].value == 'value2'
|
||||
}
|
||||
|
||||
pub fn test_parses_requests_with_bodies() {
|
||||
mut req := Request{}
|
||||
parsed := req.parse_request('POST /data HTTP/1.1\r\nContent-Length: 10\r\n\r\nsomedata') or {
|
||||
assert false, 'error while parse request: ${err}'
|
||||
0
|
||||
}
|
||||
assert parsed == 43
|
||||
assert req.body == 'somedata'
|
||||
}
|
||||
|
||||
pub fn test_handles_empty_requests() {
|
||||
mut req := Request{}
|
||||
parsed := req.parse_request('') or {
|
||||
assert false, 'error while parse request: ${err}'
|
||||
0
|
||||
}
|
||||
assert parsed == -2
|
||||
}
|
||||
|
||||
pub fn test_handles_incomplete_requests() {
|
||||
mut req := Request{}
|
||||
partial_parsed := req.parse_request('GET /partial') or {
|
||||
assert false, 'error while parse request: ${err}'
|
||||
0
|
||||
}
|
||||
assert partial_parsed == -2
|
||||
assert req.prev_len == 0
|
||||
|
||||
remaining_parsed := req.parse_request(' HTTP/1.1\r\n\r\n') or {
|
||||
assert err.msg() == 'error parsing request: invalid character "13"'
|
||||
0
|
||||
}
|
||||
assert remaining_parsed == 0
|
||||
assert req.method == ''
|
||||
assert req.path == ''
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
module picohttpparser
|
||||
|
||||
$if !windows {
|
||||
#include <sys/socket.h>
|
||||
}
|
||||
|
||||
pub struct Response {
|
||||
pub:
|
||||
fd int
|
||||
date &u8 = unsafe { nil }
|
||||
buf_start &u8 = unsafe { nil }
|
||||
pub mut:
|
||||
buf &u8 = unsafe { nil }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) write_string(s string) {
|
||||
unsafe {
|
||||
vmemcpy(r.buf, s.str, s.len)
|
||||
r.buf += s.len
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) http_ok() &Response {
|
||||
r.write_string('HTTP/1.1 200 OK\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) header(k string, v string) &Response {
|
||||
r.write_string(k)
|
||||
r.write_string(': ')
|
||||
r.write_string(v)
|
||||
r.write_string('\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) header_date() &Response {
|
||||
r.write_string('Date: ')
|
||||
unsafe {
|
||||
vmemcpy(r.buf, r.date, 29)
|
||||
r.buf += 29
|
||||
}
|
||||
r.write_string('\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) header_server() &Response {
|
||||
r.write_string('Server: V\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) content_type(s string) &Response {
|
||||
r.write_string('Content-Type: ')
|
||||
r.write_string(s)
|
||||
r.write_string('\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) html() &Response {
|
||||
r.write_string('Content-Type: text/html\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) plain() &Response {
|
||||
r.write_string('Content-Type: text/plain\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) json() &Response {
|
||||
r.write_string('Content-Type: application/json\r\n')
|
||||
return unsafe { r }
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) body(body string) {
|
||||
r.write_string('Content-Length: ')
|
||||
unsafe {
|
||||
r.buf += u64toa(r.buf, u64(body.len)) or { panic(err) }
|
||||
}
|
||||
r.write_string('\r\n\r\n')
|
||||
r.write_string(body)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) http_404() {
|
||||
r.write_string('HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n')
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) http_405() {
|
||||
r.write_string('HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n')
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) http_500() {
|
||||
r.write_string('HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n')
|
||||
}
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) raw(response string) {
|
||||
r.write_string(response)
|
||||
}
|
||||
|
||||
fn C.send(sockfd i32, buf voidptr, len usize, flags i32) i32
|
||||
|
||||
@[inline]
|
||||
pub fn (mut r Response) end() int {
|
||||
n := int(i64(r.buf) - i64(r.buf_start))
|
||||
// use send instead of write for windows compatibility
|
||||
if C.send(r.fd, r.buf_start, n, 0) != n {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
}
|
||||
Reference in new issue
Block a user