///|
pub(all) struct Request {
verb : String
path : String
/// Raw query string without the leading `?` (empty when absent).
query : String
headers : Array[(String, String)]
body : String
}
///|
pub fn Request::new(verb : String, path : String) -> Request {
let split = split_query(path)
{ verb, path: split.0, query: split.1, headers: [], body: "", }
}
///|
/// Copy of the request with `body` replaced (query and headers kept).
pub fn Request::with_body(self : Request, body : String) -> Request {
{
verb: self.verb,
path: self.path,
query: self.query,
headers: self.headers,
body,
}
}
///|
pub fn Request::header(self : Request, name : String) -> String? {
let mut found : String? = None
for entry in self.headers {
if entry.0 == name {
found = Some(entry.1)
}
}
found
}
///|
/// Look up a query-string parameter by name (percent-decoded).
pub fn Request::query_param(self : Request, name : String) -> String? {
query_param(self.query, name)
}
///|
fn split_query(target : String) -> (String, String) {
let mut at = -1
let mut i = 0
for ch in target {
if ch == '?' {
at = i
break
}
i = i + 1
}
if at < 0 {
(target, "")
} else {
(target[:at].to_owned(), target[at + 1:].to_owned())
}
}
///|
fn encode_utf8_bytes(s : String) -> Bytes {
@encoding.encode(UTF8, s)
}
///|
fn decode_utf8_bytes(b : Bytes) -> String {
let sb = StringBuilder()
@encoding.decode_to(b[:], sb, encoding=UTF8) catch {
_err => ()
}
sb.to_string()
}
///|
/// Decode "name=value" into its two URL-decoded parts.
fn decode_pair(text : String) -> (String, String) {
let mut at = -1
let mut i = 0
for ch in text {
if ch == '=' {
at = i
break
}
i = i + 1
}
if at < 0 {
(decode_url_encoded(text), "")
} else {
(
decode_url_encoded(text[:at].to_owned()),
decode_url_encoded(text[at + 1:].to_owned()),
)
}
}
///|
/// First value of `name` in a query/form string ("a=1&b=2"), decoded.
pub fn query_param(source : String, name : String) -> String? {
for pair in source.split("&") {
let text = pair.to_owned()
let decoded = decode_pair(text)
if decoded.0 == name {
return Some(decoded.1)
}
}
None
}
///|
/// Decode a URL-encoded string: '+' becomes space, %XX becomes the byte.
pub fn decode_url_encoded(s : String) -> String {
let chars : Array[Byte] = []
let bytes = encode_utf8_bytes(s)
let len = bytes.length()
let mut i = 0
while i < len {
let b = bytes[i]
if b == 37 && i + 2 < len {
match hex_value(bytes[i + 1], bytes[i + 2]) {
Some(v) => {
chars.push(v)
i = i + 3
}
None => {
chars.push(b)
i = i + 1
}
}
} else if b == 43 {
chars.push(32)
i = i + 1
} else {
chars.push(b)
i = i + 1
}
}
decode_utf8_bytes(Bytes::from_iter(chars.iter()))
}
///|
fn hex_value(high : Byte, low : Byte) -> Byte? {
let h = hex_digit(high)
let l = hex_digit(low)
match (h, l) {
(Some(hh), Some(ll)) => Some(hh * 16 + ll)
_ => None
}
}
///|
fn hex_digit(b : Byte) -> Byte? {
if b >= 48 && b <= 57 {
Some(b - 48)
} else if b >= 65 && b <= 70 {
Some(b - 55)
} else if b >= 97 && b <= 102 {
Some(b - 87)
} else {
None
}
}