fix: avoid sync I/O in async functions

This commit is contained in:
sxyazi 2026-03-07 15:25:25 +08:00
parent 3909f55097
commit f5c13ebca3
No known key found for this signature in database

View file

@ -1,9 +1,8 @@
use std::{io, sync::Arc, time::Duration};
use std::{io, sync::Arc, time::{Duration, SystemTime}};
use russh::keys::PrivateKeyWithHashAlg;
use yazi_config::vfs::ServiceSftp;
use yazi_fs::provider::local::Local;
use yazi_shared::timestamp_us;
#[derive(Clone, Copy)]
pub(super) struct Conn {
@ -11,6 +10,12 @@ pub(super) struct Conn {
pub(super) config: &'static ServiceSftp,
}
macro_rules! cfg_err {
($($args:tt)*) => {
russh::Error::InvalidConfig(format!($($args)*))
};
}
impl russh::client::Handler for Conn {
type Error = russh::Error;
@ -102,7 +107,7 @@ impl Conn {
pref: Arc<russh::client::Config>,
) -> Result<russh::client::Handle<Self>, russh::Error> {
let Some(password) = &self.config.password else {
return Err(russh::Error::InvalidConfig("Password not provided".to_owned()));
return Err(cfg_err!("Password not provided"));
};
let mut session =
@ -111,7 +116,7 @@ impl Conn {
if session.authenticate_password(&self.config.user, password).await?.success() {
Ok(session)
} else {
Err(russh::Error::InvalidConfig("Password authentication failed".to_owned()))
Err(cfg_err!("Password authentication failed"))
}
}
@ -121,14 +126,13 @@ impl Conn {
) -> Result<russh::client::Handle<Self>, russh::Error> {
let key_file = &self.config.key_file;
if key_file.as_os_str().is_empty() {
return Err(russh::Error::InvalidConfig("Key file not provided".to_owned()));
return Err(cfg_err!("Key file not provided"));
};
let key = Local::regular(key_file)
.read_to_string()
.await
.map_err(|e| russh::Error::InvalidConfig(format!("Failed to read key file: {e}")))?;
.map_err(|e| cfg_err!("Failed to read key file: {e}"))?;
let key = russh::keys::decode_secret_key(&key, self.config.key_passphrase.as_deref())?;
let mut session =
@ -144,11 +148,7 @@ impl Conn {
)
.await?;
if result.success() {
Ok(session)
} else {
Err(russh::Error::InvalidConfig("Public key authentication failed".to_owned()))
}
if result.success() { Ok(session) } else { Err(cfg_err!("Public key authentication failed")) }
}
async fn connect_by_key_and_cert(
@ -157,62 +157,52 @@ impl Conn {
) -> Result<russh::client::Handle<Self>, russh::Error> {
let key_file = &self.config.key_file;
if key_file.as_os_str().is_empty() {
return Err(russh::Error::InvalidConfig("Key file not provided".to_owned()));
return Err(cfg_err!("Key file not provided"));
};
let cert_file = &self.config.cert_file;
if cert_file.as_os_str().is_empty() {
return Err(russh::Error::InvalidConfig("Cert file not provided".to_owned()));
return Err(cfg_err!("Cert file not provided"));
};
let cert = russh::keys::load_openssh_certificate(cert_file).map_err(
|e: russh::keys::ssh_key::Error| {
russh::Error::InvalidConfig(format!("Failed to read cert file: {e}"))
},
)?;
if !self.config.no_cert_verify {
cert.verify_signature().map_err(|e| {
russh::Error::InvalidConfig(format!("Certificate signature verification failed: {e}"))
})?;
let unix_timestamp = timestamp_us() / 1_000_000;
if unix_timestamp < cert.valid_after() || unix_timestamp > cert.valid_before() {
let format_time = |ts: u64| {
i64::try_from(ts)
.ok()
.and_then(chrono::DateTime::from_timestamp_secs)
.map(|dt| dt.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "unknown date".to_string())
};
let start = format_time(cert.valid_after());
let end = format_time(cert.valid_before());
return Err(russh::Error::InvalidConfig(format!(
"Certificate is invalid. The validity range is {} to {}",
start, end
)));
}
}
// Decode the key and cert files
let key = Local::regular(key_file)
.read_to_string()
.await
.map_err(|e| russh::Error::InvalidConfig(format!("Failed to read key file: {e}")))?;
.map_err(|e| cfg_err!("Failed to read key file: {e}"))?;
let key = russh::keys::decode_secret_key(&key, self.config.key_passphrase.as_deref())?;
let cert = Local::regular(cert_file)
.read_to_string()
.await
.map_err(|e| cfg_err!("Failed to read cert file: {e}"))?;
let cert = russh::keys::Certificate::from_openssh(&cert)?;
// Verify the certificate
if !self.config.no_cert_verify {
cert
.verify_signature()
.map_err(|e| cfg_err!("Certificate signature verification failed: {e}"))?;
let now: chrono::DateTime<chrono::Local> = SystemTime::now().into();
let start: chrono::DateTime<chrono::Local> = cert.valid_after_time().into();
let end: chrono::DateTime<chrono::Local> = cert.valid_before_time().into();
if now < start || now > end {
return Err(cfg_err!(
"Certificate is out of the validity range of '{}' to '{}'",
start.to_rfc2822(),
end.to_rfc2822()
));
}
}
let mut session =
russh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;
let result = session.authenticate_openssh_cert(&self.config.user, Arc::new(key), cert).await?;
if result.success() {
if session.authenticate_openssh_cert(&self.config.user, Arc::new(key), cert).await?.success() {
Ok(session)
} else {
Err(russh::Error::InvalidConfig(
"Public key with certificate authentication failed".to_owned(),
))
Err(cfg_err!("Public key with certificate authentication failed"))
}
}
@ -222,7 +212,7 @@ impl Conn {
) -> Result<russh::client::Handle<Self>, russh::Error> {
let identity_agent = &self.config.identity_agent;
if identity_agent.as_os_str().is_empty() {
return Err(russh::Error::InvalidConfig("Identity agent not provided".to_owned()));
return Err(cfg_err!("Identity agent not provided"));
};
#[cfg(unix)]
@ -233,7 +223,7 @@ impl Conn {
let keys = agent.request_identities().await?;
if keys.is_empty() {
return Err(russh::Error::InvalidConfig("No keys found in SSH agent".to_owned()));
return Err(cfg_err!("No keys found in SSH agent"));
}
let mut session =
@ -249,6 +239,6 @@ impl Conn {
}
}
Err(russh::Error::InvalidConfig("Public key authentication via agent failed".to_owned()))
Err(cfg_err!("Public key authentication via agent failed"))
}
}