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.len == 0 || path.len == 0 {
		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.len == 0 || path.len == 0 {
		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 picohttpparser.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
}
