use alloc::format;
use alloc::string::{String, ToString};
pub(crate) fn resolve(base: &str, uri: &str) -> String {
if uri.contains("://") {
return uri.to_string();
}
if let Some(rest) = uri.strip_prefix("//") {
return match base.find("://") {
Some(idx) => format!("{}://{rest}", &base[..idx]),
None => uri.to_string(),
};
}
if let Some(path) = uri.strip_prefix('/') {
return match base.find("://") {
Some(scheme_end) => {
let authority_start = scheme_end + 3;
let authority_end = base[authority_start..]
.find('/')
.map(|i| authority_start + i)
.unwrap_or(base.len());
format!("{}/{path}", &base[..authority_end])
}
None => uri.to_string(),
};
}
let base_no_query = base.split(['?', '#']).next().unwrap_or(base);
match base_no_query.rfind('/') {
Some(idx) => format!("{}{uri}", &base_no_query[..=idx]),
None => uri.to_string(),
}
}
pub(crate) fn append_query(url: &str, extra: &str) -> String {
if url.contains('?') {
format!("{url}&{extra}")
} else {
format!("{url}?{extra}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_relative_against_directory() {
assert_eq!(
resolve("http://h/live/stream.m3u8", "seg0.m4s"),
"http://h/live/seg0.m4s"
);
}
#[test]
fn resolves_relative_ignoring_query() {
assert_eq!(
resolve("http://h/live/stream.m3u8?_HLS_msn=3", "seg0.m4s"),
"http://h/live/seg0.m4s"
);
}
#[test]
fn absolute_uri_passes_through() {
assert_eq!(
resolve("http://h/live/stream.m3u8", "https://cdn/seg0.m4s"),
"https://cdn/seg0.m4s"
);
}
#[test]
fn protocol_relative_borrows_base_scheme() {
assert_eq!(
resolve("https://h/live/stream.m3u8", "//cdn/seg0.m4s"),
"https://cdn/seg0.m4s"
);
}
#[test]
fn absolute_path_keeps_authority() {
assert_eq!(
resolve("http://h/live/stream.m3u8", "/segs/seg0.m4s"),
"http://h/segs/seg0.m4s"
);
}
#[test]
fn append_query_adds_question_mark_once() {
assert_eq!(
append_query("http://h/p.m3u8", "a=1"),
"http://h/p.m3u8?a=1"
);
assert_eq!(
append_query("http://h/p.m3u8?a=1", "b=2"),
"http://h/p.m3u8?a=1&b=2"
);
}
}