mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
feat: WebDAV support (#235)
This commit is contained in:
committed by
GitHub
parent
5dfee2cbd9
commit
c7469b8594
@@ -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(¶ms.uri);
|
||||
self.mount_username(¶ms.username);
|
||||
self.mount_password(¶ms.password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user