feat: WebDAV support (#235)

This commit is contained in:
Christian Visintin
2024-03-02 19:23:27 +01:00
committed by GitHub
parent 5dfee2cbd9
commit c7469b8594
33 changed files with 656 additions and 85 deletions

View File

@@ -11,6 +11,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams as TransferSmbParams,
WebDAVProtocolParams,
};
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
@@ -114,6 +115,17 @@ impl From<FileTransferParams> for Bookmark {
local_path,
s3: None,
},
ProtocolParams::WebDAV(parms) => Self {
protocol,
address: Some(parms.uri),
port: None,
username: Some(parms.username),
password: Some(parms.password),
remote_path,
local_path,
s3: None,
smb: None,
},
}
}
}
@@ -161,6 +173,14 @@ impl From<Bookmark> for FileTransferParams {
Self::new(bookmark.protocol, ProtocolParams::Smb(params))
}
FileTransferProtocol::WebDAV => Self::new(
FileTransferProtocol::WebDAV,
ProtocolParams::WebDAV(WebDAVProtocolParams {
uri: bookmark.address.unwrap_or_default(),
username: bookmark.username.unwrap_or_default(),
password: bookmark.password.unwrap_or_default(),
}),
),
}
.remote_path(bookmark.remote_path) // Set entry remote_path
.local_path(bookmark.local_path) // Set entry local path
@@ -390,6 +410,35 @@ mod tests {
assert_eq!(gparams.password.as_deref().unwrap(), "password");
}
#[test]
fn ftparams_from_webdav() {
let bookmark: Bookmark = Bookmark {
address: Some(String::from("192.168.1.1")),
port: None,
protocol: FileTransferProtocol::WebDAV,
username: Some(String::from("root")),
password: Some(String::from("password")),
remote_path: Some(PathBuf::from("/tmp")),
local_path: Some(PathBuf::from("/usr")),
s3: None,
smb: None,
};
let params = FileTransferParams::from(bookmark);
assert_eq!(params.protocol, FileTransferProtocol::WebDAV);
assert_eq!(
params.remote_path.as_deref().unwrap(),
std::path::Path::new("/tmp")
);
assert_eq!(
params.local_path.as_deref().unwrap(),
std::path::Path::new("/usr")
);
let gparams = params.params.webdav_params().unwrap();
assert_eq!(gparams.uri.as_str(), "192.168.1.1");
assert_eq!(gparams.username, "root");
assert_eq!(gparams.password, "password");
}
#[test]
fn ftparams_from_s3_bookmark() {
let bookmark: Bookmark = Bookmark {

View File

@@ -12,7 +12,9 @@ use remotefs_smb::SmbOptions;
#[cfg(smb)]
use remotefs_smb::{SmbCredentials, SmbFs};
use remotefs_ssh::{ScpFs, SftpFs, SshConfigParseRule, SshOpts};
use remotefs_webdav::WebDAVFs;
use super::params::WebDAVProtocolParams;
#[cfg(not(smb))]
use super::params::{AwsS3Params, GenericProtocolParams};
#[cfg(smb)]
@@ -51,6 +53,9 @@ impl Builder {
(FileTransferProtocol::Smb, ProtocolParams::Smb(params)) => {
Box::new(Self::smb_client(params))
}
(FileTransferProtocol::WebDAV, ProtocolParams::WebDAV(params)) => {
Box::new(Self::webdav_client(params))
}
(protocol, params) => {
error!("Invalid params for protocol '{:?}'", protocol);
panic!("Invalid protocol '{protocol:?}' with parameters of type {params:?}")
@@ -154,6 +159,10 @@ impl Builder {
SmbFs::new(credentials)
}
fn webdav_client(params: WebDAVProtocolParams) -> WebDAVFs {
WebDAVFs::new(&params.username, &params.password, &params.uri)
}
/// Build ssh options from generic protocol params and client configuration
fn build_ssh_opts(params: GenericProtocolParams, config_client: &ConfigClient) -> SshOpts {
let mut opts = SshOpts::new(params.address.clone())

View File

@@ -18,6 +18,7 @@ pub enum FileTransferProtocol {
Scp,
Sftp,
Smb,
WebDAV,
}
// Traits
@@ -33,6 +34,7 @@ impl std::string::ToString for FileTransferProtocol {
FileTransferProtocol::Scp => "SCP",
FileTransferProtocol::Sftp => "SFTP",
FileTransferProtocol::Smb => "SMB",
FileTransferProtocol::WebDAV => "WEBDAV",
})
}
}
@@ -47,6 +49,7 @@ impl std::str::FromStr for FileTransferProtocol {
"SCP" => Ok(FileTransferProtocol::Scp),
"SFTP" => Ok(FileTransferProtocol::Sftp),
"SMB" => Ok(FileTransferProtocol::Smb),
"WEBDAV" | "HTTP" | "HTTPS" => Ok(FileTransferProtocol::WebDAV),
_ => Err(s.to_string()),
}
}
@@ -134,9 +137,17 @@ mod tests {
FileTransferProtocol::Ftp(false).to_string(),
String::from("FTP")
);
assert_eq!(
FileTransferProtocol::WebDAV.to_string(),
String::from("WEBDAV")
);
assert_eq!(FileTransferProtocol::Scp.to_string(), String::from("SCP"));
assert_eq!(FileTransferProtocol::Sftp.to_string(), String::from("SFTP"));
assert_eq!(FileTransferProtocol::AwsS3.to_string(), String::from("S3"));
assert_eq!(FileTransferProtocol::Smb.to_string(), String::from("SMB"));
assert_eq!(
FileTransferProtocol::WebDAV.to_string(),
String::from("WEBDAV")
);
}
}

