use std::collections::HashMap; use crate::{error::HTTPError, parse::{TryParse, Parse}}; #[derive(Debug, Clone)] pub struct Path { inner: String } impl Path { pub fn as_str(&self) -> &str { self.inner.as_ref() } } impl TryParse for Path { fn try_parse(s: impl Into) -> Result { let s: String = s.into(); let Some(first) = s.chars().nth(0) else { return Err(HTTPError::InvalidPath(s)) }; if first != '/' { return Err(HTTPError::InvalidPath(s)) } else { return Ok(Self { inner: s }) } } } impl ToString for Path { fn to_string(&self) -> String { self.as_str().to_string() } } #[derive(Debug, Clone)] pub enum Scheme { Http, Https, Unkown(String) } impl Scheme { pub fn as_str(&self) -> &str { match self { Scheme::Http => "http", Scheme::Https => "https", Scheme::Unkown(ref s) => s, } } } impl Parse for Scheme { fn parse(s: impl Into) -> Self { let s = s.into(); match s.as_str() { "http" => Self::Http, "https" => Self::Https, _ => Self::Unkown(s) } } } impl ToString for Scheme { fn to_string(&self) -> String { self.as_str().to_string() } } #[derive(Debug, Clone)] pub struct Authority { pub sub_domain: Option, pub domain: String, pub tld: Option, pub port: Option } impl TryParse for Authority { fn try_parse(s: impl Into) -> Result { let s = s.into(); let domain_end = s.find(":").unwrap_or(s.len()); let authority = &s[..domain_end]; let tld_start = authority.rfind(".").unwrap_or(authority.len()); let full_domain = &authority[..tld_start]; let domain_start = match full_domain.find(".") { Some(u) => u + 1, None => 0 }; let domain = &full_domain[domain_start..]; let sub_domain; if domain_start > 0 { sub_domain = Some(full_domain[..(domain_start-1)].to_string()); } else { sub_domain = None; } let tld; if tld_start < authority.len() { tld = Some(authority[(tld_start+1)..domain_end].to_string()); } else { tld = None; } let port; if domain_end < s.len() { let port_str = &s[(domain_end+1)..]; let num = match port_str.parse::() { Ok(n) => n, Err(_) => return Err(HTTPError::InvalidPort(port_str.to_string())) }; port = Some(num); } else { port = None; } Ok(Self { sub_domain, domain: domain.to_string(), tld, port } ) } } impl ToString for Authority { fn to_string(&self) -> String { let mut s = String::new(); if let Some(sub_domain) = &self.sub_domain { s.push_str(sub_domain); s.push('.'); } s.push_str(&self.domain); if let Some(tld) = &self.tld { s.push('.'); s.push_str(tld); }; if let Some(port) = self.port { s.push(':'); s.push_str(&format!("{port}")); } s } } #[derive(Debug, Clone)] pub struct QueryFragment { pub name: String, pub value: String } impl QueryFragment { pub fn new(name: impl Into, value: impl Into) -> Result { let name = name.into(); if name.len() < 1 { return Err(HTTPError::InvalidQueryFragmentName(name)) } Ok(Self { name, value: value.into() }) } } impl TryParse for QueryFragment { fn try_parse(s: impl Into) -> Result { let s = s.into(); let Some(index) = s.find('=') else { return Err(HTTPError::InvalidQueryFragment(s.to_string())) }; if index == 0 { return Err(HTTPError::InvalidQueryFragment(s.to_string())) } Ok(Self { name: decode_str(&s[0..index])?, value: decode_str(&s[(index+1)..])? }) } } impl ToString for QueryFragment { fn to_string(&self) -> String { let mut s = String::new(); s.push_str(&encode_str(&self.name)); s.push('='); s.push_str(&encode_str(&self.value)); s } } #[derive(Debug, Clone)] pub struct QueryMap { inner: HashMap } impl QueryMap { fn new() -> Self { Self::with_fragments(Vec::new()) } pub fn with_fragments(fragments: Vec) -> Self { let mut inner = HashMap::with_capacity(fragments.len()); for fragment in fragments { inner.insert(fragment.name.clone(), fragment); } Self { inner } } pub fn insert(&mut self, fragment: &QueryFragment) { self.inner.insert(fragment.name.clone(), fragment.clone()); } pub fn remove(&mut self, name: &str) -> Option { self.inner.remove(name) } pub fn get(&self, name: &str) -> Option<&QueryFragment> { self.inner.get(name) } } impl TryParse for QueryMap { fn try_parse(s: impl Into) -> Result { let s = s.into(); let parts: Vec = s.split('&').map(|f| QueryFragment::try_parse(f)).flatten().collect(); Ok(Self::with_fragments(parts)) } } impl ToString for QueryMap { fn to_string(&self) -> String { let mut s = String::new(); for (i, fragment) in self.inner.values().enumerate() { if i == 0 { s.push('?'); } else { s.push('&'); } s.push_str(&fragment.to_string()); } s } } #[derive(Debug, Clone)] pub struct URI { pub scheme: Option, pub authority: Option, pub path: Path, pub query: QueryMap } impl TryParse for URI { fn try_parse(s: impl Into) -> Result { let s = s.into(); let mut n = s.as_str(); let scheme: Option; if let Some(scheme_end) = n.find("://") { scheme = Some(Scheme::parse(&n[..scheme_end])); n = &n[(scheme_end + 3)..]; } else { scheme = None; } let authority_end = match n.find("/") { Some(i) => i, None => return Err(HTTPError::InvalidURI(s.to_string())) }; let authority: Option; if authority_end == 0 { if scheme.is_some() { return Err(HTTPError::InvalidURI(s.to_string())) } authority = None; } else { authority = Some(Authority::try_parse(&n[..authority_end])?); n = &n[authority_end..]; } let path_end = n.find("?").unwrap_or(n.len()); let path = Path::try_parse(&n[..path_end])?; let query: QueryMap; if path_end + 1 < n.len() { query = QueryMap::try_parse(&n[(path_end+1)..])?; } else { query = QueryMap::new(); } Ok(Self { scheme, authority, path, query }) } } impl ToString for URI { fn to_string(&self) -> String { let mut s = String::new(); if let Some(scheme) = &self.scheme { s.push_str(scheme.as_str()); } if let Some(authority) = &self.authority { s.push_str(&authority.to_string()); } s.push_str(self.path.as_str()); s.push_str(&self.query.to_string()); s } } fn encoded(c: char) -> bool { match c { '/' | '&' | '%' | '?' | '=' => true, _ => false } } pub fn encode_str(s: &str) -> String { let mut e = String::new(); for c in s.chars() { if encoded(c) { e.push('%'); e.push_str(&format!("{:02X}", c as u8)); } else { e.push(c); } } e } pub fn decode_str(s: &str) -> Result { let mut e = String::new(); let mut iter = s.chars(); loop { let Some(char) = iter.next() else { break }; if char == '%' { let err = HTTPError::InvalidEncodedString(s.to_string()); let f = iter.next().ok_or(err.clone())?; let s = iter.next().ok_or(err.clone())?; let Some(fd) = f.to_digit(16) else { return Err(err) }; let Some(sd) = s.to_digit(16) else { return Err(err) }; let b = (fd * 16 + sd) as u8; e.push(b as char); } else { e.push(char); } } Ok(e) }