1mod feature;
2pub mod token;
3pub mod key;
4
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::{BufRead, BufReader};
8use std::path::Path;
9use uuid::Uuid;
10use crate::feature::{featured, SupportedDb};
11use crate::key::KeyFile;
12
13const CMD_FILE: &str = "file";
14const CMD_DB: &str = "db";
15const CMD_TBL: &str = "config";
16const CMD_TOKEN: &str = "token";
17const CMD_TTL: &str = "ttl";
18const CMD_KEY: &str = "key";
19const CMD_SECRET: &str = "secret";
20const CMD_PASS: &str = "PASSPHRASE";
21const CMD_UUID: &str = "uuid";
23
24
25#[derive(Debug, Clone)]
68pub struct ArgConfig {
69 pub uuid: Uuid,
71 pub uuid_gen: bool,
73 pub db_url: String,
75
76 pub table: String,
78 pub cfg: HashMap<String, String>,
80 pub token: token::Token,
82 pub pk: Option<KeyFile>,
84 pub secret: Option<String>,
87}
88
89
90impl ArgConfig {
91
92 pub fn from_args() -> Result<ArgConfig, String> {
93 let user = match std::env::var_os("USER") {
94 Some(a) => a.to_str().unwrap_or("postgres").to_string(),
95 _ => "postgres".to_string(),
96 };
97 let f = featured();
98 let pwd = if !f.env_pwd().is_empty() {
99 match std::env::var_os(f.env_pwd().as_str()).map(|v| v) {
100 Some(a) => a.to_str().map(|v| v.to_string()),
101 _ => None,
102 }
103 } else {
104 None
105 };
106 let input: Vec<String> = std::env::args_os().map(|e| e.to_string_lossy().to_string()).collect();
107
108 ArgConfig::new(input, f, user, pwd)
109 }
110
111 fn new(input: Vec<String>, feature: SupportedDb, user: String, pwd: Option<String>) -> Result<Self, String> {
113 let mut cfg = HashMap::new();
114 let mut db: Option<String> = None;
115 let mut tbl: Option<String> = None;
116 let mut token: Option<String> = None;
117
118 let mut ttl: Option<String> = None;
119 let mut pk: Option<String> = None;
120 let mut secret: Option<String> = None;
121 let mut uuid: Option<Uuid> = None;
122 let mut ignore_next = true;
123 for i in 1..input.len() {
124 if ignore_next {continue}
125 if input[i].starts_with("--") {
126 if i < input.len() - 1 {
127 let v = &input[i].as_str()[2..];
128 if v == CMD_FILE {
129 let _ = load(v, &mut cfg)?;
130 ignore_next = true;
131 } else if v == CMD_DB {
132 db = Some(input[i + 1].to_string());
133 ignore_next = true;
134 } else if v == CMD_TBL {
135 tbl = Some(input[i + 1].to_string());
136 ignore_next = true;
137 } else if v == CMD_TOKEN {
138 token = Some(input[i + 1].to_string());
139 ignore_next = true;
140 } else if v == CMD_TTL {
141 ttl = Some(input[i + 1].to_string());
142 ignore_next = true;
143 } else if v == CMD_KEY {
144 let file = input[i + 1].to_string();
145 if key::is_key_file(&file) {
146 pk = Some(file);
147 ignore_next = true;
148 }
149 } else if v == CMD_UUID {
150 uuid = Uuid::parse_str(input[i + 1].as_str()).ok();
151 ignore_next = true;
152 } else if v == CMD_SECRET {
153 secret = Some(input[i + 1].to_string());
154 ignore_next = true;
155 }
156 }
157 } else {
158 }
159 }
160 for i in &input {
162 if i.starts_with("--") {
163 continue
164 }
165 if db.is_none() && feature.is_valid_url(i) {
166 db = Some(i.to_string());
167 continue
168 }
169 if tbl.is_none() && is_sound_schema_table(i) {
170 tbl = Some(i.to_string());
171 continue
172 }
173 if token.is_none() && i.len() > 1 {
174 token = Some(i.to_string());
175 continue
176 }
177 if ttl.is_none() && i.parse::<u16>().is_ok() {
178 ttl = Some(i.to_string());
179 }
180 if uuid.is_none() {
181 uuid = Uuid::parse_str(i).ok();
182 }
183 }
184 if uuid.is_none() {
185 uuid = Uuid::parse_str(get_env_or_cfg(CMD_UUID, &cfg, "").as_str()).ok();
186 }
187
188 if let Some(a) = std::env::var_os(CMD_SECRET.to_uppercase()).map(|v| v) {
189 secret = Some(a.to_str().map(|v| v.to_string()).unwrap_or("".to_string()));
190 #[cfg(not(feature="keep_env_secret"))]
191 unsafe {
192 std::env::remove_var(CMD_SECRET);
193 }
194 }
195
196 Ok(ArgConfig {
197 uuid_gen: uuid.is_none(),
198 uuid: uuid.unwrap_or(Uuid::new_v4()),
199 db_url: link_db_user(db.unwrap_or(get_env_or_cfg(CMD_DB, &cfg, feature.default_url(&user).as_str())), user),
200 table: tbl.unwrap_or(get_env_or_cfg(CMD_TBL, &cfg, get_exec_name("public.", input[0].as_str()).as_str())),
201 token: token::Token::new(
202 token.unwrap_or(get_env_or_cfg(CMD_TOKEN, &cfg, "")),
203 ttl.unwrap_or(get_env_or_cfg(CMD_TTL, &cfg, "1")),
204 pwd
205 )?,
206 pk: KeyFile::new(
207 pk.unwrap_or(get_env_or_cfg(CMD_KEY, &cfg, &"")),
208 std::env::var_os(CMD_PASS).map(|p| p.to_string_lossy().to_string()),
209 )?,
210 cfg,
211 secret,
212 })
213 }
214
215 pub fn db_url(&self) -> String {
217 let url = self.db_url.clone();
218 if let Some(i) = url.find(":$P") {
219 if let Some(y) = url.find("@") {
220 let pwd = url.as_str()[i+1..y].to_owned();
221 return url.replace(
222 pwd.as_str(),
223 self.token.value.clone().unwrap_or("".into()).as_str()).to_string();
224 }
225 }
226 url
227 }
228}
229
230#[inline]
231fn link_db_user(url: String, user: String) -> String {
232 url.replace("$USER", user.as_str())
233}
234
235#[inline]
236fn get_env_or_cfg(input: &str, cfg: &HashMap<String, String>, def: &str) -> String {
237 match std::env::var_os(input).map(|v| v) {
238 Some(a) => a.to_str().map(|v| v.to_string()).unwrap_or(def.to_string()),
239 None => cfg.get(input).unwrap_or(&def.to_string()).into()
240 }
241}
242
243#[inline]
245fn get_exec_name(schema: &str, input: &str) -> String {
246 if input.is_empty() {
247 return "".to_string();
248 }
249 let e = Path::new(input);
250 let name = e.file_name().map(|f|f.to_str().unwrap_or("")).unwrap_or("").to_string();
251 #[cfg(windows)]
252 let name = name.replace(".exe", "");
253 let name = if let Some(i) = name.rfind(std::path::MAIN_SEPARATOR_STR) {
254 name[i+1..].to_string()
255 } else {
256 name
257 };
258 format!("{}{}", schema, name)
259}
260
261#[inline]
262fn is_sound_schema_table(input: &str) -> bool {
263 let y = input.contains(".");
264 #[cfg(windows)]
265 let y = y && !input.ends_with(".exe");
266 y
267}
268
269#[inline]
270fn load(file: &str, cfg: &mut HashMap<String, String>) -> Result<(), String> {
271 let f = File::open(file).map_err(|e| e.to_string())?;
272 let reader = BufReader::new(f);
273 for line in reader.lines() {
274 if let Ok(l) = line {
275 if let Some(i) = l.chars().position(|c| c == '=' || c == ':' || c == '#' || c == ';' || c == '/' || c == '[') {
276 if l.as_bytes()[i] != b'#' && l.as_bytes()[i] != b';' && l.as_bytes()[i] != b'/' && l.as_bytes()[i] != b'[' {
277 let key = l[..i].trim().to_lowercase();
278 let value = l[i + 1..].trim().to_string();
279 cfg.insert(key, value);
280 }
281 }
282 }
283 }
284 Ok(())
285}
286
287#[allow(warnings)]
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292
293 #[test]
294 #[cfg(unix)]
295 fn test_file_name() {
296 assert_eq!(get_exec_name("","").as_str(), "");
297 assert_eq!(get_exec_name("","target/debug/marg").as_str(), "marg");
298 assert_eq!(get_exec_name("","marg").as_str(), "marg");
299 }
300
301 #[test]
302 #[cfg(windows)]
303 fn test_file_name() {
304 assert_eq!(get_exec_name("","").as_str(), "");
305 assert_eq!(get_exec_name("","target\\debug\\marg.exe").as_str(), "marg");
306 assert_eq!(get_exec_name("","target\\\\debug\\\\marg.exe").as_str(), "marg");
307 }
308
309 #[test]
310 fn config_args_file1_test() {
311 let cfg = ArgConfig::new(
313 vec!["".to_string()],
314 SupportedDb::Postgres, "".to_string(), None).unwrap();
315 assert_eq!(0, cfg.cfg.len());
316
317 }
318
319 #[test]
320 fn config_args_file2_test() {
321 let url = "postgresql://user:pwd@host/db".to_string();
322 let cfg = ArgConfig::new(
323 vec![url.clone()],
324 SupportedDb::Postgres, "vk".to_string(), None).unwrap();
325 assert_eq!(url, cfg.db_url());
326 }
327
328 #[test]
329 fn config_args_file3_test() {
330 let url = "postgresql://user:pwd@host/db".to_string();
331 let t = "public.table".to_string();
332 let cfg = ArgConfig::new(
333 vec![url.clone(), t.clone()],
334 SupportedDb::Postgres, "".to_string(), None).unwrap();
335 assert_eq!(url, cfg.db_url());
336 assert_eq!(t, cfg.table);
337 }
338
339 #[test]
340 fn config_args_user_test() {
341 let user = match std::env::var_os("USER") {
342 Some(a) => a.to_str().unwrap_or("postgres").to_string(),
343 _ => "postgres".to_string(),
344 };
345
346 assert_eq!(format!("postgresql://{}:pwd@host/db", user), link_db_user("postgresql://$USER:pwd@host/db".to_string(), user.clone()));
347 assert_eq!(format!("postgresql://{}:$PWD@host/db", user), link_db_user("postgresql://$USER:$PWD@host/db".to_string(), user.clone()));
348 assert_eq!(format!("postgresql://{}@host/db", user), link_db_user("postgresql://$USER@host/db".to_string(), user.clone()));
349 assert_eq!(format!("postgresql://{}@host/db", ""), link_db_user("postgresql://@host/db".to_string(), user.clone()));
350
351 }
352
353
354}