use serde::Serialize;
use crate::util::{Bytes, BytesMut};
pub(crate) fn json_body<T: ?Sized + Serialize>(value: &T) -> serde_json::Result<Bytes> {
let mut buf = BytesMut::new();
serde_json::to_writer(&mut buf, value)?;
Ok(buf.freeze())
}
pub(crate) fn is_valid_target(target: &[u8]) -> bool {
let mut path = true;
let mut i = 0;
while let Some(&b) = target.get(i) {
match b {
b'%' => {
let (Some(&hi), Some(&lo)) = (target.get(i + 1), target.get(i + 2)) else {
return false;
};
if !hi.is_ascii_hexdigit()
|| !lo.is_ascii_hexdigit()
|| (path && hi == b'0' && lo == b'0')
{
return false;
}
i += 3;
continue;
}
b'?' => path = false,
b'#' | b'"' | b'<' | b'>' | b'\\' | 0..=b' ' | 0x7f.. => return false,
_ => (),
}
i += 1;
}
true
}
#[cfg(feature = "cookie")]
pub(crate) fn push_cookie(buf: &mut Vec<u8>, name: &str, value: &str) {
use urly::quoting::{Component, quote};
if !buf.is_empty() {
buf.extend_from_slice(b"; ");
}
buf.extend_from_slice(quote(name, Component::Opaque).as_bytes());
buf.push(b'=');
buf.extend_from_slice(quote(value, Component::Opaque).as_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_target() {
for target in [
"/",
"*",
"example.com:443",
"http://example.com/a%2Fb?c",
"/a[0]|^`{}?a[]=1&q=a|b%00",
"/a%2e%2E/%41",
] {
assert!(is_valid_target(target.as_bytes()), "{target}");
}
for target in [
"/a#f",
"/a?b#f",
"/a%zz",
"/a%",
"/a%4",
"/a?%g0",
"/a\"",
"/a<",
"/a>",
"/a\\b",
"/a b",
"/a\x7f",
"/ΓΌ",
"/a%00",
"http://h/%00",
"%00",
] {
assert!(!is_valid_target(target.as_bytes()), "{target}");
}
assert!(!is_valid_target(b"/a\xff"));
}
#[test]
fn json_body_matches_to_string() {
let small = vec![1, 2, 3];
let large: Vec<String> = (0..1000).map(|i| format!("item-{i}")).collect();
let map: std::collections::BTreeMap<_, _> = (0..50).map(|i| (i.to_string(), i)).collect();
assert_eq!(
json_body(&small).unwrap(),
serde_json::to_string(&small).unwrap()
);
assert_eq!(
json_body(&large).unwrap(),
serde_json::to_string(&large).unwrap()
);
assert_eq!(
json_body(&map).unwrap(),
serde_json::to_string(&map).unwrap()
);
assert_eq!(json_body("").unwrap(), "\"\"");
assert!(json_body(&small).unwrap().is_inline());
}
#[test]
fn json_body_error() {
let mut map = std::collections::HashMap::new();
map.insert(vec![1u8], 1);
assert!(json_body(&map).is_err());
}
}