View File

@@ -21,6 +21,7 @@ pub enum ProtocolParams {
Generic(GenericProtocolParams),
AwsS3(AwsS3Params),
Smb(SmbParams),
WebDAV(WebDAVProtocolParams),
}
/// Protocol params used by most common protocols
@@ -89,6 +90,7 @@ impl FileTransferParams {
ProtocolParams::AwsS3(params) => params.password_missing(),
ProtocolParams::Generic(params) => params.password_missing(),
ProtocolParams::Smb(params) => params.password_missing(),
ProtocolParams::WebDAV(params) => params.password_missing(),
}
}
@@ -98,10 +100,29 @@ impl FileTransferParams {
ProtocolParams::AwsS3(params) => params.set_default_secret(secret),
ProtocolParams::Generic(params) => params.set_default_secret(secret),
ProtocolParams::Smb(params) => params.set_default_secret(secret),
ProtocolParams::WebDAV(params) => params.set_default_secret(secret),
}
}
}
/// Protocol params used by WebDAV
#[derive(Debug, Clone)]
pub struct WebDAVProtocolParams {
pub uri: String,
pub username: String,
pub password: String,
}
impl WebDAVProtocolParams {
fn set_default_secret(&mut self, secret: String) {
self.password = secret;
}
fn password_missing(&self) -> bool {
self.password.is_empty()
}
}
impl Default for FileTransferParams {
fn default() -> Self {
Self::new(FileTransferProtocol::Sftp, ProtocolParams::default())
@@ -149,6 +170,15 @@ impl ProtocolParams {
_ => None,
}
}
#[cfg(test)]
/// Retrieve WebDAV parameters if any
pub fn webdav_params(&self) -> Option<&WebDAVProtocolParams> {
match self {
ProtocolParams::WebDAV(params) => Some(params),
_ => None,
}
}
}
// -- Generic protocol params
@@ -512,6 +542,40 @@ mod test {
);
}
#[test]
#[cfg(linux)]
fn set_default_secret_smb() {
let mut params = FileTransferParams::new(
FileTransferProtocol::Scp,
ProtocolParams::Smb(SmbParams::new("localhost", "temp")),
);
params.set_default_secret(String::from("secret"));
assert_eq!(
params
.params
.smb_params()
.unwrap()
.password
.as_deref()
.unwrap(),
"secret"
);
}
#[test]
fn set_default_secret_webdav() {
let mut params = FileTransferParams::new(
FileTransferProtocol::Scp,
ProtocolParams::WebDAV(WebDAVProtocolParams {
uri: "http://localhost".to_string(),
username: "user".to_string(),
password: "pass".to_string(),
}),
);
params.set_default_secret(String::from("secret"));
assert_eq!(params.params.webdav_params().unwrap().password, "secret");
}
#[test]
fn set_default_secret_generic() {
let mut params =

View File

@@ -4,7 +4,9 @@
// Locals
use super::{AuthActivity, FileTransferParams};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
};
impl AuthActivity {
/// Delete bookmark
@@ -164,6 +166,7 @@ impl AuthActivity {
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
ProtocolParams::Smb(params) => self.load_bookmark_smb_into_gui(params),
ProtocolParams::WebDAV(params) => self.load_bookmark_webdav_into_gui(params),
}
}
@@ -196,4 +199,10 @@ impl AuthActivity {
#[cfg(unix)]
self.mount_smb_workgroup(params.workgroup.as_deref().unwrap_or(""));
}
fn load_bookmark_webdav_into_gui(&mut self, params: WebDAVProtocolParams) {
self.mount_webdav_uri(&params.uri);
self.mount_username(&params.username);
self.mount_password(&params.password);
}
}

