diff --git a/src/arcstr.rs b/src/arcstr.rs new file mode 100644 index 0000000..d9b7dfe --- /dev/null +++ b/src/arcstr.rs @@ -0,0 +1,69 @@ +use std::ffi::OsStr; +use std::fmt::{Display, Formatter}; +use std::ops::Deref; +use std::path::Path; +use std::sync::Arc; + +#[derive(Debug, Clone, PartialEq, Ord, PartialOrd, Eq)] +pub(crate) struct ArcStr { + inner: Arc, +} + +impl Deref for ArcStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl From<&str> for ArcStr { + fn from(value: &str) -> Self { + Self { + inner: Arc::from(value), + } + } +} + +#[allow(suspicious_double_ref_op)] +impl From<&&str> for ArcStr { + fn from(value: &&str) -> Self { + Self { + inner: Arc::from(value.deref()), + } + } +} + +impl AsRef for ArcStr { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref().as_ref() + } +} + +impl AsRef for ArcStr { + fn as_ref(&self) -> &Path { + self.inner.as_ref().as_ref() + } +} + +impl AsRef for ArcStr { + fn as_ref(&self) -> &ArcStr { + self + } +} + +impl Display for ArcStr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.deref()) + } +} + +impl ArcStr { + pub(crate) fn as_path(&self) -> &Path { + self.as_ref() + } + + pub(crate) fn as_str(&self) -> &str { + self.inner.deref() + } +} diff --git a/src/common.rs b/src/common.rs index dda54a7..79c5480 100644 --- a/src/common.rs +++ b/src/common.rs @@ -3,7 +3,6 @@ use async_stream::stream; use clap::Parser; use encoding::{label::encoding_from_whatwg_label, EncoderTrap}; use std::{ - collections::BTreeMap, fmt::Display, path::{Path, PathBuf}, }; @@ -14,8 +13,6 @@ use tokio::{ use futures_core::stream::Stream; -pub(crate) type VarsMap = BTreeMap; - pub(crate) const UTF8_STR: &str = "utf8"; pub(crate) const DEFAULT_ENCODING: &str = UTF8_STR; diff --git a/src/main.rs b/src/main.rs index 74e479f..f56a70c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Parser; -use common::{is_file_exist, OpenSSLProviderArg, VarsMap}; +use common::{is_file_exist, OpenSSLProviderArg}; use crypto_provider::ICryptoProvider; +mod arcstr; mod certs; mod common; mod crypto_provider; @@ -14,7 +15,7 @@ use crate::certs::Certs; use crate::common::{AppConfig, Args}; use crate::openssl::{external::OpenSSLExternalProvider, internal::OpenSSLInternalProvider}; use crate::ovpn::OvpnConfig; -use crate::vars::VarsFile; +use crate::vars::{VarsFile, VarsMap}; async fn build_client_with(config: &AppConfig, provider: T) -> Result { let config_file = config.conf_filepath.clone(); @@ -54,20 +55,16 @@ async fn build_client_config(config: &AppConfig, vars: VarsMap) -> Result Result<()> { let args = Args::parse(); let config = AppConfig::from(&args); - let mut vars = VarsFile::from_config(&config).await?; - vars.parse().await?; + let vars = VarsFile::from_config(&config) + .await + .context("vars from config")? + .parse() + .await + .context("parse vars")?; - println!( - "found vars: {}", - vars.filepath - .to_str() - .ok_or(anyhow!("vars filepath to_str"))? - ); - println!("loaded: {:#?}", &vars.vars); + println!("loaded: {:#?}", &vars); - let vars = vars.vars.ok_or(anyhow!("no vars loaded"))?; let config_file = build_client_config(&config, vars).await?; - println!("created: {}", &config_file); Ok(()) diff --git a/src/openssl/external.rs b/src/openssl/external.rs index e6548f4..171b99e 100644 --- a/src/openssl/external.rs +++ b/src/openssl/external.rs @@ -1,10 +1,12 @@ use anyhow::{anyhow, Result}; +use std::ops::Deref; use std::path::PathBuf; use tokio::process::Command; -use crate::common::{is_file_exist, AppConfig, VarsMap}; +use crate::common::{is_file_exist, AppConfig}; use crate::crypto_provider::ICryptoProvider; +use crate::vars::{IStrMap, VarsMap}; pub(crate) struct OpenSSLExternalProvider { vars: VarsMap, @@ -33,12 +35,11 @@ impl OpenSSLExternalProvider { pub(crate) fn from_cfg(cfg: &AppConfig, vars: VarsMap) -> Self { let base_dir = PathBuf::from(&cfg.base_directory); - let name = cfg.name.clone(); let mut vars = vars; - vars.insert("KEY_CN".into(), name.clone()); - vars.insert("KEY_NAME".into(), name.clone()); - vars.insert("KEY_EMAIL".into(), cfg.email.clone()); + vars.insert("KEY_CN", &cfg.name); + vars.insert("KEY_NAME", &cfg.name); + vars.insert("KEY_EMAIL", &cfg.email); let openssl_cnf = base_dir.join( std::env::var(&cfg.openssl_cnf_env) @@ -87,7 +88,7 @@ impl ICryptoProvider for OpenSSLExternalProvider { "-batch", ]) .current_dir(&self.base_dir) - .envs(&self.vars) + .envs(self.vars.deref()) .status() .await?; @@ -123,7 +124,7 @@ impl ICryptoProvider for OpenSSLExternalProvider { "-batch", ]) .current_dir(&self.base_dir) - .envs(&self.vars) + .envs(self.vars.deref()) .status() .await?; diff --git a/src/openssl/internal.rs b/src/openssl/internal.rs index 0c317bb..3d6f301 100644 --- a/src/openssl/internal.rs +++ b/src/openssl/internal.rs @@ -16,13 +16,14 @@ use std::path::{Path, PathBuf}; use tokio::fs; use crate::{ - common::{is_file_exist, read_file, AppConfig, VarsMap}, + common::{is_file_exist, read_file, AppConfig}, crypto_provider::ICryptoProvider, }; use lazy_static::lazy_static; use std::collections::HashMap; +use crate::vars::{IStrMap, VarsMap}; use chrono::{Datelike, Days, Timelike, Utc}; lazy_static! { @@ -117,15 +118,13 @@ impl OpenSSLInternalProvider { } pub(crate) fn try_from_cfg(cfg: &AppConfig, vars: VarsMap) -> Result { - let name = cfg.name.clone(); let mut vars = vars; - vars.insert("KEY_CN".into(), name.clone()); - vars.insert("KEY_NAME".into(), name.clone()); - vars.insert("KEY_EMAIL".into(), cfg.email.clone()); + vars.insert("KEY_CN", &cfg.name); + vars.insert("KEY_NAME", &cfg.name); + vars.insert("KEY_EMAIL", &cfg.email); - let default_key_size = "2048".to_string(); - let key_size_s = vars.get("KEY_SIZE").unwrap_or(&default_key_size); + let key_size_s = vars.get(&"KEY_SIZE").unwrap_or("2048"); let key_size: u32 = key_size_s.parse().context("parse key size error")?; let encoding = cfg.encoding.clone(); @@ -190,9 +189,9 @@ impl OpenSSLInternalProvider { for (&key, &var) in KEYMAP.iter() { let value = self .vars - .get(var) + .get(&var) .ok_or(anyhow!("variable not set: {}", var))?; - name_builder.append_entry_by_text(key, value).unwrap(); + name_builder.append_entry_by_text(key, value)?; } Ok(name_builder.build()) } @@ -208,10 +207,10 @@ impl OpenSSLInternalProvider { let key_extended_ext = ExtendedKeyUsage::new().client_auth().build()?; let mut san_extension = SubjectAlternativeName::new(); - if let Some(name) = vars.get("KEY_NAME") { + if let Some(name) = vars.get(&"KEY_NAME") { san_extension.dns(name); } - if let Some(email) = vars.get("KEY_EMAIL") { + if let Some(email) = vars.get(&"KEY_EMAIL") { san_extension.email(email); } let san_ext = san_extension.build(context).context("build san")?; diff --git a/src/vars.rs b/src/vars.rs index 0cebbee..8365068 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -3,38 +3,95 @@ use regex::Regex; use std::{path::PathBuf, pin::Pin}; use tokio::pin; +use crate::arcstr::ArcStr; +use crate::common::{read_file_by_lines, AppConfig}; use futures_util::stream::StreamExt; +use std::collections::BTreeMap; +use std::fmt::{Debug, Formatter}; +use std::ops::Deref; +use std::path::Path; -use crate::common::{read_file_by_lines, AppConfig, VarsMap}; +pub(crate) type ArcVarsMap = BTreeMap; + +#[derive(Clone, PartialEq, Ord, PartialOrd, Eq)] +pub(crate) struct VarsMap(ArcVarsMap); + +pub(crate) trait IStrMap { + fn get + Clone>(&self, key: &S) -> Option<&str>; + fn insert + Clone>(&mut self, key: S, value: S) -> Option; +} + +impl IStrMap for VarsMap { + fn get + Clone>(&self, key: &S) -> Option<&str> { + self.0.get(&ArcStr::from(key.as_ref())).map(|x| x.as_str()) + } + + fn insert + Clone>(&mut self, key: S, value: S) -> Option { + let key = ArcStr::from(key.as_ref()); + let value2 = ArcStr::from(value.clone().as_ref()); + self.0.insert(key, value2).and(Some(value)) + } +} + +impl VarsMap { + pub(crate) fn filepath(&self) -> Option<&str> { + self.get(&"__file__") + } + + #[allow(dead_code)] + pub(crate) fn encoding(&self) -> Option<&str> { + self.get(&"__encoding__") + } +} + +impl Deref for VarsMap { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Debug for VarsMap { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let debug_map: BTreeMap<_, _> = + self.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); + debug_map.fmt(f) + } +} pub(crate) struct VarsFile { - pub(crate) filepath: PathBuf, - pub(crate) vars: Option, - pub(crate) encoding: String, + pub(crate) filepath: ArcStr, + pub(crate) encoding: ArcStr, } impl VarsFile { - async fn from_file(filepath: &PathBuf, encoding: String) -> Result { - let metadata = tokio::fs::metadata(&filepath).await.context(format!( - "file not found {}", - filepath.to_str().expect("str") - ))?; + async fn from_file

