use std::fmt::Write as FmtWrite;
use std::fs;
use std::io::BufRead;
use std::path::Path;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::crypto;
use crate::error::{ParseError, SfdlError};
const XMLNS_XSD: &str = "http://www.w3.org/2001/XMLSchema";
const XMLNS_XSI: &str = "http://www.w3.org/2001/XMLSchema-instance";
#[allow(clippy::must_use_candidate)]
fn default_xmlns_xsd() -> String {
XMLNS_XSD.to_string()
}
#[allow(clippy::must_use_candidate)]
fn default_xmlns_xsi() -> String {
XMLNS_XSI.to_string()
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "PascalCase", rename = "SFDLFile")]
pub struct SfdlFile {
#[serde(rename = "@xmlns:xsd", default = "default_xmlns_xsd")]
pub xmlns_xsd: String,
#[serde(rename = "@xmlns:xsi", default = "default_xmlns_xsi")]
pub xmlns_xsi: String,
pub description: String,
pub uploader: String,
#[serde(rename = "SFDLFileVersion")]
pub sfdlfile_version: u16,
pub encrypted: bool,
pub connection_info: ConnectionInfo,
pub packages: Packages,
pub max_download_threads: u16,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct ConnectionInfo {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
pub auth_required: bool,
pub data_connection_type: DataConnectionType,
pub data_type: DataType,
pub character_encoding: CharacterEncoding,
pub encryption_mode: EncryptionMode,
pub list_method: String,
pub default_path: String,
pub force_single_connection: bool,
pub data_stale_detection: bool,
pub special_server_mode: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct Packages {
#[serde(rename = "SFDLPackage")]
pub package: Vec<SfdlPackage>,
}
impl std::ops::Deref for Packages {
type Target = Vec<SfdlPackage>;
fn deref(&self) -> &Self::Target {
&self.package
}
}
impl std::ops::DerefMut for Packages {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.package
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct SfdlPackage {
#[serde(rename = "Packagename")]
pub package_name: String,
pub bulk_folder_mode: bool,
#[serde(default)]
pub bulk_folder_list: BulkFolderList,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_list: Option<FileList>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct BulkFolderList {
#[serde(default)]
pub bulk_folder: Vec<BulkFolder>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub struct BulkFolder {
pub bulk_folder_path: String,
pub package_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct FileList {
#[serde(default)]
pub file_info: Vec<FileInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "PascalCase")]
pub struct FileInfo {
pub file_name: String,
pub directory_root: String,
pub directory_path: String,
pub file_full_path: String,
pub file_size: u64,
pub file_hash_type: String,
pub file_hash: String,
pub package_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum DataConnectionType {
#[serde(rename = "AutoPassive")]
AutoPassive,
#[serde(rename = "AutoActive")]
AutoActive,
#[serde(rename = "EPRT")]
EPRT,
#[serde(rename = "EPSV")]
EPSV,
#[serde(rename = "PASV")]
PASV,
#[serde(rename = "PASVEX")]
PASVEX,
#[serde(rename = "PORT")]
PORT,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum DataType {
#[serde(rename = "Binary")]
Binary,
#[serde(rename = "ASCII")]
ASCII,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum CharacterEncoding {
#[serde(rename = "Standard")]
Standard,
#[serde(rename = "UTF8")]
UTF8,
#[serde(rename = "UTF7")]
UTF7,
#[serde(rename = "ASCII")]
ASCII,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum EncryptionMode {
#[serde(rename = "None")]
None,
#[serde(rename = "SSL")]
SSL,
#[serde(rename = "TLS")]
TLS,
}
impl SfdlFile {
pub fn from_reader<R>(reader: R) -> Result<Self, ParseError>
where
R: BufRead,
{
quick_xml::de::from_reader(reader).map_err(ParseError::InvalidSfdlDeserialize)
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, SfdlError> {
let content = fs::read_to_string(path)?;
let sfdl: Self = content.parse()?;
Ok(sfdl)
}
pub fn encrypt(&mut self, password: &str) -> Result<(), SfdlError> {
if self.encrypted {
return Err(SfdlError::AlreadyEncrypted);
}
*self = crypto::encrypt_sfdl(self, password);
self.encrypted = true;
Ok(())
}
pub fn decrypt(&mut self, password: &str) -> Result<(), SfdlError> {
if !self.encrypted {
return Err(SfdlError::NotEncrypted);
}
*self = crypto::decrypt_sfdl(self, password)?;
self.encrypted = false;
Ok(())
}
pub fn to_xml_string(&self) -> Result<String, ParseError> {
quick_xml::se::to_string(self).map_err(ParseError::InvalidSfdlSerialize)
}
pub fn to_xml_writer<W: FmtWrite>(&self, writer: W) -> Result<(), ParseError> {
quick_xml::se::to_writer(writer, self).map_err(ParseError::InvalidSfdlSerialize)?;
Ok(())
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), SfdlError> {
let content = self.to_xml_string()?;
fs::write(path, content)?;
Ok(())
}
}
impl FromStr for SfdlFile {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
quick_xml::de::from_str(s).map_err(ParseError::InvalidSfdlDeserialize)
}
}
impl Default for SfdlFile {
fn default() -> Self {
Self {
xmlns_xsd: XMLNS_XSD.to_string(),
xmlns_xsi: XMLNS_XSI.to_string(),
description: String::new(),
uploader: String::new(),
sfdlfile_version: 6,
encrypted: false,
connection_info: ConnectionInfo::default(),
packages: Packages {
package: vec![SfdlPackage::default()],
},
max_download_threads: 3,
}
}
}
impl Default for ConnectionInfo {
fn default() -> Self {
Self {
host: String::new(),
port: 21,
username: String::new(),
password: String::new(),
auth_required: false,
data_connection_type: DataConnectionType::AutoPassive,
data_type: DataType::Binary,
character_encoding: CharacterEncoding::Standard,
encryption_mode: EncryptionMode::None,
list_method: "ForceList".to_string(),
default_path: "/".to_string(),
force_single_connection: false,
data_stale_detection: true,
special_server_mode: false,
}
}
}
impl Default for SfdlPackage {
fn default() -> Self {
Self {
package_name: String::new(),
bulk_folder_mode: true,
bulk_folder_list: BulkFolderList::default(),
file_list: None,
}
}
}