use sim_kernel::{Error, Result};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct HttpRequest {
pub method: String,
pub path: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ParsedUrl {
pub scheme: String,
pub host: String,
pub port: u16,
pub path: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum WsMessage {
Binary(Vec<u8>),
Close,
}
const WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
pub(crate) fn parse_url(url: &str, expected_scheme: &str, default_path: &str) -> Result<ParsedUrl> {
let (scheme, rest) = url
.split_once("://")
.ok_or_else(|| Error::Eval(format!("invalid url {url}")))?;
if scheme != expected_scheme {
return Err(Error::Eval(format!(
"expected {expected_scheme} url, found {scheme}"
)));
}
let (host_port, path) = match rest.split_once('/') {
Some((host_port, suffix)) => (host_port, format!("/{suffix}")),
None => (rest, default_path.to_owned()),
};
if host_port.is_empty() {
return Err(Error::Eval(format!("url missing host in {url}")));
}
let (host, port) = match host_port.rsplit_once(':') {
Some((host, port)) => {
let port = port
.parse::<u16>()
.map_err(|_| Error::Eval(format!("invalid port in url {url}")))?;
(host.to_owned(), port)
}
None => {
let port = match expected_scheme {
"http" => 80,
"ws" => 80,
_ => return Err(Error::Eval(format!("missing port in url {url}"))),
};
(host_port.to_owned(), port)
}
};
Ok(ParsedUrl {
scheme: scheme.to_owned(),
host,
port,
path,
})
}
pub(crate) fn format_url(parsed: &ParsedUrl) -> String {
format!(
"{}://{}:{}{}",
parsed.scheme, parsed.host, parsed.port, parsed.path
)
}
pub(crate) fn header_value<'a>(headers: &'a [(String, String)], name: &str) -> Option<&'a str> {
headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
pub(crate) fn base64_encode(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b0 = chunk[0];
let b1 = *chunk.get(1).unwrap_or(&0);
let b2 = *chunk.get(2).unwrap_or(&0);
out.push(ALPHABET[(b0 >> 2) as usize] as char);
out.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
if chunk.len() > 1 {
out.push(ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) as usize] as char);
} else {
out.push('=');
}
if chunk.len() > 2 {
out.push(ALPHABET[(b2 & 0x3f) as usize] as char);
} else {
out.push('=');
}
}
out
}
pub(crate) fn base64_decode(text: &str) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(text.len() / 4 * 3);
let bytes = text.as_bytes();
if !bytes.len().is_multiple_of(4) {
return Err(Error::HostError("invalid base64 length".to_owned()));
}
for chunk in bytes.chunks(4) {
let c0 = decode_b64(chunk[0])?;
let c1 = decode_b64(chunk[1])?;
let c2 = if chunk[2] == b'=' {
None
} else {
Some(decode_b64(chunk[2])?)
};
let c3 = if chunk[3] == b'=' {
None
} else {
Some(decode_b64(chunk[3])?)
};
out.push((c0 << 2) | (c1 >> 4));
if let Some(c2) = c2 {
out.push(((c1 & 0x0f) << 4) | (c2 >> 2));
if let Some(c3) = c3 {
out.push(((c2 & 0x03) << 6) | c3);
}
}
}
Ok(out)
}
pub(crate) fn websocket_accept_value(client_key: &str) -> String {
let mut bytes = client_key.as_bytes().to_vec();
bytes.extend_from_slice(WS_GUID.as_bytes());
base64_encode(&sha1_digest(&bytes))
}
fn decode_b64(byte: u8) -> Result<u8> {
match byte {
b'A'..=b'Z' => Ok(byte - b'A'),
b'a'..=b'z' => Ok(byte - b'a' + 26),
b'0'..=b'9' => Ok(byte - b'0' + 52),
b'+' => Ok(62),
b'/' => Ok(63),
_ => Err(Error::HostError("invalid base64 byte".to_owned())),
}
}
fn sha1_digest(bytes: &[u8]) -> [u8; 20] {
let mut h0: u32 = 0x67452301;
let mut h1: u32 = 0xEFCDAB89;
let mut h2: u32 = 0x98BADCFE;
let mut h3: u32 = 0x10325476;
let mut h4: u32 = 0xC3D2E1F0;
let bit_len = (bytes.len() as u64) * 8;
let mut padded = bytes.to_vec();
padded.push(0x80);
while !(padded.len() + 8).is_multiple_of(64) {
padded.push(0);
}
padded.extend_from_slice(&bit_len.to_be_bytes());
for chunk in padded.chunks_exact(64) {
let mut w = [0u32; 80];
for (index, block) in chunk.chunks_exact(4).enumerate() {
w[index] = u32::from_be_bytes([block[0], block[1], block[2], block[3]]);
}
for index in 16..80 {
w[index] = (w[index - 3] ^ w[index - 8] ^ w[index - 14] ^ w[index - 16]).rotate_left(1);
}
let (mut a, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4);
for (index, word) in w.iter().enumerate() {
let (f, k) = match index {
0..=19 => (((b & c) | ((!b) & d)), 0x5A827999),
20..=39 => (b ^ c ^ d, 0x6ED9EBA1),
40..=59 => (((b & c) | (b & d) | (c & d)), 0x8F1BBCDC),
_ => (b ^ c ^ d, 0xCA62C1D6),
};
let temp = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k)
.wrapping_add(*word);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = temp;
}
h0 = h0.wrapping_add(a);
h1 = h1.wrapping_add(b);
h2 = h2.wrapping_add(c);
h3 = h3.wrapping_add(d);
h4 = h4.wrapping_add(e);
}
let mut out = [0u8; 20];
out[0..4].copy_from_slice(&h0.to_be_bytes());
out[4..8].copy_from_slice(&h1.to_be_bytes());
out[8..12].copy_from_slice(&h2.to_be_bytes());
out[12..16].copy_from_slice(&h3.to_be_bytes());
out[16..20].copy_from_slice(&h4.to_be_bytes());
out
}