use std::net::{IpAddr, Ipv4Addr};
use std::time::SystemTime;
use crate::{Bytes, Normalized, RequestContext};
const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded";
const JSON_CONTENT_TYPE: &str = "application/json";
pub fn base_ctx() -> RequestContext {
Request::new().build()
}
pub struct Request {
ctx: RequestContext,
}
impl Default for Request {
fn default() -> Self {
Self::new()
}
}
impl Request {
pub fn new() -> Self {
Self {
ctx: RequestContext {
client_ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
request_id: "corpus".to_string(),
timestamp: SystemTime::UNIX_EPOCH,
method: "GET".to_string(),
path: "/".to_string(),
raw_path: "/".to_string(),
query: None,
http_version: "HTTP/1.1".to_string(),
headers: Vec::new(),
cookies: Vec::new(),
body: Bytes::new(),
normalized: Normalized::default(),
score: 0,
score_contributions: Vec::new(),
},
}
}
pub fn request_id(mut self, id: &str) -> Self {
self.ctx.request_id = id.to_string();
self
}
pub fn timestamp(mut self, t: SystemTime) -> Self {
self.ctx.timestamp = t;
self
}
pub fn method(mut self, method: &str) -> Self {
self.ctx.method = method.to_string();
self
}
pub fn path(mut self, path: &str) -> Self {
self.ctx.path = path.to_string();
self.ctx.raw_path = path.to_string();
self
}
pub fn raw_query(mut self, query_string: &str) -> Self {
self.ctx.query = Some(query_string.to_string());
self
}
pub fn query(mut self, name: &str, value: &str) -> Self {
let pair = format!("{}={}", enc_query_component(name), enc_query_component(value));
match &mut self.ctx.query {
Some(existing) => {
existing.push('&');
existing.push_str(&pair);
}
None => self.ctx.query = Some(pair),
}
self
}
pub fn header(mut self, name: &str, value: &str) -> Self {
self.ctx.headers.push((name.to_string(), value.to_string()));
self
}
pub fn cookie_header(self, raw: &str) -> Self {
self.header("cookie", raw)
}
pub fn form_body(self, raw: &str) -> Self {
self.body(raw.as_bytes().to_vec(), FORM_CONTENT_TYPE)
}
pub fn json_body(self, raw: &str) -> Self {
self.body(raw.as_bytes().to_vec(), JSON_CONTENT_TYPE)
}
pub fn body(mut self, bytes: impl Into<Bytes>, content_type: &str) -> Self {
self.ctx.body = bytes.into();
self.ctx.headers.push(("content-type".to_string(), content_type.to_string()));
self
}
pub fn build(self) -> RequestContext {
self.ctx
}
}
fn enc_query_component(s: &str) -> String {
let mut out: Vec<u8> = Vec::with_capacity(s.len());
for &b in s.as_bytes() {
match b {
b'%' => out.extend_from_slice(b"%25"),
b'&' => out.extend_from_slice(b"%26"),
b'+' => out.extend_from_slice(b"%2B"),
other => out.push(other),
}
}
String::from_utf8(out).expect("only ASCII bytes were substituted; UTF-8 stays valid")
}