View File

@@ -11,7 +11,7 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
use crate::ui::activities::auth::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
// -- protocol
@@ -31,9 +31,9 @@ impl ProtocolRadio {
.modifiers(BorderType::Rounded),
)
.choices(if cfg!(smb) {
&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV", "SMB"]
} else {
&["SFTP", "SCP", "FTP", "FTPS", "S3"]
&["SFTP", "SCP", "FTP", "FTPS", "S3", "WebDAV"]
})
.foreground(color)
.rewind(true)
@@ -50,6 +50,7 @@ impl ProtocolRadio {
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,
_ => FileTransferProtocol::Sftp,
}
}
@@ -63,6 +64,7 @@ impl ProtocolRadio {
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,
}
}
}
@@ -788,3 +790,40 @@ impl Component<Msg, NoUserEvent> for InputSmbWorkgroup {
)
}
}
#[derive(MockComponent)]
pub struct InputWebDAVUri {
component: Input,
}
impl InputWebDAVUri {
pub fn new(host: &str, color: Color) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(color)
.modifiers(BorderType::Rounded),
)
.foreground(color)
.placeholder(
"http://localhost:8080",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("HTTP url", Alignment::Left)
.input_type(InputType::Text)
.value(host),
}
}
}
impl Component<Msg, NoUserEvent> for InputWebDAVUri {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Ui(UiMsg::WebDAVUriBlurDown),
Msg::Ui(UiMsg::WebDAVUriBlurUp),
)
}
}

View File

@@ -19,7 +19,7 @@ pub use form::{
InputAddress, InputLocalDirectory, InputPassword, InputPort, InputRemoteDirectory,
InputS3AccessKey, InputS3Bucket, InputS3Endpoint, InputS3Profile, InputS3Region,
InputS3SecretAccessKey, InputS3SecurityToken, InputS3SessionToken, InputSmbShare,
InputUsername, ProtocolRadio, RadioS3NewPathStyle,
InputUsername, InputWebDAVUri, ProtocolRadio, RadioS3NewPathStyle,
};
pub use popup::{
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,

View File

@@ -15,6 +15,7 @@ impl AuthActivity {
FileTransferProtocol::Ftp(_) => 21,
FileTransferProtocol::AwsS3 => 22, // Doesn't matter, since not used
FileTransferProtocol::Smb => 445,
FileTransferProtocol::WebDAV => 80, // Doesn't matter, since not used
}
}
@@ -41,6 +42,7 @@ impl AuthActivity {
FileTransferProtocol::Ftp(_)
| FileTransferProtocol::Scp
| FileTransferProtocol::Sftp => self.collect_generic_host_params(self.protocol),
FileTransferProtocol::WebDAV => self.collect_webdav_host_params(),
}
}
@@ -98,6 +100,19 @@ impl AuthActivity {
})
}
pub(super) fn collect_webdav_host_params(&self) -> Result<FileTransferParams, &'static str> {
let params = self.get_webdav_params_input();
if params.uri.is_empty() {
return Err("Invalid URI");
}
Ok(FileTransferParams {
protocol: FileTransferProtocol::WebDAV,
params: ProtocolParams::WebDAV(params),
local_path: self.get_input_local_directory(),
remote_path: self.get_input_remote_directory(),
})
}
// -- update install
/// If enabled in configuration, check for updates from Github

View File

@@ -29,7 +29,8 @@ const RADIO_PROTOCOL_SCP: usize = 1;
const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_SMB: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 5;
const RADIO_PROTOCOL_SMB: usize = 6;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
@@ -71,6 +72,7 @@ pub enum Id {
Title,
Username,
WaitPopup,
WebDAVUri,
WindowSizeError,
}
@@ -155,6 +157,8 @@ pub enum UiMsg {
ShowSaveBookmarkPopup,
UsernameBlurDown,
UsernameBlurUp,
WebDAVUriBlurDown,
WebDAVUriBlurUp,
WindowResized,
}
@@ -164,6 +168,7 @@ enum InputMask {
Generic,
AwsS3,
Smb,
WebDAV,
}
// Store keys
@@ -240,6 +245,7 @@ impl AuthActivity {
| FileTransferProtocol::Scp
| FileTransferProtocol::Sftp => InputMask::Generic,
FileTransferProtocol::Smb => InputMask::Smb,
FileTransferProtocol::WebDAV => InputMask::WebDAV,
}
}
}

