mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: WTF-8 validator (#3374)
This commit is contained in:
parent
329c80cbee
commit
3561867b99
26 changed files with 308 additions and 166 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -3876,21 +3876,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -3899,9 +3899,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
|
|
@ -4615,9 +4615,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
|
|
@ -4732,7 +4732,6 @@ dependencies = [
|
|||
"futures",
|
||||
"hashbrown 0.16.1",
|
||||
"regex",
|
||||
"serde",
|
||||
"vergen-gitcl",
|
||||
"yazi-adapter",
|
||||
"yazi-config",
|
||||
|
|
@ -4933,6 +4932,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"trash",
|
||||
"typed-path",
|
||||
"uzers",
|
||||
"windows-sys 0.61.2",
|
||||
"yazi-ffi",
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ impl Refresh {
|
|||
|
||||
// TODO: performance improvement
|
||||
fn trigger_dirs(folders: &[&Folder]) {
|
||||
async fn go(cwd: UrlBuf, cha: Cha) {
|
||||
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
|
||||
async fn go(dir: UrlBuf, cha: Cha) {
|
||||
let Some(cha) = Files::assert_stale(&dir, cha).await else { return };
|
||||
|
||||
match Files::from_dir_bulk(&cwd).await {
|
||||
Ok(files) => FilesOp::Full(cwd, files, cha).emit(),
|
||||
Err(e) => FilesOp::issue_error(&cwd, e).await,
|
||||
match Files::from_dir_bulk(&dir).await {
|
||||
Ok(files) => FilesOp::Full(dir, files, cha).emit(),
|
||||
Err(e) => FilesOp::issue_error(&dir, e).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ clap = { workspace = true }
|
|||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ tokio-stream = { workspace = true }
|
|||
|
||||
# Logging
|
||||
tracing = { workspace = true }
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-appender = "0.2.4"
|
||||
tracing-subscriber = { version = "0.3.20", features = [ "env-filter" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ scopeguard = { workspace = true }
|
|||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
typed-path = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use yazi_shared::{FromWtf8Vec, loc::LocBuf, path::{PathBufDyn, PathCow, PathDyn, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathDyn, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}, wtf8::FromWtf8Vec};
|
||||
|
||||
use crate::{CWD, path::clean_url};
|
||||
|
||||
|
|
@ -25,21 +25,25 @@ fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> {
|
|||
let mut path = PathBufDyn::with_capacity(url.kind(), n_base.len() + n_rest.len() + n_urn.len());
|
||||
path.try_extend([n_base, n_rest, n_urn]).expect("extend original parts should not fail");
|
||||
|
||||
let loc = LocBuf::<PathBuf>::with(
|
||||
path.into_os().expect("Failed to convert PathBufDyn to PathBuf"),
|
||||
(uri_count + rest_diff + urn_diff) as usize,
|
||||
(urn_count + urn_diff) as usize,
|
||||
)
|
||||
.expect("Failed to create Loc from expanded path");
|
||||
let uri = (uri_count + rest_diff + urn_diff) as usize;
|
||||
let urn = (urn_count + urn_diff) as usize;
|
||||
|
||||
let expanded = match url {
|
||||
Url::Regular(_) => UrlBuf::Regular(loc),
|
||||
Url::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() },
|
||||
Url::Archive { domain, .. } => UrlBuf::Archive { loc, domain: domain.intern() },
|
||||
Url::Sftp { domain, .. } => {
|
||||
todo!();
|
||||
// UrlBuf::Sftp { loc, domain: domain.intern() }
|
||||
}
|
||||
Url::Regular(_) => UrlBuf::Regular(
|
||||
LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
),
|
||||
Url::Search { domain, .. } => UrlBuf::Search {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
},
|
||||
Url::Archive { domain, .. } => UrlBuf::Archive {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
},
|
||||
Url::Sftp { domain, .. } => UrlBuf::Sftp {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
},
|
||||
};
|
||||
|
||||
absolute_url(expanded)
|
||||
|
|
|
|||
|
|
@ -1,45 +1,32 @@
|
|||
use std::{borrow::Cow, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
|
||||
use yazi_shared::path::PathDyn;
|
||||
use yazi_shared::path::{PathCow, PathDyn, PathKind};
|
||||
|
||||
const SET: &AsciiSet =
|
||||
&CONTROLS.add(b'"').add(b'*').add(b':').add(b'<').add(b'>').add(b'?').add(b'\\').add(b'|');
|
||||
|
||||
pub trait PercentEncoding {
|
||||
fn percent_encode(&self) -> Cow<'_, Path>;
|
||||
pub trait PercentEncoding<'a> {
|
||||
fn percent_encode(self) -> Cow<'a, Path>;
|
||||
|
||||
fn percent_decode(&self) -> Cow<'_, [u8]>;
|
||||
fn percent_decode<K>(self, kind: K) -> Result<PathCow<'a>>
|
||||
where
|
||||
K: Into<PathKind>;
|
||||
}
|
||||
|
||||
impl PercentEncoding for Path {
|
||||
fn percent_encode(&self) -> Cow<'_, Path> {
|
||||
match percent_encode(self.as_os_str().as_encoded_bytes(), SET).into() {
|
||||
Cow::Borrowed(_) => self.into(),
|
||||
impl<'a> PercentEncoding<'a> for PathDyn<'a> {
|
||||
fn percent_encode(self) -> Cow<'a, Path> {
|
||||
match percent_encode(self.encoded_bytes(), SET).into() {
|
||||
Cow::Borrowed(s) => Path::new(s).into(),
|
||||
Cow::Owned(s) => PathBuf::from(s).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn percent_decode(&self) -> Cow<'_, [u8]> {
|
||||
match percent_decode(self.as_os_str().as_encoded_bytes()).into() {
|
||||
Cow::Borrowed(_) => self.as_os_str().as_encoded_bytes().into(),
|
||||
Cow::Owned(s) => s.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PercentEncoding for PathDyn<'_> {
|
||||
fn percent_encode(&self) -> Cow<'_, Path> {
|
||||
match self {
|
||||
PathDyn::Os(p) => p.percent_encode(),
|
||||
PathDyn::Unix(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn percent_decode(&self) -> Cow<'_, [u8]> {
|
||||
match self {
|
||||
PathDyn::Os(p) => p.percent_decode(),
|
||||
PathDyn::Unix(_) => todo!(),
|
||||
}
|
||||
fn percent_decode<K>(self, kind: K) -> Result<PathCow<'a>>
|
||||
where
|
||||
K: Into<PathKind>,
|
||||
{
|
||||
PathCow::with(kind, percent_decode(self.encoded_bytes()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
|
||||
|
||||
use yazi_shared::{path::PathDyn, url::{AsUrl, Url, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{path::{AsPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlCow}};
|
||||
|
||||
use crate::{FsHash128, FsScheme};
|
||||
use crate::{FsHash128, FsScheme, path::PercentEncoding};
|
||||
|
||||
pub trait FsUrl<'a> {
|
||||
fn cache(&self) -> Option<PathBuf>;
|
||||
|
|
@ -26,13 +26,12 @@ impl<'a> FsUrl<'a> for Url<'a> {
|
|||
fn cache(&self) -> Option<PathBuf> {
|
||||
fn with_loc(loc: PathDyn, mut root: PathBuf) -> PathBuf {
|
||||
let mut it = loc.components();
|
||||
todo!();
|
||||
// if it.next() == Some(std::path::Component::RootDir) {
|
||||
// root.push(it.as_path().percent_encode());
|
||||
// } else {
|
||||
// root.push(".%2F");
|
||||
// root.push(loc.percent_encode());
|
||||
// }
|
||||
if it.next() == Some(yazi_shared::path::Component::RootDir) {
|
||||
root.push(it.as_path().percent_encode());
|
||||
} else {
|
||||
root.push(".%2F");
|
||||
root.push(loc.percent_encode());
|
||||
}
|
||||
root
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{any::TypeId, ffi::OsStr, io, process::Stdio};
|
|||
use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value};
|
||||
use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
|
||||
use yazi_binding::Error;
|
||||
use yazi_shared::FromWtf8;
|
||||
use yazi_shared::wtf8::FromWtf8;
|
||||
|
||||
use super::{Child, output::Output};
|
||||
use crate::process::Status;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#![allow(clippy::option_map_unit_fn)]
|
||||
|
||||
yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url);
|
||||
yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url wtf8);
|
||||
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8 wtf8);
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8);
|
||||
|
||||
pub fn init() {
|
||||
pool::init();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::{ffi::OsString, hash::{Hash, Hasher}};
|
||||
|
||||
use hashbrown::Equivalent;
|
||||
|
||||
use crate::{FromWtf8, FromWtf8Vec, path::{AsPath, Component, PathDyn, PathDynError, PathKind, SetNameError}, strand::{AsStrand, Strand}};
|
||||
use crate::{path::{AsPath, Component, PathDyn, PathDynError, PathKind, SetNameError}, strand::AsStrand, wtf8::FromWtf8Vec};
|
||||
|
||||
// --- PathBufDyn
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
pub enum PathBufDyn {
|
||||
Os(std::path::PathBuf),
|
||||
Unix(typed_path::UnixPathBuf),
|
||||
|
|
@ -35,6 +35,16 @@ impl TryFrom<PathBufDyn> for typed_path::UnixPathBuf {
|
|||
fn try_from(value: PathBufDyn) -> Result<Self, Self::Error> { value.into_unix() }
|
||||
}
|
||||
|
||||
// --- Hash
|
||||
impl Hash for PathBufDyn {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.as_path().hash(state) }
|
||||
}
|
||||
|
||||
// --- PartialEq
|
||||
impl PartialEq for PathBufDyn {
|
||||
fn eq(&self, other: &PathBufDyn) -> bool { self.as_path() == other.as_path() }
|
||||
}
|
||||
|
||||
impl PartialEq<PathDyn<'_>> for PathBufDyn {
|
||||
fn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other }
|
||||
}
|
||||
|
|
@ -128,14 +138,10 @@ impl PathBufDyn {
|
|||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
Ok(match (self, name.as_strand()) {
|
||||
(Self::Os(p), Strand::Os(s)) => p.set_file_name(s),
|
||||
(Self::Os(p), Strand::Utf8(s)) => p.set_file_name(s),
|
||||
(Self::Os(p), Strand::Bytes(b)) => {
|
||||
p.set_file_name(OsStr::from_wtf8(b).map_err(|_| SetNameError::FromWtf8)?)
|
||||
}
|
||||
|
||||
(Self::Unix(p), s) => p.set_file_name(s.encoded_bytes()),
|
||||
let s = name.as_strand();
|
||||
Ok(match self {
|
||||
Self::Os(p) => p.set_file_name(s.as_os()?),
|
||||
Self::Unix(p) => p.set_file_name(s.encoded_bytes()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ pub enum Component<'a> {
|
|||
Normal(Strand<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Component<'a> {
|
||||
pub fn as_normal(&self) -> Option<Strand<'a>> {
|
||||
match self {
|
||||
Self::Normal(s) => Some(*s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<std::path::Component<'a>> for Component<'a> {
|
||||
fn from(value: std::path::Component<'a>) -> Self {
|
||||
match value {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ use crate::strand::StrandError;
|
|||
// --- EndsWithError
|
||||
#[derive(Debug, Error)]
|
||||
#[error("calling ends_with on paths with different encodings")]
|
||||
pub struct EndsWithError;
|
||||
pub enum EndsWithError {
|
||||
FromStrand(#[from] StrandError),
|
||||
}
|
||||
|
||||
// --- JoinError
|
||||
#[derive(Debug, Error)]
|
||||
#[error("calling join on paths with different encodings")]
|
||||
pub enum JoinError {
|
||||
FromWtf8,
|
||||
FromStrand(#[from] StrandError),
|
||||
FromPathDyn(#[from] PathDynError),
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +49,6 @@ impl From<PathDynError> for std::io::Error {
|
|||
#[derive(Debug, Error)]
|
||||
#[error("calling set_name on paths with different encodings")]
|
||||
pub enum SetNameError {
|
||||
FromWtf8,
|
||||
FromStrand(#[from] StrandError),
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +80,9 @@ impl From<StrandError> for RsplitOnceError {
|
|||
// --- StartsWithError
|
||||
#[derive(Error, Debug)]
|
||||
#[error("calling starts_with on paths with different encodings")]
|
||||
pub struct StartsWithError;
|
||||
pub enum StartsWithError {
|
||||
FromStrand(#[from] StrandError),
|
||||
}
|
||||
|
||||
// --- StripPrefixError
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -92,6 +95,14 @@ pub enum StripPrefixError {
|
|||
WrongEncoding,
|
||||
}
|
||||
|
||||
impl From<StrandError> for StripPrefixError {
|
||||
fn from(err: StrandError) -> Self {
|
||||
match err {
|
||||
StrandError::AsOs | StrandError::AsUtf8 => Self::WrongEncoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::path::StripPrefixError> for StripPrefixError {
|
||||
fn from(_: std::path::StripPrefixError) -> Self { Self::NotPrefix }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use anyhow::Result;
|
|||
use hashbrown::Equivalent;
|
||||
|
||||
use super::{RsplitOnceError, StartsWithError};
|
||||
use crate::{BytesExt, FromWtf8, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
use crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError}, strand::{AsStrand, Strand, StrandError}};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PathDyn<'p> {
|
||||
Os(&'p std::path::Path),
|
||||
Unix(&'p typed_path::UnixPath),
|
||||
|
|
@ -186,14 +186,10 @@ impl<'p> PathDyn<'p> {
|
|||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
Ok(match (self, child.as_strand()) {
|
||||
(Self::Os(p), Strand::Os(q)) => p.ends_with(q),
|
||||
(Self::Os(p), Strand::Utf8(q)) => p.ends_with(q),
|
||||
(Self::Os(p), Strand::Bytes(b)) => {
|
||||
p.ends_with(OsStr::from_wtf8(b).map_err(|_| EndsWithError)?)
|
||||
}
|
||||
|
||||
(Self::Unix(p), s) => p.ends_with(s.encoded_bytes()),
|
||||
let s = child.as_strand();
|
||||
Ok(match self {
|
||||
Self::Os(p) => p.ends_with(s.as_os()?),
|
||||
Self::Unix(p) => p.ends_with(s.encoded_bytes()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -201,14 +197,10 @@ impl<'p> PathDyn<'p> {
|
|||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
Ok(match (self, path.as_strand()) {
|
||||
(Self::Os(p), Strand::Os(q)) => PathBufDyn::Os(p.join(q)),
|
||||
(Self::Os(p), Strand::Utf8(q)) => PathBufDyn::Os(p.join(q)),
|
||||
(Self::Os(p), Strand::Bytes(b)) => {
|
||||
PathBufDyn::Os(p.join(OsStr::from_wtf8(b).map_err(|_| JoinError::FromWtf8)?))
|
||||
}
|
||||
|
||||
(Self::Unix(p), s) => PathBufDyn::Unix(p.join(s.encoded_bytes())),
|
||||
let s = path.as_strand();
|
||||
Ok(match self {
|
||||
Self::Os(p) => PathBufDyn::Os(p.join(s.as_os()?)),
|
||||
Self::Unix(p) => PathBufDyn::Unix(p.join(s.encoded_bytes())),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -235,14 +227,10 @@ impl<'p> PathDyn<'p> {
|
|||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
Ok(match (self, base.as_strand()) {
|
||||
(Self::Os(p), Strand::Os(s)) => p.starts_with(s),
|
||||
(Self::Os(p), Strand::Utf8(s)) => p.starts_with(s),
|
||||
(Self::Os(p), Strand::Bytes(b)) => {
|
||||
p.starts_with(OsStr::from_wtf8(b).map_err(|_| StartsWithError)?)
|
||||
}
|
||||
|
||||
(Self::Unix(p), s) => p.starts_with(s.encoded_bytes()),
|
||||
let s = base.as_strand();
|
||||
Ok(match self {
|
||||
Self::Os(p) => p.starts_with(s.as_os()?),
|
||||
Self::Unix(p) => p.starts_with(s.encoded_bytes()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -250,14 +238,10 @@ impl<'p> PathDyn<'p> {
|
|||
where
|
||||
T: AsStrand,
|
||||
{
|
||||
Ok(match (self, base.as_strand()) {
|
||||
(Self::Os(p), Strand::Os(s)) => Self::Os(p.strip_prefix(s)?),
|
||||
(Self::Os(p), Strand::Utf8(s)) => Self::Os(p.strip_prefix(s)?),
|
||||
(Self::Os(p), Strand::Bytes(b)) => {
|
||||
Self::Os(p.strip_prefix(OsStr::from_wtf8(b).map_err(|_| StripPrefixError::WrongEncoding)?)?)
|
||||
}
|
||||
|
||||
(Self::Unix(p), s) => Self::Unix(p.strip_prefix(s.encoded_bytes())?),
|
||||
let s = base.as_strand();
|
||||
Ok(match self {
|
||||
Self::Os(p) => Self::Os(p.strip_prefix(s.as_os()?)?),
|
||||
Self::Unix(p) => Self::Unix(p.strip_prefix(s.encoded_bytes())?),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub fn escape_os_bytes(b: &[u8]) -> Cow<'_, [u8]> {
|
|||
|
||||
#[cfg(windows)]
|
||||
pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<'_, std::ffi::OsStr> {
|
||||
use crate::FromWtf8Vec;
|
||||
use crate::wtf8::FromWtf8Vec;
|
||||
|
||||
match escape_os_bytes(s.as_encoded_bytes()) {
|
||||
Cow::Borrowed(_) => Cow::Borrowed(s),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsString, hash::{Hash, Hasher}};
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{FromWtf8Vec, path::PathDyn, strand::{AsStrand, Strand, StrandCow, StrandError, StrandKind}};
|
||||
use crate::{path::PathDyn, strand::{AsStrand, Strand, StrandCow, StrandError, StrandKind}, wtf8::FromWtf8Vec};
|
||||
|
||||
// --- StrandBuf
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ impl AsStrand for &std::path::Path {
|
|||
fn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) }
|
||||
}
|
||||
|
||||
impl AsStrand for &std::path::PathBuf {
|
||||
fn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) }
|
||||
}
|
||||
|
||||
impl AsStrand for &typed_path::UnixPath {
|
||||
fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self.as_bytes()) }
|
||||
}
|
||||
|
|
@ -55,6 +59,10 @@ impl AsStrand for crate::path::Components<'_> {
|
|||
fn as_strand(&self) -> Strand<'_> { self.strand() }
|
||||
}
|
||||
|
||||
impl AsStrand for Cow<'_, [u8]> {
|
||||
fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) }
|
||||
}
|
||||
|
||||
impl AsStrand for Cow<'_, OsStr> {
|
||||
fn as_strand(&self) -> Strand<'_> { Strand::Os(self) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::Display};
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{BytesExt, FromWtf8, strand::{AsStrand, StrandBuf, StrandError, StrandKind}};
|
||||
use crate::{BytesExt, strand::{AsStrand, StrandBuf, StrandError, StrandKind}, wtf8::FromWtf8};
|
||||
|
||||
// --- Strand
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd)]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{borrow::Cow, fmt::{Debug, Formatter}, path::{Path, PathBuf}, str::FromStr};
|
||||
use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Path, PathBuf}, str::FromStr};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::{Scheme, SchemeKind}, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlLike}};
|
||||
|
||||
#[derive(Clone, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Eq)]
|
||||
pub enum UrlBuf {
|
||||
Regular(LocBuf),
|
||||
Search { loc: LocBuf, domain: Symbol<str> },
|
||||
|
|
@ -92,6 +92,10 @@ impl From<Cow<'_, Self>> for UrlBuf {
|
|||
}
|
||||
|
||||
// --- Eq
|
||||
impl PartialEq for UrlBuf {
|
||||
fn eq(&self, other: &Self) -> bool { self.as_url() == other.as_url() }
|
||||
}
|
||||
|
||||
impl PartialEq<Url<'_>> for UrlBuf {
|
||||
fn eq(&self, other: &Url) -> bool { self.as_url() == *other }
|
||||
}
|
||||
|
|
@ -100,6 +104,11 @@ impl PartialEq<Url<'_>> for &UrlBuf {
|
|||
fn eq(&self, other: &Url) -> bool { self.as_url() == *other }
|
||||
}
|
||||
|
||||
// --- Hash
|
||||
impl Hash for UrlBuf {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.as_url().hash(state) }
|
||||
}
|
||||
|
||||
impl UrlBuf {
|
||||
#[inline]
|
||||
pub fn new() -> &'static Self {
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@ impl<'a> Components<'a> {
|
|||
}
|
||||
|
||||
pub fn os_str(&self) -> Cow<'a, OsStr> {
|
||||
let Ok(path) = self.inner.path().as_os() else {
|
||||
let Ok(os) = self.inner.strand().as_os() else {
|
||||
return OsString::from(EncodeUrl(self.url()).to_string()).into();
|
||||
};
|
||||
|
||||
if self.url.is_regular() || self.scheme_yielded {
|
||||
return path.as_os_str().into();
|
||||
return os.into();
|
||||
}
|
||||
|
||||
let mut s = OsString::from(EncodeScheme(self.url()).to_string());
|
||||
s.reserve_exact(path.as_os_str().len());
|
||||
s.push(path);
|
||||
s.reserve_exact(os.len());
|
||||
s.push(os);
|
||||
s.into()
|
||||
}
|
||||
|
||||
|
|
|
|||
1
yazi-shared/src/wtf8/mod.rs
Normal file
1
yazi-shared/src/wtf8/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(validator wtf8);
|
||||
116
yazi-shared/src/wtf8/validator.rs
Normal file
116
yazi-shared/src/wtf8/validator.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#[cfg(windows)]
|
||||
pub(super) fn valid_wtf8(bytes: &[u8]) -> bool {
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let b = bytes[i];
|
||||
if b < 0b1000_0000 {
|
||||
// ASCII
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2-byte: 110x_xxxx 10xx_xxxx
|
||||
// first byte must be >= 0b1100_0010 to forbid overlongs
|
||||
if (b & 0b1110_0000) == 0b1100_0000 {
|
||||
if b < 0b1100_0010 {
|
||||
return false;
|
||||
}
|
||||
if i + 1 >= bytes.len() {
|
||||
return false;
|
||||
}
|
||||
if (bytes[i + 1] & 0b1100_0000) != 0b1000_0000 {
|
||||
return false;
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3-byte: 1110_xxxx 10xx_xxxx 10xx_xxxx
|
||||
if (b & 0b1111_0000) == 0b1110_0000 {
|
||||
if i + 2 >= bytes.len() {
|
||||
return false;
|
||||
}
|
||||
let (b1, b2) = (bytes[i + 1], bytes[i + 2]);
|
||||
if (b1 & 0b1100_0000) != 0b1000_0000 || (b2 & 0b1100_0000) != 0b1000_0000 {
|
||||
return false;
|
||||
}
|
||||
if b == 0b1110_0000 && b1 < 0b1010_0000 {
|
||||
// to forbid overlongs, second byte must be >= 0xA0
|
||||
return false;
|
||||
}
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (b & 0b1111_1000) == 0b1111_0000 {
|
||||
if b > 0b1111_0100 {
|
||||
return false;
|
||||
}
|
||||
if i + 3 >= bytes.len() {
|
||||
return false;
|
||||
}
|
||||
let (b1, b2, b3) = (bytes[i + 1], bytes[i + 2], bytes[i + 3]);
|
||||
if (b1 & 0b1100_0000) != 0b1000_0000
|
||||
|| (b2 & 0b1100_0000) != 0b1000_0000
|
||||
|| (b3 & 0b1100_0000) != 0b1000_0000
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if b == 0b1111_0000 && b1 < 0b1001_0000 {
|
||||
// to forbid overlongs for > U+FFFF, second byte must be >= 0x90
|
||||
return false;
|
||||
} else if b == 0b1111_0100 && b1 > 0b1000_1111 {
|
||||
// to stay <= U+10FFFF, second byte must be <= 0x8F
|
||||
return false;
|
||||
}
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_valid_wtf8() {
|
||||
let cases: &[(&[u8], bool)] = &[
|
||||
// Valid ASCII
|
||||
(b"hello", true),
|
||||
// Valid 2-byte UTF-8
|
||||
(&[0xc2, 0xa0], true), // U+00A0
|
||||
// Invalid 2-byte: overlong encoding
|
||||
(&[0xc0, 0x80], false), // overlong for U+0000
|
||||
(&[0xc1, 0xbf], false), // overlong for U+007F
|
||||
// Valid 3-byte UTF-8
|
||||
(&[0xe0, 0xa0, 0x80], true), // U+0800
|
||||
// Invalid 3-byte: overlong encoding
|
||||
(&[0xe0, 0x9f, 0xbf], false), // overlong for U+07FF
|
||||
// WTF-8 specific: unpaired surrogates (should be valid in WTF-8)
|
||||
((&[0xed, 0xa0, 0x80]), true), // U+D800 = ED A0 80 (high surrogate)
|
||||
((&[0xed, 0xbf, 0xbf]), true), // U+DFFF = ED BF BF (low surrogate)
|
||||
// Valid 4-byte UTF-8
|
||||
((&[0xf0, 0x90, 0x80, 0x80]), true), // U+10000
|
||||
// Invalid 4-byte: overlong
|
||||
((&[0xf0, 0x8f, 0xbf, 0xbf]), false), // overlong for U+FFFF
|
||||
// Invalid 4-byte: beyond U+10FFFF
|
||||
((&[0xf4, 0x90, 0x80, 0x80]), false), // U+110000
|
||||
// Invalid continuation byte
|
||||
((&[0xc2, 0x00]), false),
|
||||
// Incomplete sequence
|
||||
((&[0xc2]), false),
|
||||
((&[0xe0, 0xa0]), false),
|
||||
((&[0xf0, 0x90, 0x80]), false),
|
||||
];
|
||||
|
||||
for &(input, expected) in cases {
|
||||
assert_eq!(valid_wtf8(input), expected, "input: {:?}", input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,8 +35,11 @@ impl FromWtf8 for OsStr {
|
|||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// FIXME: validate WTF-8
|
||||
Ok(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })
|
||||
if super::valid_wtf8(wtf8) {
|
||||
Ok(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Invalid WTF-8 sequence"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,8 +64,11 @@ impl FromWtf8Vec for OsString {
|
|||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// FIXME: validate WTF-8
|
||||
Ok(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })
|
||||
if super::valid_wtf8(&wtf8) {
|
||||
Ok(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Invalid WTF-8 sequence"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{io, path::Path, sync::Arc};
|
||||
use std::{io, sync::Arc};
|
||||
|
||||
use tokio::io::{AsyncWriteExt, BufReader, BufWriter};
|
||||
use yazi_config::vfs::{ProviderSftp, Vfs};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use percent_encoding::percent_decode;
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::{scheme::SchemeKind, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
|
|
@ -24,8 +27,8 @@ impl Reporter {
|
|||
}
|
||||
}
|
||||
|
||||
fn report_local<'a>(&self, url: UrlCow<'a>) {
|
||||
let Some((parent, name)) = url.pair() else { return };
|
||||
fn report_local(&self, url: UrlCow) {
|
||||
let Some((parent, urn)) = url.pair() else { return };
|
||||
|
||||
// FIXME: LINKED should return Url instead of Path
|
||||
let linked = LINKED.read();
|
||||
|
|
@ -37,19 +40,22 @@ impl Reporter {
|
|||
self.local_tx.send(url.to_owned()).ok();
|
||||
self.local_tx.send(parent.to_owned()).ok();
|
||||
}
|
||||
if name.ext().is_some_and(|e| e == "%tmp") {
|
||||
|
||||
if urn.ext().is_some_and(|e| e == "%tmp") {
|
||||
continue;
|
||||
}
|
||||
// SFTP caches
|
||||
// todo!();
|
||||
// if let Some(dir) = watched.find_by_cache(&parent.loc()) {
|
||||
// self.remote_tx.send(dir.join(name)).ok();
|
||||
// self.remote_tx.send(dir.to_owned()).ok();
|
||||
// }
|
||||
|
||||
// Virtual caches
|
||||
let Some(dir) = watched.find_by_cache(parent.loc()) else { continue };
|
||||
let Some(name) = url.name() else { continue };
|
||||
if let Ok(u) = dir.try_join(Cow::from(percent_decode(name.encoded_bytes()))) {
|
||||
self.remote_tx.send(u).ok();
|
||||
}
|
||||
self.remote_tx.send(dir).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn report_remote<'a>(&self, url: UrlCow<'a>) {
|
||||
fn report_remote(&self, url: UrlCow) {
|
||||
let Some(parent) = url.parent() else { return };
|
||||
if !WATCHED.read().contains(parent) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||
use hashbrown::HashSet;
|
||||
use percent_encoding::percent_decode_str;
|
||||
use yazi_fs::{Xdg, path::PercentEncoding};
|
||||
use yazi_shared::url::{AsUrl, UrlBuf, UrlLike};
|
||||
use yazi_shared::{path::{Component, PathBufDyn, PathDyn, PathLike}, pool::InternStr, scheme::SchemeKind, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Watched(HashSet<UrlBuf>);
|
||||
|
|
@ -28,28 +28,24 @@ impl Watched {
|
|||
#[inline]
|
||||
pub(crate) fn remove(&mut self, url: impl AsUrl) { self.0.remove(&url.as_url()); }
|
||||
|
||||
pub(super) fn find_by_cache(&self, cache: &Path) -> Option<&UrlBuf> {
|
||||
let mut it = cache.strip_prefix(Xdg::cache_dir()).ok()?.components();
|
||||
pub(super) fn find_by_cache(&self, cache: PathDyn) -> Option<UrlBuf> {
|
||||
let mut it = cache.try_strip_prefix(Xdg::cache_dir()).ok()?.components();
|
||||
|
||||
let l1 = it.next()?.as_os_str().to_str()?;
|
||||
let (l2, rel) =
|
||||
if let Ok(p) = it.as_path().strip_prefix(".%2F") { (p, true) } else { (it.as_path(), false) };
|
||||
// Parse domain
|
||||
let domain = it.next()?.as_normal()?.to_str().ok()?;
|
||||
let domain = percent_decode_str(domain.strip_prefix("sftp-")?).decode_utf8().ok()?.intern();
|
||||
|
||||
let domain = percent_decode_str(l1.strip_prefix("sftp-")?).decode_utf8().ok()?;
|
||||
let loc = l2.percent_decode();
|
||||
// Parse path
|
||||
let (path, abs) =
|
||||
if let Ok(p) = it.path().try_strip_prefix(".%2F") { (p, false) } else { (it.path(), true) };
|
||||
let path = path.percent_decode(SchemeKind::Sftp).ok()?;
|
||||
let path = PathBufDyn::from_components(
|
||||
SchemeKind::Sftp,
|
||||
Some(Component::RootDir).filter(|_| abs).into_iter().chain(path.components()),
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
self.0.iter().find(|u| {
|
||||
todo!()
|
||||
// if u.scheme != SchemeRef::Sftp(&domain) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// let mut it = u.loc.components();
|
||||
// if it.next() == Some(std::path::Component::RootDir) {
|
||||
// !rel && it.as_path().as_os_str().as_encoded_bytes() == loc.as_ref()
|
||||
// } else {
|
||||
// rel && u.loc.as_os_str().as_encoded_bytes() == loc.as_ref()
|
||||
// }
|
||||
})
|
||||
let url = UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), domain };
|
||||
if self.contains(&url) { Some(url) } else { None }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue