use std::path::{Path, PathBuf};
use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
const FILENAME_ENCODE_SET: &AsciiSet = &CONTROLS
.add(b'/')
.add(b'\\')
.add(b'?')
.add(b'%')
.add(b'*')
.add(b':')
.add(b'|')
.add(b'"')
.add(b'<')
.add(b'>')
.add(b'&')
.add(b'=');
pub fn get_path_from_url(url: &str) -> &str {
if let Some(start_pos) = url.find("//") {
let mut pos = start_pos + 2;
if let Some(third_slash_pos) = url[pos..].find('/') {
pos += third_slash_pos;
return &url[pos..];
}
}
"/"
}
pub fn generate_file_name(url: &str) -> String {
let path_without_query = url.split('?').next().unwrap_or("");
let ext = Path::new(path_without_query)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
let encoded_name = utf8_percent_encode(url, FILENAME_ENCODE_SET).to_string();
if path_without_query.ends_with(&format!(".{ext}")) {
encoded_name
} else if ext.is_empty() {
encoded_name
} else {
format!("{encoded_name}.{ext}")
}
}
pub fn url_to_path_manual(base: &str, url: &str) -> PathBuf {
let url = url
.trim_start_matches("http://")
.trim_start_matches("https://");
let mut parts = url.splitn(2, '/');
let domain = parts.next().unwrap_or("");
let path = parts.next().unwrap_or("");
let mut full_path = PathBuf::from(base);
full_path.push(domain);
for segment in Path::new(path).components() {
let cleaned = generate_file_name(segment.as_os_str().to_string_lossy().as_ref());
full_path.push(cleaned);
}
full_path
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_path_from_url() {
assert_eq!(get_path_from_url("https://example.com/assets/file.js"), "/assets/file.js");
assert_eq!(get_path_from_url("http://example.com/"), "/");
assert_eq!(get_path_from_url("ftp://example.com/path/to/resource"), "/path/to/resource");
assert_eq!(get_path_from_url("invalid-url"), "/");
}
#[test]
fn test_generate_file_name() {
assert_eq!(
generate_file_name("https://example.com/assets/file.js"),
"https%3A%2F%2Fexample.com%2Fassets%2Ffile.js"
);
assert_eq!(
generate_file_name("https://example.com/file?version=1.2"),
"https%3A%2F%2Fexample.com%2Ffile%3Fversion%3D1.2"
);
assert_eq!(
generate_file_name("https://example.com/file.with.double.ext.tar.gz"),
"https%3A%2F%2Fexample.com%2Ffile.with.double.ext.tar.gz"
);
}
#[test]
fn test_url_to_path_manual() {
let base = "/tmp/assets";
let path = url_to_path_manual(base, "https://example.com/a/b/c.js");
assert_eq!(
path,
PathBuf::from(format!(
"{}/example.com/a/b/c.js",
base
))
);
let path = url_to_path_manual(base, "http://cdn.example.com/lib/jquery.min.js?ver=3.6.0");
assert_eq!(
path,
PathBuf::from(format!(
"{}/cdn.example.com/lib/jquery.min.js%3Fver%3D3.6.0",
base
))
);
let path = url_to_path_manual(base, "https://cdn.example.com/");
assert_eq!(path, PathBuf::from(format!("{}/cdn.example.com", base)));
}
}