View File

@@ -70,6 +70,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -82,6 +83,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Password,
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -177,6 +179,7 @@ impl AuthActivity {
#[cfg(windows)]
InputMask::Smb => &Id::RemoteDirectory,
InputMask::AwsS3 => panic!("this shouldn't happen (password on s3)"),
InputMask::WebDAV => &Id::RemoteDirectory,
})
.is_ok());
}
@@ -189,7 +192,8 @@ impl AuthActivity {
.active(match self.input_mask() {
InputMask::Generic => &Id::Username,
InputMask::Smb => &Id::SmbShare,
InputMask::AwsS3 => panic!("this shouldn't happen (port on s3)"),
InputMask::AwsS3 | InputMask::WebDAV =>
panic!("this shouldn't happen (port on s3)"),
})
.is_ok());
}
@@ -203,6 +207,7 @@ impl AuthActivity {
InputMask::Generic => &Id::Address,
InputMask::Smb => &Id::Address,
InputMask::AwsS3 => &Id::S3Bucket,
InputMask::WebDAV => &Id::WebDAVUri,
})
.is_ok());
}
@@ -225,6 +230,7 @@ impl AuthActivity {
#[cfg(windows)]
InputMask::Smb => &Id::Password,
InputMask::AwsS3 => &Id::S3NewPathStyle,
InputMask::WebDAV => &Id::Password,
})
.is_ok());
}
@@ -332,9 +338,16 @@ impl AuthActivity {
InputMask::Generic => &Id::Port,
InputMask::Smb => &Id::SmbShare,
InputMask::AwsS3 => panic!("this shouldn't happen (username on s3)"),
InputMask::WebDAV => &Id::WebDAVUri,
})
.is_ok());
}
UiMsg::WebDAVUriBlurDown => {
assert!(self.app.active(&Id::Username).is_ok());
}
UiMsg::WebDAVUriBlurUp => {
assert!(self.app.active(&Id::Protocol).is_ok());
}
UiMsg::WindowResized => {
self.redraw = true;
}

View File

