use super::*;
pub(crate) fn bytes_to_string(bytes: Vec<u8>) -> std::io::Result<String> {
match simdutf8::basic::from_utf8(&bytes) {
Ok(_) => Ok(unsafe { String::from_utf8_unchecked(bytes) }),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
)),
}
}
pub(crate) fn hex_encode(s: &str) -> String {
let mut out = String::with_capacity(s.len() * 2);
for b in s.bytes() {
out.push(char::from_digit((b >> 4) as u32, 16).unwrap());
out.push(char::from_digit((b & 0xf) as u32, 16).unwrap());
}
out
}
pub(crate) fn hex_decode(s: &str) -> Option<String> {
let bytes = s.as_bytes();
if !bytes.len().is_multiple_of(2) {
return None;
}
let mut out = Vec::with_capacity(bytes.len() / 2);
for pair in bytes.chunks(2) {
let hi = (pair[0] as char).to_digit(16)?;
let lo = (pair[1] as char).to_digit(16)?;
out.push((hi * 16 + lo) as u8);
}
String::from_utf8(out).ok()
}
pub(crate) fn decode_at_id(seg: &str) -> String {
if let Some(s) = hex_decode(seg) {
return s;
}
urldecode(seg).replace("__x00__", "\0")
}
pub(crate) fn is_asset_ext(ext: &str) -> bool {
oj_compiler::assets::is_asset_ext(ext) || ext.eq_ignore_ascii_case("wasm")
}
pub(crate) fn is_asset_path(file: &Path) -> bool {
file.extension()
.and_then(|e| e.to_str())
.map(is_asset_ext)
.unwrap_or(false)
}
pub(crate) fn content_type(ext: &str) -> &'static str {
match ext {
"html" => "text/html; charset=utf-8",
"js" | "mjs" | "cjs" => "text/javascript",
"css" => "text/css",
"json" | "map" => "application/json",
"svg" => "image/svg+xml",
"png" => "image/png",
"jpg" | "jpeg" => "image/jpeg",
"ico" => "image/x-icon",
"wasm" => "application/wasm",
"woff2" => "font/woff2",
"woff" => "font/woff",
"ttf" => "font/ttf",
"otf" => "font/otf",
"eot" => "application/vnd.ms-fontobject",
"webp" => "image/webp",
"gif" => "image/gif",
"txt" | "map2" => "text/plain; charset=utf-8",
other => oj_compiler::assets::asset_mime(other),
}
}
pub(crate) fn now_millis() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
}
pub(crate) fn fs_gate(state: &ServerState, candidate: &Path) -> Option<PathBuf> {
let real = std::fs::canonicalize(candidate).ok()?;
let allowed = !state.fs_strict || {
let allow = state.fs_allow.lock().unwrap();
real.ancestors().any(|a| allow.contains(a))
};
if !allowed || path_is_denied(&real, &state.root, &state.fs_deny) {
return None;
}
Some(candidate.to_path_buf())
}
pub(crate) fn insert_allow_root(allow: &mut std::collections::HashSet<PathBuf>, root: PathBuf) {
if let Ok(real) = std::fs::canonicalize(&root) {
if real != root {
allow.insert(real);
}
}
allow.insert(root);
}
pub(crate) fn allow_root(set: &Mutex<std::collections::HashSet<PathBuf>>, root: PathBuf) {
insert_allow_root(&mut set.lock().unwrap(), root);
}
pub(crate) fn canonicalize_memo(state: &ServerState, p: &str) -> PathBuf {
if let Some(hit) = state.canon_memo.lock().unwrap().get(p) {
return hit.clone();
}
match std::fs::canonicalize(p) {
Ok(real) => {
state
.canon_memo
.lock()
.unwrap()
.insert(p.to_string(), real.clone());
real
}
Err(_) => PathBuf::from(p),
}
}
pub(crate) fn urldecode(input: &str) -> String {
let bytes = input.as_bytes();
let nibble = |b: u8| (b as char).to_digit(16).map(|d| d as u8);
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
if let (Some(h), Some(l)) = (nibble(bytes[i + 1]), nibble(bytes[i + 2])) {
out.push(h * 16 + l);
i += 3;
continue;
}
}
out.push(bytes[i]);
i += 1;
}
String::from_utf8_lossy(&out).into_owned()
}