konobangu/crates/quirks_path/src/url.rs

98 lines
3.2 KiB
Rust

use std::{borrow::Cow, fmt::Write};
use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
use crate::{windows::parse_drive, Component, Path, Prefix};
const URL_FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
const URL_PATH: &AsciiSet = &URL_FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
const URL_PATH_SEGMENT: &AsciiSet = &URL_PATH.add(b'/').add(b'%');
#[derive(thiserror::Error, Debug)]
pub enum PathToUrlError {
#[error("Path not absolute: {path}")]
PathNotAbsoluteError { path: Cow<'static, str> },
#[error("Invalid UNC path")]
ParseUrlError(#[from] ::url::ParseError),
#[error("Path prefix can not be a url: {path}")]
UrlNotSupportedPrefix { path: Cow<'static, str> },
}
#[inline]
pub fn to_u32(i: usize) -> Result<u32, url::ParseError> {
if i <= ::std::u32::MAX as usize {
Ok(i as u32)
} else {
Err(url::ParseError::Overflow)
}
}
pub(crate) fn path_to_file_url_segments(
path: &Path,
serialization: &mut String,
) -> Result<(u32, Option<::url::Host<String>>), PathToUrlError> {
if !path.is_absolute() {
return Err(PathToUrlError::PathNotAbsoluteError {
path: Cow::Owned(path.as_str().to_string()),
});
}
let mut components = path.components();
let host_start = serialization.len() + 1;
let host_end;
let host_internal: Option<url::Host<String>>;
match components.next() {
Some(Component::Prefix(ref p)) => match p.kind() {
Prefix::Disk { drive } | Prefix::VerbatimDisk { drive } => {
host_end = to_u32(serialization.len()).unwrap();
host_internal = None;
serialization.push('/');
serialization.push(drive);
serialization.push(':');
}
Prefix::UNC { server, share } | Prefix::VerbatimUNC { server, share } => {
let host = url::Host::parse(server)?;
write!(serialization, "{}", host).unwrap();
host_end = to_u32(serialization.len()).unwrap();
host_internal = Some(host);
serialization.push('/');
serialization.extend(percent_encode(share.as_bytes(), URL_PATH_SEGMENT));
}
_ => {
return Err(PathToUrlError::UrlNotSupportedPrefix {
path: Cow::Owned(path.as_str().to_string()),
})
}
},
_ => {
return Err(PathToUrlError::UrlNotSupportedPrefix {
path: Cow::Owned(path.as_str().to_string()),
})
}
}
let mut path_only_has_prefix = true;
for component in components {
if matches!(component, Component::RootDir(..)) {
continue;
}
path_only_has_prefix = false;
let component = component.as_str();
serialization.push('/');
serialization.extend(percent_encode(component.as_bytes(), URL_PATH_SEGMENT));
}
// A windows drive letter must end with a slash.
if serialization.len() > host_start
&& matches!(parse_drive(&serialization[host_start..]), Ok(..))
&& path_only_has_prefix
{
serialization.push('/');
}
Ok((host_end, host_internal))
}