@@ -12,7 +12,9 @@ use tuirealm::tui::widgets::Clear;
use tuirealm::{State, StateValue, Sub, SubClause, SubEventClause};
use super::{components, AuthActivity, Context, FileTransferProtocol, Id, InputMask};
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, SmbParams, WebDAVProtocolParams,
};
use crate::filetransfer::FileTransferParams;
use crate::utils::ui::{Popup, Size};
@@ -61,6 +63,7 @@ impl AuthActivity {
self.mount_smb_share("");
#[cfg(unix)]
self.mount_smb_workgroup("");
self.mount_webdav_uri("");
// Version notice
if let Some(version) = self
.context()
@@ -195,6 +198,18 @@ impl AuthActivity {
)
.direction(Direction::Vertical)
.split(auth_chunks[4]),
InputMask::WebDAV => Layout::default()
.constraints(
[
Constraint::Length(3), // uri
Constraint::Length(3), // username
Constraint::Length(3), // password
Constraint::Length(3), // dir
]
.as_ref(),
)
.direction(Direction::Vertical)
.split(auth_chunks[4]),
};
// Create bookmark chunks
let bookmark_chunks = Layout::default()
@@ -230,6 +245,13 @@ impl AuthActivity {
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
InputMask::WebDAV => {
let view_ids = self.get_webdav_view();
self.app.view(&view_ids[0], f, input_mask[0]);
self.app.view(&view_ids[1], f, input_mask[1]);
self.app.view(&view_ids[2], f, input_mask[2]);
self.app.view(&view_ids[3], f, input_mask[3]);
}
}
// Bookmark chunks
self.app.view(&Id::BookmarksList, f, bookmark_chunks[0]);
@@ -794,6 +816,18 @@ impl AuthActivity {
.is_ok());
}
pub(super) fn mount_webdav_uri(&mut self, uri: &str) {
let addr_color = self.theme().auth_address;
assert!(self
.app
.remount(
Id::WebDAVUri,
Box::new(components::InputWebDAVUri::new(uri, addr_color)),
vec![]
)
.is_ok());
}
// -- query
/// Collect input values from view
@@ -860,6 +894,18 @@ impl AuthActivity {
.password(password)
}
pub(super) fn get_webdav_params_input(&self) -> WebDAVProtocolParams {
let uri: String = self.get_webdav_uri();
let username = self.get_input_username().unwrap_or_default();
let password = self.get_input_password().unwrap_or_default();
WebDAVProtocolParams {
uri,
username,
password,
}
}
pub(super) fn get_input_remote_directory(&self) -> Option<PathBuf> {
match self.app.state(&Id::RemoteDirectory) {
Ok(State::One(StateValue::String(x))) if !x.is_empty() => {
@@ -878,6 +924,13 @@ impl AuthActivity {
}
}
pub(super) fn get_webdav_uri(&self) -> String {
match self.app.state(&Id::WebDAVUri) {
Ok(State::One(StateValue::String(x))) => x,
_ => String::new(),
}
}
pub(super) fn get_input_addr(&self) -> String {
match self.app.state(&Id::Address) {
Ok(State::One(StateValue::String(x))) => x,
@@ -1011,6 +1064,7 @@ impl AuthActivity {
InputMask::AwsS3 => 12,
InputMask::Generic => 12,
InputMask::Smb => 12,
InputMask::WebDAV => 12,
}
}
@@ -1069,6 +1123,7 @@ impl AuthActivity {
};
format!("\\\\{username}{}\\{}", params.address, params.share)
}
ProtocolParams::WebDAV(params) => params.uri,
}
}
@@ -1180,6 +1235,23 @@ impl AuthActivity {
}
}
fn get_webdav_view(&self) -> [Id; 4] {
match self.app.focus() {
Some(&Id::LocalDirectory) => [
Id::Username,
Id::Password,
Id::RemoteDirectory,
Id::LocalDirectory,
],
_ => [
Id::WebDAVUri,
Id::Username,
Id::Password,
Id::RemoteDirectory,
],
}
}
fn init_global_listener(&mut self) {
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
assert!(self

View File

@@ -112,6 +112,7 @@ impl FileTransferActivity {
ProtocolParams::Generic(params) => params.address.clone(),
ProtocolParams::AwsS3(params) => params.bucket_name.clone(),
ProtocolParams::Smb(params) => params.address.clone(),
ProtocolParams::WebDAV(params) => params.uri.clone(),
}
}
@@ -141,6 +142,13 @@ impl FileTransferActivity {
);
format!("Connecting to \\\\{}\\{}", params.address, params.share)
}
ProtocolParams::WebDAV(params) => {
info!(
"Client is not connected to remote; connecting to {}",
params.uri
);
format!("Connecting to {}", params.uri)
}
}
}

View File

@@ -13,7 +13,7 @@ use crate::explorer::GroupDirs as GroupDirsEnum;
use crate::filetransfer::FileTransferProtocol;
use crate::ui::activities::setup::{
RADIO_PROTOCOL_FTP, RADIO_PROTOCOL_FTPS, RADIO_PROTOCOL_S3, RADIO_PROTOCOL_SCP,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB,
RADIO_PROTOCOL_SFTP, RADIO_PROTOCOL_SMB, RADIO_PROTOCOL_WEBDAV,
};
use crate::utils::parser::parse_bytesize;
@@ -67,7 +67,7 @@ impl DefaultProtocol {
.color(Color::Cyan)
.modifiers(BorderType::Rounded),
)
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB"])
.choices(&["SFTP", "SCP", "FTP", "FTPS", "S3", "SMB", "WebDAV"])
.foreground(Color::Cyan)
.rewind(true)
.title("Default protocol", Alignment::Left)
@@ -78,6 +78,7 @@ impl DefaultProtocol {
FileTransferProtocol::Ftp(true) => RADIO_PROTOCOL_FTPS,
FileTransferProtocol::AwsS3 => RADIO_PROTOCOL_S3,
FileTransferProtocol::Smb => RADIO_PROTOCOL_SMB,
FileTransferProtocol::WebDAV => RADIO_PROTOCOL_WEBDAV,
}),
}
}

View File

