///|
fn hex_digit(b : Byte) -> Int? {
if b >= b'0' && b <= b'9' {
Some(b.to_int() - b'0'.to_int())
} else if b >= b'a' && b <= b'f' {
Some(b.to_int() - b'a'.to_int() + 10)
} else if b >= b'A' && b <= b'F' {
Some(b.to_int() - b'A'.to_int() + 10)
} else {
None
}
}
///|
pub fn url_decode(bytes : BytesView) -> String {
let len = bytes.length()
let res = Array::make(len, b'\x00')
let mut w = 0
let mut r = 0
while r < len {
let b = bytes[r]
if b == b'+' {
res[w] = b' '
w = w + 1
r = r + 1
} else if b == b'%' && r + 2 < len {
match (hex_digit(bytes[r + 1]), hex_digit(bytes[r + 2])) {
(Some(h1), Some(h2)) => {
res[w] = (h1 * 16 + h2).to_byte()
w = w + 1
r = r + 3
}
_ => {
res[w] = b'%'
w = w + 1
r = r + 1
}
}
} else {
res[w] = b
w = w + 1
r = r + 1
}
}
let final_bytes = Bytes::from_array(res)
let view = final_bytes[0:w]
@utf8.decode(view) catch {
_ => ""
}
}
///|
pub fn parse_form_data(bytes : BytesView) -> Map[String, String] {
let res = Map([])
if bytes.length() == 0 {
return res
}
// Split by '&'
let mut start = 0
let len = bytes.length()
for i in 0.. Unit {
let len = part.length()
let mut eq_idx = -1
for i in 0.. String {
let buf = StringBuilder::new()
for c in s {
match c {
'&' => buf.write_string("&")
'<' => buf.write_string("<")
'>' => buf.write_string(">")
'"' => buf.write_string(""")
'\'' => buf.write_string("'")
_ => buf.write_char(c)
}
}
buf.to_string()
}
///|
fn is_unreserved(b : Byte) -> Bool {
(b >= b'A' && b <= b'Z') ||
(b >= b'a' && b <= b'z') ||
(b >= b'0' && b <= b'9') ||
b == b'-' ||
b == b'_' ||
b == b'.' ||
b == b'~'
}
///|
pub fn url_encode(s : String) -> String {
let bytes = @utf8.encode(s)
let buf = StringBuilder::new()
for i in 0.. buf.write_char(c)
None => ()
}
} else {
buf.write_char('%')
let hex_bytes = b"0123456789ABCDEF"
let h1 = (b.to_int() >> 4) & 0xF
let h2 = b.to_int() & 0xF
match hex_bytes[h1].to_int().to_char() {
Some(c) => buf.write_char(c)
None => ()
}
match hex_bytes[h2].to_int().to_char() {
Some(c) => buf.write_char(c)
None => ()
}
}
}
buf.to_string()
}
///|
pub fn form_encode(map : Map[String, String]) -> String {
let buf = StringBuilder::new()
let mut first = true
map.each(fn(k, v) {
if !first {
buf.write_char('&')
}
first = false
buf.write_string(url_encode(k))
buf.write_char('=')
buf.write_string(url_encode(v))
})
buf.to_string()
}
///|
fn find_boundary(
bytes : BytesView,
boundary : BytesView,
start_index : Int,
) -> Int {
let len = bytes.length()
let boundary_len = boundary.length()
if start_index + boundary_len > len {
return -1
}
for i in start_index..<=(len - boundary_len) {
let mut match_found = true
for j in 0.. Map[String, MultipartFormValue] {
let res = Map([])
let boundary_bytes = @utf8.encode("--" + boundary)
let boundary_len = boundary_bytes.length()
let len = bytes.length()
let mut start = 0
// Find first boundary
let first_boundary = find_boundary(bytes, boundary_bytes[:], start)
if first_boundary == -1 {
return res
}
start = first_boundary + boundary_len
while start < len {
// Check for end boundary "--"
if start + 2 <= len && bytes[start] == b'-' && bytes[start + 1] == b'-' {
break
}
// Skip CRLF after boundary
if start + 2 <= len && bytes[start] == b'\r' && bytes[start + 1] == b'\n' {
start = start + 2
} else {
// Malformed or unexpected char, try to find next boundary
let next = find_boundary(bytes, boundary_bytes[:], start)
if next == -1 {
break
}
start = next + boundary_len
continue
}
// Find next boundary
let next_boundary = find_boundary(bytes, boundary_bytes[:], start)
if next_boundary == -1 {
break
}
// Part content is between start and next_boundary - 2 (CRLF)
let part_end = next_boundary - 2
if part_end < start {
// Should not happen if format is correct
break
}
let part_bytes = bytes[start:part_end]
parse_multipart_part(part_bytes, res)
start = next_boundary + boundary_len
}
res
}
///|
fn parse_multipart_part(
part : BytesView,
res : Map[String, MultipartFormValue],
) -> Unit {
// Find double CRLF separating headers from body
let len = part.length()
let mut body_start = -1
for i in 0..<(len - 3) {
if part[i] == b'\r' &&
part[i + 1] == b'\n' &&
part[i + 2] == b'\r' &&
part[i + 3] == b'\n' {
body_start = i + 4
break
}
}
if body_start == -1 {
return
}
let headers_bytes = part[0:body_start - 4]
let body_bytes = part[body_start:len]
let headers_str = @utf8.decode(headers_bytes) catch { _ => return }
let headers = headers_str.split("\r\n")
let mut name = ""
let mut filename : String? = None
let mut content_type : String? = None
for header in headers {
let parts = header.split(":").to_array()
if parts.length() >= 2 {
let key = parts[0].trim().to_lower()
let val = parts[1].trim()
if key == "content-disposition" {
// form-data; name="field"; filename="file.txt"
let directives = val.split(";").to_array()
for i in 0..