mirror of
https://github.com/veeso/termscp.git
synced 2026-06-08 14:18:41 +02:00
refactor: split parser internals into focused modules
This commit is contained in:
@@ -2,106 +2,56 @@
|
|||||||
//!
|
//!
|
||||||
//! `parser` is the module which provides utilities for parsing different kind of stuff
|
//! `parser` is the module which provides utilities for parsing different kind of stuff
|
||||||
|
|
||||||
// Locals
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
// Ext
|
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use lazy_regex::{Lazy, Regex};
|
use lazy_regex::{Lazy, Regex};
|
||||||
use tuirealm::ratatui::style::Color;
|
use tuirealm::ratatui::style::Color;
|
||||||
use tuirealm::utils::parser as tuirealm_parser;
|
use tuirealm::utils::parser as tuirealm_parser;
|
||||||
|
|
||||||
#[cfg(smb)]
|
use crate::filetransfer::FileTransferParams;
|
||||||
use crate::filetransfer::params::SmbParams;
|
#[path = "parser/credentials.rs"]
|
||||||
use crate::filetransfer::params::{
|
mod credentials;
|
||||||
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, WebDAVProtocolParams,
|
#[path = "parser/ports.rs"]
|
||||||
};
|
mod ports;
|
||||||
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
#[path = "parser/protocol.rs"]
|
||||||
#[cfg(not(test))] // NOTE: don't use configuration during tests
|
mod protocol;
|
||||||
use crate::system::config_client::ConfigClient;
|
#[path = "parser/remote.rs"]
|
||||||
#[cfg(not(test))] // NOTE: don't use configuration during tests
|
mod remote;
|
||||||
use crate::system::environment;
|
|
||||||
|
|
||||||
// Regex
|
/// This regex matches the protocol used as option.
|
||||||
|
pub(super) static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> =
|
||||||
|
lazy_regex!(r"(?:([a-z0-9]+)://)?(\\\\)?(?:(.+))");
|
||||||
|
|
||||||
/**
|
/// Regex matches generic remote options.
|
||||||
* This regex matches the protocol used as option
|
pub(super) static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
||||||
* Regex matches:
|
|
||||||
* - group 1: Some(protocol) | None
|
|
||||||
* - group 2: SMB windows prefix
|
|
||||||
* - group 3: Some(other args)
|
|
||||||
*/
|
|
||||||
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(\\\\)?(?:(.+))");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regex matches:
|
|
||||||
* - group 1: Some(user) | None
|
|
||||||
* - group 2: Address
|
|
||||||
* - group 3: Some(port) | None
|
|
||||||
* - group 4: Some(path) | None
|
|
||||||
*/
|
|
||||||
static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
|
||||||
r"(?:(.+[^@])@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?"
|
r"(?:(.+[^@])@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?"
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/// Regex matches WebDAV remote options.
|
||||||
* Regex matches:
|
pub(super) static REMOTE_WEBDAV_OPT_REGEX: Lazy<Regex> =
|
||||||
* - group 1: Username
|
|
||||||
* - group 2: Password
|
|
||||||
* - group 2: Uri
|
|
||||||
* - group 4: Some(path) | None
|
|
||||||
*/
|
|
||||||
static REMOTE_WEBDAV_OPT_REGEX: Lazy<Regex> =
|
|
||||||
lazy_regex!(r"(?:([^:]+):)(?:(.+[^@])@)(?:([^/]+))(?:(.+))?");
|
lazy_regex!(r"(?:([^:]+):)(?:(.+[^@])@)(?:([^/]+))(?:(.+))?");
|
||||||
|
|
||||||
/**
|
/// Regex matches kube remote options.
|
||||||
* Regex matches: {namespace}[@{cluster_url}]$/{path}
|
pub(super) static REMOTE_KUBE_OPT_REGEX: Lazy<Regex> =
|
||||||
* - group 1: Namespace
|
lazy_regex!(r"(?:([^@]+))(@(?:([^$]+)))?(\$(?:(.+)))?");
|
||||||
* - group 3: Some(cluster_url) | None
|
|
||||||
* - group 5: Some(path) | None
|
|
||||||
*/
|
|
||||||
static REMOTE_KUBE_OPT_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([^@]+))(@(?:([^$]+)))?(\$(?:(.+)))?");
|
|
||||||
|
|
||||||
/**
|
/// Regex matches s3 remote options.
|
||||||
* Regex matches:
|
pub(super) static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
|
||||||
* - group 1: Bucket
|
|
||||||
* - group 2: Region
|
|
||||||
* - group 3: Some(profile) | None
|
|
||||||
* - group 4: Some(path) | None
|
|
||||||
*/
|
|
||||||
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
|
|
||||||
lazy_regex!(r"(?:(.+[^@])@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
|
lazy_regex!(r"(?:(.+[^@])@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
|
||||||
|
|
||||||
/**
|
/// Regex matches SMB remote options on Unix platforms.
|
||||||
* Regex matches:
|
|
||||||
* - group 1: username
|
|
||||||
* - group 2: address
|
|
||||||
* - group 3: port?
|
|
||||||
* - group 4: share?
|
|
||||||
* - group 5: remote-dir?
|
|
||||||
*/
|
|
||||||
#[cfg(smb_unix)]
|
#[cfg(smb_unix)]
|
||||||
static REMOTE_SMB_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
pub(super) static REMOTE_SMB_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
||||||
r"(?:(.+[^@])@)?(?:([^/:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?:/([^/]+))?(?:(/.+))?"
|
r"(?:(.+[^@])@)?(?:([^/:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?:/([^/]+))?(?:(/.+))?"
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/// Regex matches SMB remote options on Windows.
|
||||||
* Regex matches:
|
|
||||||
* - group 1: username?
|
|
||||||
* - group 2: address
|
|
||||||
* - group 3: share
|
|
||||||
* - group 4: remote-dir?
|
|
||||||
*/
|
|
||||||
#[cfg(smb_windows)]
|
#[cfg(smb_windows)]
|
||||||
static REMOTE_SMB_OPT_REGEX: Lazy<Regex> =
|
pub(super) static REMOTE_SMB_OPT_REGEX: Lazy<Regex> =
|
||||||
lazy_regex!(r"(?:(.+[^@])@)?(?:([^:\\]+))(?:\\([^\\]+))?(?:(\\.+))?");
|
lazy_regex!(r"(?:(.+[^@])@)?(?:([^:\\]+))(?:\\([^\\]+))?(?:(\\.+))?");
|
||||||
|
|
||||||
/**
|
/// Regex matches semantic versions.
|
||||||
* Regex matches:
|
|
||||||
* - group 1: Version
|
|
||||||
* E.g. termscp-0.3.2 => 0.3.2; v0.4.0 => 0.4.0
|
|
||||||
*/
|
|
||||||
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r"v?((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))");
|
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r"v?((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,19 +61,6 @@ static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r"v?((0|[1-9]\d*)\.(0|[1-9]\d*)\.
|
|||||||
*/
|
*/
|
||||||
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
|
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
|
||||||
|
|
||||||
fn capture_group_to_string(
|
|
||||||
groups: ®ex::Captures<'_>,
|
|
||||||
index: usize,
|
|
||||||
field_name: &str,
|
|
||||||
) -> Result<String, String> {
|
|
||||||
groups
|
|
||||||
.get(index)
|
|
||||||
.map(|group| group.as_str().to_string())
|
|
||||||
.ok_or_else(|| format!("Missing {field_name}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- remote opts
|
|
||||||
|
|
||||||
/// Parse remote option string. Returns in case of success a RemoteOptions struct
|
/// Parse remote option string. Returns in case of success a RemoteOptions struct
|
||||||
/// For ssh if username is not provided, current user will be used.
|
/// For ssh if username is not provided, current user will be used.
|
||||||
/// In case of error, message is returned
|
/// In case of error, message is returned
|
||||||
@@ -156,256 +93,7 @@ fn capture_group_to_string(
|
|||||||
/// \\<address>\<share>[\path]
|
/// \\<address>\<share>[\path]
|
||||||
///
|
///
|
||||||
pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||||
// Set protocol to default protocol
|
remote::parse_remote_opt(s)
|
||||||
#[cfg(not(test))] // NOTE: don't use configuration during tests
|
|
||||||
let default_protocol: FileTransferProtocol = match environment::init_config_dir() {
|
|
||||||
Ok(p) => match p {
|
|
||||||
Some(p) => {
|
|
||||||
// Create config client
|
|
||||||
let (config_path, ssh_key_path) = environment::get_config_paths(p.as_path());
|
|
||||||
match ConfigClient::new(config_path.as_path(), ssh_key_path.as_path()) {
|
|
||||||
Ok(cli) => cli.get_default_protocol(),
|
|
||||||
Err(_) => FileTransferProtocol::Sftp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => FileTransferProtocol::Sftp,
|
|
||||||
},
|
|
||||||
Err(_) => FileTransferProtocol::Sftp,
|
|
||||||
};
|
|
||||||
#[cfg(test)] // NOTE: during test set protocol just to Sftp
|
|
||||||
let default_protocol: FileTransferProtocol = FileTransferProtocol::Sftp;
|
|
||||||
// Get protocol
|
|
||||||
let (protocol, remote): (FileTransferProtocol, String) =
|
|
||||||
parse_remote_opt_protocol(s, default_protocol)?;
|
|
||||||
// Match against regex for protocol type
|
|
||||||
match protocol {
|
|
||||||
FileTransferProtocol::AwsS3 => parse_s3_remote_opt(remote.as_str()),
|
|
||||||
FileTransferProtocol::Kube => parse_kube_remote_opt(remote.as_str()),
|
|
||||||
#[cfg(smb)]
|
|
||||||
FileTransferProtocol::Smb => parse_smb_remote_opts(remote.as_str()),
|
|
||||||
FileTransferProtocol::WebDAV => {
|
|
||||||
// get the differnece between s and remote
|
|
||||||
let prefix = if s.starts_with("https") {
|
|
||||||
"https"
|
|
||||||
} else {
|
|
||||||
"http"
|
|
||||||
};
|
|
||||||
|
|
||||||
parse_webdav_remote_opt(remote.as_str(), prefix)
|
|
||||||
}
|
|
||||||
protocol => parse_generic_remote_opt(remote.as_str(), protocol),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse protocol from CLI option. In case of success, return the protocol to be used and the remaining arguments
|
|
||||||
fn parse_remote_opt_protocol(
|
|
||||||
s: &str,
|
|
||||||
default: FileTransferProtocol,
|
|
||||||
) -> Result<(FileTransferProtocol, String), String> {
|
|
||||||
match REMOTE_OPT_PROTOCOL_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
// Parse protocol or use default
|
|
||||||
let protocol = groups.get(1).map(|x| {
|
|
||||||
FileTransferProtocol::from_str(x.as_str())
|
|
||||||
.map_err(|_| format!("Unknown protocol \"{}\"", x.as_str()))
|
|
||||||
});
|
|
||||||
let protocol = match protocol {
|
|
||||||
Some(Ok(protocol)) => protocol,
|
|
||||||
Some(Err(err)) => return Err(err),
|
|
||||||
#[cfg(smb_windows)]
|
|
||||||
None if groups.get(2).is_some() => FileTransferProtocol::Smb,
|
|
||||||
None => default,
|
|
||||||
};
|
|
||||||
// Return protocol and remaining arguments
|
|
||||||
Ok((
|
|
||||||
protocol,
|
|
||||||
groups
|
|
||||||
.get(3)
|
|
||||||
.map(|x| x.as_str().to_string())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
None => Err("Invalid args".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse generic remote options
|
|
||||||
fn parse_generic_remote_opt(
|
|
||||||
s: &str,
|
|
||||||
protocol: FileTransferProtocol,
|
|
||||||
) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_GENERIC_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
// Match user
|
|
||||||
let username = groups.get(1).map(|x| x.as_str().to_string());
|
|
||||||
// Get address
|
|
||||||
let address: String = match groups.get(2) {
|
|
||||||
Some(group) => group.as_str().to_string(),
|
|
||||||
None => return Err(String::from("Missing address")),
|
|
||||||
};
|
|
||||||
// Get port
|
|
||||||
let port: u16 = match groups.get(3) {
|
|
||||||
Some(port) => match port.as_str().parse::<u16>() {
|
|
||||||
// Try to parse port
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(err) => return Err(format!("Bad port \"{}\": {}", port.as_str(), err)),
|
|
||||||
},
|
|
||||||
None => match protocol {
|
|
||||||
// Set port based on protocol
|
|
||||||
FileTransferProtocol::Ftp(_) => 21,
|
|
||||||
FileTransferProtocol::Scp => 22,
|
|
||||||
FileTransferProtocol::Sftp => 22,
|
|
||||||
_ => 22, // Doesn't matter
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Get workdir
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
let params: ProtocolParams = ProtocolParams::Generic(
|
|
||||||
GenericProtocolParams::default()
|
|
||||||
.address(address)
|
|
||||||
.port(port)
|
|
||||||
.username(username),
|
|
||||||
);
|
|
||||||
Ok(FileTransferParams::new(protocol, params).remote_path(remote_path))
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_webdav_remote_opt(s: &str, prefix: &str) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_WEBDAV_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
let username = capture_group_to_string(&groups, 1, "username")?;
|
|
||||||
let password = capture_group_to_string(&groups, 2, "password")?;
|
|
||||||
let uri = capture_group_to_string(&groups, 3, "server URI")?;
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
|
|
||||||
let params = ProtocolParams::WebDAV(WebDAVProtocolParams {
|
|
||||||
uri: format!("{}://{}", prefix, uri),
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
Ok(
|
|
||||||
FileTransferParams::new(FileTransferProtocol::WebDAV, params)
|
|
||||||
.remote_path(remote_path),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse remote options for s3 protocol
|
|
||||||
fn parse_s3_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_S3_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
let bucket: String = groups
|
|
||||||
.get(1)
|
|
||||||
.map(|x| x.as_str().to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let region: String = groups
|
|
||||||
.get(2)
|
|
||||||
.map(|x| x.as_str().to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let profile: Option<String> = groups.get(3).map(|x| x.as_str().to_string());
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
Ok(FileTransferParams::new(
|
|
||||||
FileTransferProtocol::AwsS3,
|
|
||||||
ProtocolParams::AwsS3(AwsS3Params::new(bucket, Some(region), profile)),
|
|
||||||
)
|
|
||||||
.remote_path(remote_path))
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_kube_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_KUBE_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
let namespace: Option<String> = groups.get(1).map(|x| x.as_str().to_string());
|
|
||||||
let cluster_url: Option<String> = groups.get(3).map(|x| x.as_str().to_string());
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(5).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
Ok(FileTransferParams::new(
|
|
||||||
FileTransferProtocol::Kube,
|
|
||||||
ProtocolParams::Kube(KubeProtocolParams {
|
|
||||||
namespace,
|
|
||||||
cluster_url,
|
|
||||||
username: None,
|
|
||||||
client_cert: None,
|
|
||||||
client_key: None,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.remote_path(remote_path))
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse remote options for smb protocol
|
|
||||||
#[cfg(smb_unix)]
|
|
||||||
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_SMB_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
let username = match groups.get(1) {
|
|
||||||
Some(group) => Some(group.as_str().to_string()),
|
|
||||||
None => whoami::username().ok(),
|
|
||||||
};
|
|
||||||
let address = match groups.get(2) {
|
|
||||||
Some(group) => group.as_str().to_string(),
|
|
||||||
None => return Err(String::from("Missing address")),
|
|
||||||
};
|
|
||||||
let port = match groups.get(3) {
|
|
||||||
Some(port) => match port.as_str().parse::<u16>() {
|
|
||||||
// Try to parse port
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(err) => return Err(format!("Bad port \"{}\": {}", port.as_str(), err)),
|
|
||||||
},
|
|
||||||
None => 445,
|
|
||||||
};
|
|
||||||
let share = match groups.get(4) {
|
|
||||||
Some(group) => group.as_str().to_string(),
|
|
||||||
None => return Err(String::from("Missing address")),
|
|
||||||
};
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(5).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
|
|
||||||
Ok(FileTransferParams::new(
|
|
||||||
FileTransferProtocol::Smb,
|
|
||||||
ProtocolParams::Smb(SmbParams::new(address, share).port(port).username(username)),
|
|
||||||
)
|
|
||||||
.remote_path(remote_path))
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(smb_windows)]
|
|
||||||
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
|
||||||
match REMOTE_SMB_OPT_REGEX.captures(s) {
|
|
||||||
Some(groups) => {
|
|
||||||
let username = groups.get(1).map(|x| x.as_str().to_string());
|
|
||||||
let address = match groups.get(2) {
|
|
||||||
Some(group) => group.as_str().to_string(),
|
|
||||||
None => return Err(String::from("Missing address")),
|
|
||||||
};
|
|
||||||
let share = match groups.get(3) {
|
|
||||||
Some(group) => group.as_str().to_string(),
|
|
||||||
None => return Err(String::from("Missing address")),
|
|
||||||
};
|
|
||||||
let remote_path: Option<PathBuf> =
|
|
||||||
groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
|
||||||
|
|
||||||
Ok(FileTransferParams::new(
|
|
||||||
FileTransferProtocol::Smb,
|
|
||||||
ProtocolParams::Smb(SmbParams::new(address, share).username(username)),
|
|
||||||
)
|
|
||||||
.remote_path(remote_path))
|
|
||||||
}
|
|
||||||
None => Err(String::from("Bad remote host syntax!")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse semver string
|
/// Parse semver string
|
||||||
@@ -500,10 +188,12 @@ pub fn parse_bytesize<S: AsRef<str>>(bytes: S) -> Option<ByteSize> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_utils_parse_remote_opt() {
|
fn test_utils_parse_remote_opt() {
|
||||||
|
|||||||
11
src/utils/parser/credentials.rs
Normal file
11
src/utils/parser/credentials.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pub(super) fn optional_capture(groups: ®ex::Captures<'_>, index: usize) -> Option<String> {
|
||||||
|
groups.get(index).map(|group| group.as_str().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn required_capture(
|
||||||
|
groups: ®ex::Captures<'_>,
|
||||||
|
index: usize,
|
||||||
|
field_name: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
optional_capture(groups, index).ok_or_else(|| format!("Missing {field_name}"))
|
||||||
|
}
|
||||||
19
src/utils/parser/ports.rs
Normal file
19
src/utils/parser/ports.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
|
||||||
|
pub(super) fn default_port_for_protocol(protocol: FileTransferProtocol) -> u16 {
|
||||||
|
match protocol {
|
||||||
|
FileTransferProtocol::Ftp(_) => 21,
|
||||||
|
FileTransferProtocol::Scp | FileTransferProtocol::Sftp => 22,
|
||||||
|
_ => 22,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn parse_port(port: Option<regex::Match<'_>>, default: u16) -> Result<u16, String> {
|
||||||
|
match port {
|
||||||
|
Some(port) => port
|
||||||
|
.as_str()
|
||||||
|
.parse::<u16>()
|
||||||
|
.map_err(|err| format!("Bad port \"{}\": {err}", port.as_str())),
|
||||||
|
None => Ok(default),
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/utils/parser/protocol.rs
Normal file
28
src/utils/parser/protocol.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::REMOTE_OPT_PROTOCOL_REGEX;
|
||||||
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
|
||||||
|
pub(super) fn parse_remote_opt_protocol(
|
||||||
|
s: &str,
|
||||||
|
default: FileTransferProtocol,
|
||||||
|
) -> Result<(FileTransferProtocol, String), String> {
|
||||||
|
let groups = REMOTE_OPT_PROTOCOL_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| "Invalid args".to_string())?;
|
||||||
|
|
||||||
|
let protocol = match groups.get(1) {
|
||||||
|
Some(protocol) => FileTransferProtocol::from_str(protocol.as_str())
|
||||||
|
.map_err(|_| format!("Unknown protocol \"{}\"", protocol.as_str()))?,
|
||||||
|
#[cfg(smb_windows)]
|
||||||
|
None if groups.get(2).is_some() => FileTransferProtocol::Smb,
|
||||||
|
None => default,
|
||||||
|
};
|
||||||
|
|
||||||
|
let remote = groups
|
||||||
|
.get(3)
|
||||||
|
.map(|group| group.as_str().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Ok((protocol, remote))
|
||||||
|
}
|
||||||
177
src/utils/parser/remote.rs
Normal file
177
src/utils/parser/remote.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(smb)]
|
||||||
|
use super::REMOTE_SMB_OPT_REGEX;
|
||||||
|
use super::credentials::{optional_capture, required_capture};
|
||||||
|
use super::ports::{default_port_for_protocol, parse_port};
|
||||||
|
use super::protocol::parse_remote_opt_protocol;
|
||||||
|
use super::{
|
||||||
|
REMOTE_GENERIC_OPT_REGEX, REMOTE_KUBE_OPT_REGEX, REMOTE_S3_OPT_REGEX, REMOTE_WEBDAV_OPT_REGEX,
|
||||||
|
};
|
||||||
|
#[cfg(smb)]
|
||||||
|
use crate::filetransfer::params::SmbParams;
|
||||||
|
use crate::filetransfer::params::{
|
||||||
|
AwsS3Params, GenericProtocolParams, KubeProtocolParams, ProtocolParams, WebDAVProtocolParams,
|
||||||
|
};
|
||||||
|
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
|
||||||
|
#[cfg(not(test))]
|
||||||
|
use crate::system::config_client::ConfigClient;
|
||||||
|
#[cfg(not(test))]
|
||||||
|
use crate::system::environment;
|
||||||
|
|
||||||
|
pub(super) fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let default_protocol = default_protocol();
|
||||||
|
let (protocol, remote) = parse_remote_opt_protocol(s, default_protocol)?;
|
||||||
|
|
||||||
|
match protocol {
|
||||||
|
FileTransferProtocol::AwsS3 => parse_s3_remote_opt(remote.as_str()),
|
||||||
|
FileTransferProtocol::Kube => parse_kube_remote_opt(remote.as_str()),
|
||||||
|
#[cfg(smb)]
|
||||||
|
FileTransferProtocol::Smb => parse_smb_remote_opts(remote.as_str()),
|
||||||
|
FileTransferProtocol::WebDAV => {
|
||||||
|
let prefix = if s.starts_with("https") {
|
||||||
|
"https"
|
||||||
|
} else {
|
||||||
|
"http"
|
||||||
|
};
|
||||||
|
|
||||||
|
parse_webdav_remote_opt(remote.as_str(), prefix)
|
||||||
|
}
|
||||||
|
protocol => parse_generic_remote_opt(remote.as_str(), protocol),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
fn default_protocol() -> FileTransferProtocol {
|
||||||
|
match environment::init_config_dir() {
|
||||||
|
Ok(Some(path)) => {
|
||||||
|
let (config_path, ssh_key_path) = environment::get_config_paths(path.as_path());
|
||||||
|
match ConfigClient::new(config_path.as_path(), ssh_key_path.as_path()) {
|
||||||
|
Ok(config) => config.get_default_protocol(),
|
||||||
|
Err(_) => FileTransferProtocol::Sftp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) | Err(_) => FileTransferProtocol::Sftp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn default_protocol() -> FileTransferProtocol {
|
||||||
|
FileTransferProtocol::Sftp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_generic_remote_opt(
|
||||||
|
s: &str,
|
||||||
|
protocol: FileTransferProtocol,
|
||||||
|
) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_GENERIC_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let username = optional_capture(&groups, 1);
|
||||||
|
let address = required_capture(&groups, 2, "address")?;
|
||||||
|
let port = parse_port(groups.get(3), default_port_for_protocol(protocol))?;
|
||||||
|
let remote_path = groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
let params = ProtocolParams::Generic(
|
||||||
|
GenericProtocolParams::default()
|
||||||
|
.address(address)
|
||||||
|
.port(port)
|
||||||
|
.username(username),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(protocol, params).remote_path(remote_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_webdav_remote_opt(s: &str, prefix: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_WEBDAV_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let username = required_capture(&groups, 1, "username")?;
|
||||||
|
let password = required_capture(&groups, 2, "password")?;
|
||||||
|
let uri = required_capture(&groups, 3, "server URI")?;
|
||||||
|
let remote_path = groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
let params = ProtocolParams::WebDAV(WebDAVProtocolParams {
|
||||||
|
uri: format!("{prefix}://{uri}"),
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(FileTransferProtocol::WebDAV, params).remote_path(remote_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_s3_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_S3_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let bucket = optional_capture(&groups, 1).unwrap_or_default();
|
||||||
|
let region = optional_capture(&groups, 2).unwrap_or_default();
|
||||||
|
let profile = optional_capture(&groups, 3);
|
||||||
|
let remote_path = groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(
|
||||||
|
FileTransferProtocol::AwsS3,
|
||||||
|
ProtocolParams::AwsS3(AwsS3Params::new(bucket, Some(region), profile)),
|
||||||
|
)
|
||||||
|
.remote_path(remote_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_kube_remote_opt(s: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_KUBE_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let namespace = optional_capture(&groups, 1);
|
||||||
|
let cluster_url = optional_capture(&groups, 3);
|
||||||
|
let remote_path = groups.get(5).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(
|
||||||
|
FileTransferProtocol::Kube,
|
||||||
|
ProtocolParams::Kube(KubeProtocolParams {
|
||||||
|
namespace,
|
||||||
|
cluster_url,
|
||||||
|
username: None,
|
||||||
|
client_cert: None,
|
||||||
|
client_key: None,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.remote_path(remote_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(smb_unix)]
|
||||||
|
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_SMB_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let username = optional_capture(&groups, 1).or_else(|| whoami::username().ok());
|
||||||
|
let address = required_capture(&groups, 2, "address")?;
|
||||||
|
let port = parse_port(groups.get(3), 445)?;
|
||||||
|
let share = required_capture(&groups, 4, "share")?;
|
||||||
|
let remote_path = groups.get(5).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(
|
||||||
|
FileTransferProtocol::Smb,
|
||||||
|
ProtocolParams::Smb(SmbParams::new(address, share).port(port).username(username)),
|
||||||
|
)
|
||||||
|
.remote_path(remote_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(smb_windows)]
|
||||||
|
fn parse_smb_remote_opts(s: &str) -> Result<FileTransferParams, String> {
|
||||||
|
let groups = REMOTE_SMB_OPT_REGEX
|
||||||
|
.captures(s)
|
||||||
|
.ok_or_else(|| String::from("Bad remote host syntax!"))?;
|
||||||
|
|
||||||
|
let username = optional_capture(&groups, 1);
|
||||||
|
let address = required_capture(&groups, 2, "address")?;
|
||||||
|
let share = required_capture(&groups, 3, "share")?;
|
||||||
|
let remote_path = groups.get(4).map(|group| PathBuf::from(group.as_str()));
|
||||||
|
|
||||||
|
Ok(FileTransferParams::new(
|
||||||
|
FileTransferProtocol::Smb,
|
||||||
|
ProtocolParams::Smb(SmbParams::new(address, share).username(username)),
|
||||||
|
)
|
||||||
|
.remote_path(remote_path))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user