use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::builder::HttpUrlBuilder;
use crate::error::{HttpUrlError, Result};
use crate::scheme::Scheme;
use crate::util::percent;
pub(crate) fn split_authority(rest: &str) -> (&str, &str) {
let end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
(&rest[..end], &rest[end..])
}
pub(crate) fn split_path_query_fragment(pqf: &str) -> (&str, Option<&str>, Option<&str>) {
let pqf = if pqf.is_empty() { "/" } else { pqf };
let frag_start = pqf.find('#');
let (before_frag, frag) = match frag_start {
Some(pos) => (&pqf[..pos], Some(&pqf[pos + 1..])),
None => (pqf, None),
};
let query_start = before_frag.find('?');
let (path, query) = match query_start {
Some(pos) => (&before_frag[..pos], Some(&before_frag[pos + 1..])),
None => (before_frag, None),
};
(path, query, frag)
}
pub(crate) fn parse_path_segments(path: &str) -> Result<Vec<String>> {
let path = if path.is_empty() { "/" } else { path };
let path = path.replace('\\', "/");
let mut segments = Vec::new();
for segment in path.split('/') {
if segment.is_empty() {
continue;
}
let decoded = percent::decode(segment)
.map_err(|_| HttpUrlError::InvalidPercentEncoding(segment.to_string()))?;
segments.push(decoded);
}
canonicalize_path_segments(&mut segments);
Ok(segments)
}
pub(crate) fn canonicalize_path_segments(segments: &mut Vec<String>) {
let mut i = 0;
while i < segments.len() {
if segments[i] == "." {
segments.remove(i);
} else if segments[i] == ".." {
if i > 0 {
segments.remove(i);
segments.remove(i - 1);
i -= 1;
} else {
segments.remove(i);
}
} else {
i += 1;
}
}
}
pub(crate) fn parse_query_string(query: &str) -> Result<Vec<String>> {
let mut parts = Vec::new();
for pair in query.split('&') {
if pair.is_empty() {
continue;
}
if let Some(eq_pos) = pair.find('=') {
let name = percent::decode(&pair[..eq_pos])
.map_err(|_| HttpUrlError::InvalidPercentEncoding(pair[..eq_pos].to_string()))?;
let value = percent::decode(&pair[eq_pos + 1..]).map_err(|_| {
HttpUrlError::InvalidPercentEncoding(pair[eq_pos + 1..].to_string())
})?;
parts.push(name);
parts.push(value);
} else {
let name = percent::decode(pair)
.map_err(|_| HttpUrlError::InvalidPercentEncoding(pair.to_string()))?;
parts.push(name);
parts.push(String::new());
}
}
Ok(parts)
}
pub(crate) fn parse_authority<'a>(
authority: &'a str,
default_scheme: &Scheme,
) -> Result<(String, String, &'a str, u16)> {
let default_port = default_scheme.default_port();
let (userinfo, host_port) = if let Some(at_pos) = authority.rfind('@') {
let userinfo = &authority[..at_pos];
let host_port = &authority[at_pos + 1..];
(Some(userinfo), host_port)
} else {
(None, authority)
};
let (username, password) = if let Some(ui) = userinfo {
if let Some(colon_pos) = ui.find(':') {
let uname = percent::decode(&ui[..colon_pos])
.map_err(|_| HttpUrlError::InvalidPercentEncoding(ui[..colon_pos].to_string()))?;
let pwd = percent::decode(&ui[colon_pos + 1..]).map_err(|_| {
HttpUrlError::InvalidPercentEncoding(ui[colon_pos + 1..].to_string())
})?;
(uname, pwd)
} else {
let uname = percent::decode(ui)
.map_err(|_| HttpUrlError::InvalidPercentEncoding(ui.to_string()))?;
(uname, String::new())
}
} else {
(String::new(), String::new())
};
if host_port.is_empty() {
return Err(HttpUrlError::MissingHost);
}
let (host_str, port) = if host_port.starts_with('[') {
let close_bracket = host_port
.find(']')
.ok_or_else(|| HttpUrlError::InvalidHost(host_port.to_string()))?;
let ipv6_host = &host_port[..close_bracket + 1];
let after = &host_port[close_bracket + 1..];
if after.is_empty() {
(ipv6_host, default_port)
} else if let Some(port_str) = after.strip_prefix(':') {
let port = port_str
.parse::<u16>()
.map_err(|_| HttpUrlError::InvalidPort(port_str.to_string()))?;
(ipv6_host, port)
} else {
return Err(HttpUrlError::InvalidHost(host_port.to_string()));
}
} else if let Some(colon_pos) = host_port.rfind(':') {
let host_str = &host_port[..colon_pos];
let port_str = &host_port[colon_pos + 1..];
if port_str.is_empty() {
return Err(HttpUrlError::InvalidPort("".to_string()));
}
let port = port_str
.parse::<u16>()
.map_err(|_| HttpUrlError::InvalidPort(port_str.to_string()))?;
(host_str, port)
} else {
(host_port, default_port)
};
Ok((username, password, host_str, port))
}
pub(crate) fn apply_path_query_fragment(builder: &mut HttpUrlBuilder, s: &str) -> Result<()> {
let (path_str, query_str, frag_str) = split_path_query_fragment(s);
let segments = parse_path_segments(path_str)?;
builder.path_segments = segments;
if let Some(q) = query_str {
builder.query_names_and_values = parse_query_string(q)?;
}
if let Some(f) = frag_str {
let decoded =
percent::decode(f).map_err(|_| HttpUrlError::InvalidPercentEncoding(f.to_string()))?;
builder.fragment = Some(decoded);
}
Ok(())
}
pub(crate) fn merge_path(builder: &mut HttpUrlBuilder, relative: &str) -> Result<()> {
let (rel_path, query_str, frag_str) = split_path_query_fragment(relative);
if !builder.path_segments.is_empty() {
builder.path_segments.pop();
}
for segment in rel_path.split('/') {
if segment.is_empty() {
continue;
}
let decoded = percent::decode(segment)
.map_err(|_| HttpUrlError::InvalidPercentEncoding(segment.to_string()))?;
builder.path_segments.push(decoded);
}
canonicalize_path_segments(&mut builder.path_segments);
if let Some(q) = query_str {
builder.query_names_and_values = parse_query_string(q)?;
}
if let Some(f) = frag_str {
let decoded =
percent::decode(f).map_err(|_| HttpUrlError::InvalidPercentEncoding(f.to_string()))?;
builder.fragment = Some(decoded);
}
Ok(())
}