(filepath: P, encoding: ArcStr) -> Result + where + P: AsRef, + { + let filepath_str = filepath + .as_ref() + .to_str() + .ok_or(anyhow!("filepath to str"))?; + let metadata = tokio::fs::metadata(filepath_str) + .await + .context(format!("config file not found: {}", &filepath_str))?; if !metadata.is_file() { - Err(anyhow!("{} is not a file", filepath.to_str().expect("str")))? + Err(anyhow!("config is not a file {}", &filepath_str))?; } Ok(VarsFile { - filepath: filepath.to_path_buf(), - vars: None, + filepath: ArcStr::from(filepath_str), encoding, }) } - async fn from_dir(dir: PathBuf, encoding: String) -> Result { - let filepath = dir.join("vars"); - let err_context = format!( - "vars or vars.bat file not found in {}", - dir.to_str().expect("str") - ); + async fn from_dir

(dir: P, encoding: ArcStr) -> Result + where + P: AsRef, + { + let filepath = dir.as_ref().join("vars"); + let err_context = "vars or vars.bat file not found"; match Self::from_file(&filepath, encoding.clone()).await { Ok(res) => Ok(res), @@ -47,35 +104,38 @@ impl VarsFile { pub(crate) async fn from_config(config: &AppConfig) -> Result { Self::from_dir( PathBuf::from(&config.base_directory), - config.encoding.clone(), + ArcStr::from(config.encoding.as_str()), ) .await } - pub(crate) async fn parse(&mut self) -> Result<()> { - let mut result = VarsMap::new(); - let lines = read_file_by_lines(&self.filepath, &self.encoding).await?; + pub(crate) async fn parse(&self) -> Result { + let mut result = ArcVarsMap::new(); + result.insert("__file__".into(), self.filepath.clone()); + result.insert("__encoding__".into(), self.encoding.clone()); + + let lines = read_file_by_lines(&self.filepath.as_path(), &self.encoding) + .await + .context("vars read error")?; let lines = Pin::from(lines); pin!(lines); - let re_v2 = - Regex::new(r#"^(export|set)\s\b(?P[\w\d_]+)\b=\s?"?(?P[^\#]+?)"?$"#) - .context("regex v2")?; - let re_v3 = Regex::new(r"^set_var\s(?P[\w\d_]+)\s+(?P[^\#]+?)$") - .context("regex v3")?; + let re_v2 = Regex::new(r#"^(export|set)\s\b(?P[\w\d_]+)\b=\s?"?(?P[^#]+?)"?$"#) + .context("regex v2") + .context("re_v2 error")?; + let re_v3 = Regex::new(r"^set_var\s(?P[\w\d_]+)\s+(?P[^#]+?)$") + .context("regex v3") + .context("re_v3 error")?; while let Some(line) = lines.next().await { - if let Some(caps) = re_v2.captures(line.as_str()) { - result.insert(caps["key"].to_string(), caps["value"].to_string()); - continue; + for re in [&re_v2, &re_v3].iter() { + if let Some(caps) = re.captures(line.as_str()) { + let [key, value] = [&caps["key"], &caps["value"]].map(ArcStr::from); + result.insert(key, value); + continue; + } } - - if let Some(caps) = re_v3.captures(line.as_str()) { - result.insert(caps["key"].to_string(), caps["value"].to_string()); - }; } - - self.vars = Some(result); - Ok(()) + Ok(VarsMap(result)) } }