@@ -31,6 +31,7 @@ const RADIO_PROTOCOL_FTP: usize = 2;
const RADIO_PROTOCOL_FTPS: usize = 3;
const RADIO_PROTOCOL_S3: usize = 4;
const RADIO_PROTOCOL_SMB: usize = 5;
const RADIO_PROTOCOL_WEBDAV: usize = 6;
// -- components
#[derive(Debug, Eq, PartialEq, Clone, Hash)]

View File

@@ -10,7 +10,9 @@ use std::path::PathBuf;
use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::{State, StateValue};
use super::{components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout};
use super::{
components, Context, Id, IdCommon, IdConfig, SetupActivity, ViewLayout, RADIO_PROTOCOL_WEBDAV,
};
use crate::explorer::GroupDirs;
use crate::filetransfer::FileTransferProtocol;
use crate::ui::activities::setup::{
@@ -277,6 +279,7 @@ impl SetupActivity {
RADIO_PROTOCOL_FTPS => FileTransferProtocol::Ftp(true),
RADIO_PROTOCOL_S3 => FileTransferProtocol::AwsS3,
RADIO_PROTOCOL_SMB => FileTransferProtocol::Smb,
RADIO_PROTOCOL_WEBDAV => FileTransferProtocol::WebDAV,
_ => FileTransferProtocol::Sftp,
};
self.config_mut().set_default_protocol(protocol);

View File

@@ -14,7 +14,9 @@ use tuirealm::utils::parser as tuirealm_parser;
#[cfg(smb)]
use crate::filetransfer::params::SmbParams;
use crate::filetransfer::params::{AwsS3Params, GenericProtocolParams, ProtocolParams};
use crate::filetransfer::params::{
AwsS3Params, GenericProtocolParams, ProtocolParams, WebDAVProtocolParams,
};
use crate::filetransfer::{FileTransferParams, FileTransferProtocol};
#[cfg(not(test))] // NOTE: don't use configuration during tests
use crate::system::config_client::ConfigClient;
@@ -43,6 +45,16 @@ 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])))?(?::([^:]+))?"
);
/**
* Regex matches:
* - group 1: Username
* - group 2: Password
* - group 2: Uri
* - group 4: Some(path) | None
*/
static REMOTE_WEBDAV_OPT_REGEX: Lazy<Regex> =
lazy_regex!(r"(?:([^:]+):)(?:([^@]+)@)(?:([^/]+))(?:/(.+))?");
/**
* Regex matches:
* - group 1: Bucket
@@ -145,14 +157,24 @@ pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
#[cfg(test)] // NOTE: during test set protocol just to Sftp
let default_protocol: FileTransferProtocol = FileTransferProtocol::Sftp;
// Get protocol
let (protocol, s): (FileTransferProtocol, String) =
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(s.as_str()),
FileTransferProtocol::AwsS3 => parse_s3_remote_opt(remote.as_str()),
#[cfg(smb)]
FileTransferProtocol::Smb => parse_smb_remote_opts(s.as_str()),
protocol => parse_generic_remote_opt(s.as_str(), protocol),
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),
}
}
@@ -232,6 +254,29 @@ fn parse_generic_remote_opt(
}
}
fn parse_webdav_remote_opt(s: &str, prefix: &str) -> Result<FileTransferParams, String> {
match REMOTE_WEBDAV_OPT_REGEX.captures(s) {
Some(groups) => {
let username = groups.get(1).map(|x| x.as_str().to_string()).unwrap();
let password = groups.get(2).map(|x| x.as_str().to_string()).unwrap();
let uri = groups.get(3).map(|x| x.as_str().to_string()).unwrap();
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) {
@@ -553,6 +598,25 @@ mod tests {
assert!(parse_remote_opt(&String::from("scp://172.26.104.1:650000")).is_err());
}
#[test]
fn test_should_parse_webdav_opt() {
let result =
parse_remote_opt("https://omar:password@myserver:4445/myshare/dir/subdir").unwrap();
let params = result.params.webdav_params().unwrap();
assert_eq!(params.uri.as_str(), "https://myserver:4445");
assert_eq!(params.username.as_str(), "omar");
assert_eq!(params.password.as_str(), "password");
let result =
parse_remote_opt("http://omar:password@myserver:4445/myshare/dir/subdir").unwrap();
let params = result.params.webdav_params().unwrap();
assert_eq!(params.uri.as_str(), "http://myserver:4445");
assert_eq!(params.username.as_str(), "omar");
assert_eq!(params.password.as_str(), "password");
}
#[test]
fn parse_aws_s3_opt() {
// Simple