mirror of
https://github.com/sharkdp/fd.git
synced 2026-10-12 02:22:28 +00:00
Fixes #1843 Changed the host() function for Windows to return '/' instead of '' to generate RFC 3986 compliant file URLs with three slashes. Before: file://C:/test/hi%20there.txt (broken) After: file:///C:/test/hi%20there.txt (works) This fixes hyperlinks failing on Windows when file paths contain spaces or other characters that require percent-encoding.
88 lines
2.3 KiB
Rust
88 lines
2.3 KiB
Rust
use crate::filesystem::absolute_path;
|
|
use std::fmt::{self, Formatter, Write};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
pub(crate) struct PathUrl(PathBuf);
|
|
|
|
impl PathUrl {
|
|
pub(crate) fn new(path: &Path) -> Option<PathUrl> {
|
|
Some(PathUrl(absolute_path(path).ok()?))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PathUrl {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
write!(f, "file://{}", host())?;
|
|
let bytes = self.0.as_os_str().as_encoded_bytes();
|
|
for &byte in bytes.iter() {
|
|
encode(f, byte)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn encode(f: &mut Formatter, byte: u8) -> fmt::Result {
|
|
// NOTE:
|
|
// Most terminals can handle non-ascii unicode characters in a file url fine. But on some OSes (notably
|
|
// windows), the encoded bytes of the path may not be valid UTF-8. Since we don't know if a
|
|
// byte >= 128 is part of a valid UTF-8 encoding or not, we just percent encode any non-ascii
|
|
// byte.
|
|
// Percent encoding these bytes is probably safer anyway.
|
|
match byte {
|
|
b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'/' | b':' | b'-' | b'.' | b'_' | b'~' => {
|
|
f.write_char(byte.into())
|
|
}
|
|
#[cfg(windows)]
|
|
b'\\' => f.write_char('/'),
|
|
_ => {
|
|
write!(f, "%{byte:02X}")
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn host() -> &'static str {
|
|
use std::sync::OnceLock;
|
|
|
|
static HOSTNAME: OnceLock<String> = OnceLock::new();
|
|
|
|
HOSTNAME
|
|
.get_or_init(|| {
|
|
nix::unistd::gethostname()
|
|
.ok()
|
|
.and_then(|h| h.into_string().ok())
|
|
.unwrap_or_default()
|
|
})
|
|
.as_ref()
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
const fn host() -> &'static str {
|
|
"/"
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
// This allows us to test the encoding without having to worry about the host, or absolute path
|
|
struct Encoded(&'static str);
|
|
|
|
impl fmt::Display for Encoded {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
for byte in self.0.bytes() {
|
|
encode(f, byte)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_unicode_encoding() {
|
|
assert_eq!(
|
|
Encoded("$*\x1bßé/∫😃\x07").to_string(),
|
|
"%24%2A%1B%C3%9F%C3%A9/%E2%88%AB%F0%9F%98%83%07",
|
|
);
|
|
}
|
|
}
|