use crate::DownloadOptions;
use crate::config::ProxyConfig;
use crate::download::download as http_download;
use crate::optimization::Optimizer;
use std::error::Error;
pub fn is_webdav_url(url: &str) -> bool {
let lower = url.to_lowercase();
lower.starts_with("webdav://") || lower.starts_with("webdavs://")
}
pub struct WebDavDownloader {
http_url: String,
output: String,
quiet: bool,
proxy: ProxyConfig,
optimizer: Optimizer,
username: Option<String>,
password: Option<String>,
}
impl WebDavDownloader {
pub fn new(
url: String,
output: String,
quiet: bool,
proxy: ProxyConfig,
optimizer: Optimizer,
) -> Self {
let (http_url, username, password) = parse_webdav_url(&url);
Self { http_url, output, quiet, proxy, optimizer, username, password }
}
pub fn download(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
let mut extra_headers: Vec<(String, String)> = Vec::new();
if let Some(user) = &self.username {
let pass = self.password.as_deref().unwrap_or("");
let creds = base64_encode(format!("{user}:{pass}").as_bytes());
extra_headers.push(("Authorization".to_string(), format!("Basic {creds}")));
}
let options = DownloadOptions {
quiet_mode: self.quiet,
output_path: Some(self.output.clone()),
verify_iso: false,
expected_sha256: None,
extra_headers,
};
http_download(
&self.http_url,
self.proxy.clone(),
self.optimizer.clone(),
options,
None,
)
}
}
fn parse_webdav_url(url: &str) -> (String, Option<String>, Option<String>) {
let (scheme, rest) = if url.to_lowercase().starts_with("webdavs://") {
("https", &url["webdavs://".len()..])
} else {
("http", &url["webdav://".len()..])
};
if let Some(at) = rest.find('@') {
let creds = &rest[..at];
let host_path = &rest[at + 1..];
let http_url = format!("{scheme}://{host_path}");
if let Some(colon) = creds.find(':') {
return (
http_url,
Some(creds[..colon].to_string()),
Some(creds[colon + 1..].to_string()),
);
}
return (http_url, Some(creds.to_string()), None);
}
(format!("{scheme}://{rest}"), None, None)
}
fn base64_encode(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity((data.len() + 2) / 3 * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as usize;
let b1 = if chunk.len() > 1 { chunk[1] as usize } else { 0 };
let b2 = if chunk.len() > 2 { chunk[2] as usize } else { 0 };
out.push(CHARS[b0 >> 2] as char);
out.push(CHARS[((b0 & 3) << 4) | (b1 >> 4)] as char);
out.push(if chunk.len() > 1 { CHARS[((b1 & 15) << 2) | (b2 >> 6)] as char } else { '=' });
out.push(if chunk.len() > 2 { CHARS[b2 & 63] as char } else { '=' });
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_plain_webdav() {
let (url, user, pass) = parse_webdav_url("webdav://nas.local/files/report.pdf");
assert_eq!(url, "http://nas.local/files/report.pdf");
assert!(user.is_none() && pass.is_none());
}
#[test]
fn parse_webdavs_with_credentials() {
let (url, user, pass) = parse_webdav_url("webdavs://alice:secret@cloud.example.com/docs/file.zip");
assert_eq!(url, "https://cloud.example.com/docs/file.zip");
assert_eq!(user.as_deref(), Some("alice"));
assert_eq!(pass.as_deref(), Some("secret"));
}
#[test]
fn base64_roundtrip() {
assert_eq!(base64_encode(b"user:pass"), "dXNlcjpwYXNz");
assert_eq!(base64_encode(b"Man"), "TWFu");
assert_eq!(base64_encode(b""), "");
}
}