diff --git a/src/addins/ftp/src/component/ftp_client.rs b/src/addins/ftp/src/component/ftp_client.rs index ac3466c90d..a180b5ab0b 100644 --- a/src/addins/ftp/src/component/ftp_client.rs +++ b/src/addins/ftp/src/component/ftp_client.rs @@ -35,6 +35,15 @@ impl FtpClient { } } + + pub fn get_welcome_msg(&self) -> String { + let msg = match self { + FtpClient::Secure(stream) => stream.get_welcome_msg().unwrap_or(""), + FtpClient::Insecure(stream) => stream.get_welcome_msg().unwrap_or(""), + }; + + json!({"result": true, "data": msg}).to_string() + } } fn format_json_error(error: E) -> String { diff --git a/src/addins/ftp/src/component/mod.rs b/src/addins/ftp/src/component/mod.rs index 9e2dbee32d..3374da9062 100644 --- a/src/addins/ftp/src/component/mod.rs +++ b/src/addins/ftp/src/component/mod.rs @@ -9,7 +9,8 @@ use suppaftp::types::Mode; use serde::Deserialize; use crate::component::ftp_client::FtpClient; use std::net::TcpStream; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Duration; use socks::{Socks4Stream, Socks5Stream}; // МЕТОДЫ КОМПОНЕНТЫ ------------------------------------------------------------------------------- @@ -19,7 +20,8 @@ pub const METHODS: &[&[u16]] = &[ name!("Connect"), name!("UpdateSettings"), name!("UpdateProxy"), - name!("SetTLS") + name!("SetTLS"), + name!("GetWelcomeMsg") ]; // Число параметров функций компоненты @@ -56,6 +58,12 @@ pub fn cal_func(obj: &mut AddIn, num: usize, params: &mut [Variant]) -> Box { + Box::new(match obj.get_client(){ + Ok(c) => c.get_welcome_msg(), + Err(e) => process_error(e.as_str()) + }) } _ => Box::new(false), // Неверный номер команды } @@ -71,8 +79,8 @@ pub const PROPS: &[&[u16]] = &[]; #[derive(Deserialize, Debug)] struct FtpProxySettings { - server: String, - port: u16, + server: Option, + port: Option, login: Option, password: Option, proxy_type: Option, // "http", "socks4", "socks5" @@ -85,7 +93,8 @@ struct FtpSettings { login: Option, password: Option, passive: Option, - timeout: Option, + read_timeout: Option, + write_timeout: Option, } pub struct AddIn { @@ -94,7 +103,8 @@ pub struct AddIn { login: Option, password: Option, passive: bool, - timeout: u64, + read_timeout: u64, + write_timeout: u64, // TLS use_tls: bool, accept_invalid_certs: bool, @@ -118,11 +128,11 @@ impl AddIn { login: None, password: None, passive: true, - timeout: 120, + read_timeout: 120, + write_timeout: 120, use_tls: false, accept_invalid_certs: false, ca_cert_path: String::new(), - // Инициализация полей прокси proxy_server: None, proxy_port: None, proxy_login: None, @@ -135,7 +145,7 @@ impl AddIn { let json_struct: FtpSettings = match serde_json::from_str(json_data){ Ok(s) => s, - Err(e) => return Self::process_error(&e.to_string()), + Err(e) => return process_error(&e.to_string()), }; if let Some(domain) = json_struct.domain { @@ -150,12 +160,16 @@ impl AddIn { self.passive = passive; } - if let Some(timeout) = json_struct.timeout { - self.timeout = timeout; + if let Some(read_timeout) = json_struct.read_timeout { + self.read_timeout = read_timeout; } - self.login = json_struct.login.or(self.login.clone()); - self.password = json_struct.password.or(self.password.clone()); + if let Some(write_timeout) = json_struct.write_timeout { + self.write_timeout = write_timeout; + } + + self.login = json_struct.login; + self.password = json_struct.password; json!({"result": true}).to_string() @@ -165,11 +179,12 @@ impl AddIn { let json_struct: FtpProxySettings = match serde_json::from_str(json_data){ Ok(s) => s, - Err(e) => return Self::process_error(&e.to_string()), + Err(e) => return process_error(&e.to_string()), }; - self.proxy_server = Some(json_struct.server); - self.proxy_port = Some(json_struct.port); + + self.proxy_server = json_struct.server; + self.proxy_port = json_struct.port; self.proxy_login = json_struct.login; self.proxy_password = json_struct.password; self.proxy_type = json_struct.proxy_type; @@ -181,7 +196,7 @@ impl AddIn { pub fn initialize(&mut self) -> String { if self.domain.is_empty() { - return Self::process_error("Address must be initialized"); + return process_error("Address must be initialized"); } let tcp_stream = match self.create_tcp_connection() { @@ -189,6 +204,19 @@ impl AddIn { Err(e) => return e, }; + let w_timeout = Some(Duration::from_secs(self.write_timeout)); + let r_timeout = Some(Duration::from_secs(self.read_timeout)); + + match tcp_stream.set_write_timeout(w_timeout){ + Ok(_) => (), + Err(e) => return process_error(&e.to_string()), + } + + match tcp_stream.set_read_timeout(r_timeout) { + Ok(_) => (), + Err(e) => return process_error(&e.to_string()), + } + let client = match self.configure_ftp_client(tcp_stream) { Ok(client) => client, Err(e) => return e, @@ -199,7 +227,7 @@ impl AddIn { self.client = match client.login(login, password) { Ok(auth) => Some(Arc::new(Mutex::new(auth))), - Err(e) => return Self::process_error(&e.to_string()), + Err(e) => return process_error(&e.to_string()), }; json!({"result": true}).to_string() @@ -217,11 +245,11 @@ impl AddIn { let tls_connector = self.get_tls_connector()?; let ftp_stream = NativeTlsFtpStream::connect_with_stream(tcp_stream) - .map_err(|e| Self::process_error(&e.to_string()))?; + .map_err(|e| process_error(&e.to_string()))?; let mut secure_stream = ftp_stream .into_secure(NativeTlsConnector::from(tls_connector), &self.domain) - .map_err(|e| Self::process_error(&e.to_string()))?; + .map_err(|e| process_error(&e.to_string()))?; secure_stream.set_mode(mode); secure_stream.set_passive_nat_workaround(true); @@ -230,7 +258,7 @@ impl AddIn { } else { let mut ftp_stream = FtpStream::connect_with_stream(tcp_stream) - .map_err(|e| Self::process_error(&e.to_string()))?; + .map_err(|e| process_error(&e.to_string()))?; ftp_stream.set_mode(mode); ftp_stream.set_passive_nat_workaround(true); @@ -250,7 +278,7 @@ impl AddIn { match proxy_type.to_lowercase().as_str() { "socks5" => self.connect_via_socks5(&proxy_addr, target_addr), "socks4" => self.connect_via_socks4(&proxy_addr, target_addr), - _ => Err(Self::process_error("Unsupported proxy type")), + _ => Err(process_error("Unsupported proxy type")), } } else { self.connect_direct() @@ -266,7 +294,7 @@ impl AddIn { }; stream.map(|s| s.into_inner()) - .map_err(|e| Self::process_error(&format!("SOCKS5 error: {}", e))) + .map_err(|e| process_error(&format!("SOCKS5 error: {}", e))) } fn connect_via_socks4(&self, proxy_addr: &str, target_addr: (&str, u16)) -> Result { @@ -278,12 +306,12 @@ impl AddIn { }; stream.map(|s| s.into_inner()) - .map_err(|e| Self::process_error(&format!("SOCKS4 error: {}", e))) + .map_err(|e| process_error(&format!("SOCKS4 error: {}", e))) } fn connect_direct(&self) -> Result { let addr = format!("{}:{}", &self.domain, &self.port); - TcpStream::connect(&addr).map_err(|e| Self::process_error(&format!("Direct connection error: {}", e))) + TcpStream::connect(&addr).map_err(|e| process_error(&format!("Direct connection error: {}", e))) } pub fn close_connection(&mut self) -> String { @@ -295,7 +323,7 @@ impl AddIn { FtpClient::Insecure(stream) => _ = stream.quit(), } } - Err(e) => return Self::process_error(&format!("Failed to lock client: {}", e)), + Err(e) => return process_error(&format!("Failed to lock client: {}", e)), } } json!({"result": true}).to_string() @@ -304,7 +332,7 @@ impl AddIn { pub fn set_tls(&mut self, use_tls: bool, accept_invalid_certs: bool, ca_cert_path: &str) -> String { if self.client.is_some(){ - return Self::process_error("TLS settings can only be set before the connection is established"); + return process_error("TLS settings can only be set before the connection is established"); }; self.accept_invalid_certs = accept_invalid_certs; @@ -323,25 +351,25 @@ impl AddIn { if !self.ca_cert_path.is_empty() { let cert_data = std::fs::read(&self.ca_cert_path) - .map_err(|e| Self::process_error(&e.to_string()))?; + .map_err(|e| process_error(&e.to_string()))?; let cert = native_tls::Certificate::from_pem(&cert_data) - .map_err(|e| Self::process_error(&e.to_string()))?; + .map_err(|e| process_error(&e.to_string()))?; tls_builder.add_root_certificate(cert); } match tls_builder.build(){ Ok(connector) => Ok(connector), - Err(e) => Err(Self::process_error(&e.to_string())), + Err(e) => Err(process_error(&e.to_string())), } } - fn process_error(e: &str) -> String{ - json!({ - "result": false, - "error": e - }).to_string() + fn get_client(&self) -> Result, String> { + self.client + .as_ref() + .ok_or_else(|| process_error("FTP client is not initialized")) + .and_then(|arc| arc.lock().map_err(|_| process_error("Failed to lock FTP client mutex"))) } pub fn get_field_ptr(&self, index: usize) -> *const dyn getset::ValueType { @@ -352,6 +380,13 @@ impl AddIn { pub fn get_field_ptr_mut(&mut self, index: usize) -> *mut dyn getset::ValueType { self.get_field_ptr(index) as *mut _ } } +pub fn process_error(e: &str) -> String{ + json!({ + "result": false, + "error": e + }).to_string() +} + // ------------------------------------------------------------------------------------------------- diff --git a/src/ru/OPI/src/CommonModules/OPI_FTP/Module.bsl b/src/ru/OPI/src/CommonModules/OPI_FTP/Module.bsl index ef6f012b70..29d5a13f4d 100644 --- a/src/ru/OPI/src/CommonModules/OPI_FTP/Module.bsl +++ b/src/ru/OPI/src/CommonModules/OPI_FTP/Module.bsl @@ -45,6 +45,123 @@ #Область ОсновныеМетоды +// Открыть соединение !NOCLI +// Открывает FTP соединение с указанными настройками +// +// Параметры: +// НастройкиFTP - Структура Из КлючИЗначение - Настройки FTP. См. ПолучитьНастройкиFTP - set +// Прокси - Структура Из КлючИЗначение - Настройки прокси, если необходимо. См ПолучитьНастройкиПрокси - proxy +// Tls - Структура Из КлючИЗначение - Настройки TLS, если необходимо. См. ПолучитьНастройкиTls - tls +// +// Возвращаемое значение: +// Произвольный - Объект коннектора или соответствие с информацией об ошибке +Функция ОткрытьСоединение(Знач НастройкиFTP, Знач Прокси = Неопределено, Знач Tls = Неопределено) Экспорт + + Если ЭтоКоннектор(НастройкиFTP) Тогда + Возврат НастройкиFTP; + КонецЕсли; + + Коннектор = OPI_Компоненты.ПолучитьКомпоненту("FTP"); + + УстановкаНастроек = УстановитьНастройкиFtp(Коннектор, НастройкиFTP); + + Если Не OPI_Инструменты.ПолучитьИли(УстановкаНастроек, "result", Ложь) Тогда + Возврат УстановкаНастроек; + КонецЕсли; + + Tls = OPI_Компоненты.УстановитьTls(Коннектор, Tls); + + Если Не OPI_Инструменты.ПолучитьИли(Tls, "result", Ложь) Тогда + Возврат Tls; + КонецЕсли; + + УстановитьПрокси = УстановитьНастройкиПрокси(Коннектор, Прокси); + + Если Не OPI_Инструменты.ПолучитьИли(УстановитьПрокси, "result", Ложь) Тогда + Возврат УстановитьПрокси; + КонецЕсли; + + Результат = Коннектор.Connect(); + Результат = OPI_Инструменты.JSONВСтруктуру(Результат); + + Возврат Результат; + +КонецФункции + +// Это коннектор !NOCLI +// Проверяет, что значение является объектом внешней компоненты для работы с FTP +// +// Параметры: +// Значение - Произвольный - Значение для проверки - value +// +// Возвращаемое значение: +// Булево - Это коннектор +Функция ЭтоКоннектор(Знач Значение) Экспорт + + Возврат Строка(ТипЗнч(Значение)) = "AddIn.OPI_FTP.Main"; + +КонецФункции + #КонецОбласти #КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Функция УстановитьНастройкиFtp(Знач Коннектор, Знач НастройкиFTP) + + ТекстОшибки = "Настройки FTP не являются валидной структурой ключ-значение"; + Настройки = НастройкиВJson(НастройкиFTP, ТекстОшибки); + + Если ТипЗнч(Настройки) = Тип("Соответствие") Тогда + Возврат Настройки; + КонецЕсли; + + Результат = Коннектор.UpdateSettings(Настройки); + Результат = OPI_Инструменты.JsonВСтруктуру(Результат); + + Возврат Результат; + +КонецФункции + +Функция УстановитьНастройкиПрокси(Знач Коннектор, Знач НастройкиПрокси) + + Если НастройкиПрокси = Неопределено Тогда + Результат = Новый Соответствие; + Результат.Вставить("result", Истина); + Возврат Результат; + КонецЕсли; + + ТекстОшибки = "Настройки прокси не являются валидной структурой ключ-значение"; + Настройки = НастройкиВJson(НастройкиПрокси, ТекстОшибки); + + Если ТипЗнч(Настройки) = Тип("Соответствие") Тогда + Возврат Настройки; + КонецЕсли; + + Результат = Коннектор.UpdateProxy(Настройки); + Результат = OPI_Инструменты.JsonВСтруктуру(Результат); + + Возврат Результат; + +КонецФункции + +Функция НастройкиВJson(Знач Коллекция, Знач ТекстОшибки) + + OPI_ПреобразованиеТипов.ПолучитьКоллекциюКлючИЗначение(Коллекция); + + Попытка + Результат = OPI_Инструменты.JSONСтрокой(Коллекция); + Исключение + + Результат = Новый Соответствие; + Результат.Вставить("result", Ложь); + Результат.Вставить("error" , "Настройки FTP должны содержать только сериализуемые значения"); + + КонецПопытки; + + Возврат Результат; + +КонецФункции + +#КонецОбласти