1use crate::error::{HypermailError, Result};
2use std::path::{Path, PathBuf};
3
4pub const ANTISPAM_AT: &str = "@";
5pub const LANGUAGE: &str = "en";
6pub const HTMLSUFFIX: &str = "html";
7pub const DEFAULTINDEX: &str = "date";
8pub const INLINE_TYPES: &str = "image/gif image/jpeg image/png";
9pub const PROGRESS: i32 = 0;
10pub const MAILCOMMAND: &str = "mailto:$TO?subject=$SUBJECT&in-reply-to=$ID";
11pub const DOMAINADDR: &str = "";
12
13pub const DELETE_REMOVES_FILES: i32 = 0;
14pub const DELETE_LEAVES_STUBS: i32 = 1;
15pub const DELETE_LEAVES_EXPIRED_TEXT: i32 = 2;
16pub const DELETE_LEAVES_TEXT: i32 = 3;
17
18#[derive(Debug, Clone)]
20pub enum ConfigType {
21 String,
22 Switch,
23 Integer,
24 List,
25 StringList,
26 Octal,
27}
28
29#[derive(Debug, Clone)]
31pub struct ConfigEntry {
32 pub label: &'static str,
33 pub flags: ConfigType,
34 pub default_str: Option<&'static str>,
35 pub default_int: i64,
36 pub verbose: &'static str,
37}
38
39#[derive(Debug, Clone)]
41pub struct HmList {
42 pub values: Vec<String>,
43}
44
45impl Default for HmList {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl HmList {
52 pub fn new() -> Self {
54 HmList { values: Vec::new() }
55 }
56
57 pub fn from_whitespace_str(s: &str) -> Self {
59 let values: Vec<String> = s.split_whitespace().map(|s| s.to_string()).collect();
60 HmList { values }
61 }
62
63 pub fn contains(&self, val: &str) -> bool {
65 self.values.iter().any(|v| v == val)
66 }
67
68 pub fn add(&mut self, val: &str) {
70 if !self.contains(val) {
71 self.values.push(val.to_string());
72 }
73 }
74
75 pub fn add_list(&mut self, val: &str) {
77 for v in val.split_whitespace() {
78 self.add(v);
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
88pub struct Config {
89 pub fragment_prefix: String,
91 pub htmlmessage_deleted: Option<String>,
92 pub antispam_at: String,
93 pub antispamdomain: Option<String>,
94 pub language: String,
95 pub htmlsuffix: String,
96 pub mbox: Option<String>,
97 pub archives: Option<String>,
98 pub custom_archives: Option<String>,
99 pub about: Option<String>,
100 pub label: Option<String>,
101 pub dir: Option<String>,
102 pub defaultindex: String,
103 pub default_top_index: String,
104 pub mailcommand: String,
105 pub newmsg_command: String,
106 pub replymsg_command: String,
107 pub inreplyto_command: Option<String>,
108 pub mailto: Option<String>,
109 pub hmail: Option<String>,
110 pub domainaddr: Option<String>,
111 pub css: Option<String>,
112 pub icss_url: Option<String>,
113 pub mcss_url: Option<String>,
114 pub dateformat: Option<String>,
115 pub indexdateformat: Option<String>,
116 pub stripsubject: Option<String>,
117 pub link_to_replies: Option<String>,
118 pub quote_link_string: Option<String>,
119 pub ihtmlheader: Option<String>,
120 pub ihtmlfooter: Option<String>,
121 pub ihtmlhead: Option<String>,
122 pub ihtmlhelpup: Option<String>,
123 pub ihtmlhelplow: Option<String>,
124 pub ihtmlnavbar2up: Option<String>,
125 pub mhtmlheader: Option<String>,
126 pub mhtmlfooter: Option<String>,
127 pub attachmentlink: Option<String>,
128 pub bodyheader: Option<String>,
129 pub bodyheaderend: Option<String>,
130 pub bodyfooter: Option<String>,
131 pub unsafe_chars: Option<String>,
132 pub filename_base: Option<String>,
133 pub folder_by_date: Option<String>,
134 pub latest_folder: Option<String>,
135 pub base_url: Option<String>,
136 pub describe_folder: Option<String>,
137 pub delete_older: Option<String>,
138 pub delete_newer: Option<String>,
139 pub alts_text: Option<String>,
140 pub description: Option<String>,
141 pub theme: Option<String>,
142 pub append_filename: Option<String>,
143 pub txtsuffix: Option<String>,
144
145 pub email_address_obfuscation: bool,
147 pub i18n: bool,
148 pub i18n_body: bool,
149 pub overwrite: bool,
150 pub inlinehtml: bool,
151 pub readone: bool,
152 pub reverse: bool,
153 pub reverse_folders: bool,
154 pub showheaders: bool,
155 pub showbr: bool,
156 pub showreplies: bool,
157 pub indextable: bool,
158 pub iquotes: bool,
159 pub eurodate: bool,
160 pub gmtime: bool,
161 pub isodate: bool,
162 pub require_msgids: bool,
163 pub discard_dup_msgids: bool,
164 pub usemeta: bool,
165 pub uselock: bool,
166 pub ietf_mbox: bool,
167 pub linkquotes: bool,
168 pub monthly_index: bool,
169 pub yearly_index: bool,
170 pub spamprotect: bool,
171 pub spamprotect_id: bool,
172 pub attachmentsindex: bool,
173 pub usegdbm: bool,
174 pub writehaof: bool,
175 pub append: bool,
176 pub nonsequential: bool,
177 pub warn_surpressions: bool,
178 pub files_by_thread: bool,
179 pub href_detection: bool,
180 pub mbox_shortened: bool,
181 pub report_new_file: bool,
182 pub report_new_folder: bool,
183 pub use_sender_date: bool,
184 pub inline_addlink: bool,
185 pub iso2022jp: bool,
186 pub delete_incremental: bool,
187 pub showgenerator: bool,
188 pub show_warnings: bool,
189
190 pub increment: i32,
192 pub showhtml: i32,
193 pub show_msg_links: i32,
194 pub show_index_links: i32,
195 pub thrdlevels: i32,
196 pub dirmode: i32,
197 pub filemode: i32,
198 pub locktime: i32,
199 pub searchbackmsgnum: i32,
200 pub quote_hide_threshold: i32,
201 pub thread_file_depth: i32,
202 pub startmsgnum: i32,
203 pub msgsperfolder: i32,
204 pub save_alts: i32,
205 pub delete_level: i32,
206 pub progress: i32,
207 pub max_message_size: usize,
208
209 pub show_headers: HmList,
211 pub avoid_indices: HmList,
212 pub avoid_top_indices: HmList,
213 pub skip_headers: HmList,
214 pub text_types: HmList,
215 pub inline_types: HmList,
216 pub prefered_types: HmList,
217 pub ignore_types: HmList,
218 pub filter_out: HmList,
219 pub filter_require: HmList,
220 pub filter_out_full_body: HmList,
221 pub filter_require_full_body: HmList,
222 pub deleted: HmList,
223 pub expires: HmList,
224 pub delete_msgnum: HmList,
225}
226
227impl Default for Config {
228 fn default() -> Self {
229 Config {
230 fragment_prefix: "msg".to_string(),
231 htmlmessage_deleted: None,
232 antispam_at: ANTISPAM_AT.to_string(),
233 antispamdomain: None,
234 language: LANGUAGE.to_string(),
235 htmlsuffix: HTMLSUFFIX.to_string(),
236 mbox: None,
237 archives: None,
238 custom_archives: None,
239 about: None,
240 label: None,
241 dir: None,
242 defaultindex: DEFAULTINDEX.to_string(),
243 default_top_index: "folders".to_string(),
244 mailcommand: MAILCOMMAND.to_string(),
245 newmsg_command: "mailto:$TO".to_string(),
246 replymsg_command: MAILCOMMAND.to_string(),
247 inreplyto_command: None,
248 mailto: None,
249 hmail: None,
250 domainaddr: None,
251 css: None,
252 icss_url: None,
253 mcss_url: None,
254 dateformat: None,
255 indexdateformat: None,
256 stripsubject: None,
257 link_to_replies: None,
258 quote_link_string: None,
259 ihtmlheader: None,
260 ihtmlfooter: None,
261 ihtmlhead: None,
262 ihtmlhelpup: None,
263 ihtmlhelplow: None,
264 ihtmlnavbar2up: None,
265 mhtmlheader: None,
266 mhtmlfooter: None,
267 attachmentlink: None,
268 unsafe_chars: None,
269 filename_base: None,
270 folder_by_date: None,
271 latest_folder: None,
272 base_url: None,
273 describe_folder: None,
274 delete_older: None,
275 delete_newer: None,
276 alts_text: None,
277 append_filename: None,
278 txtsuffix: None,
279 description: None,
280 theme: None,
281 bodyheader: None,
282 bodyheaderend: None,
283 bodyfooter: None,
284 email_address_obfuscation: false,
285 i18n: false,
286 i18n_body: false,
287 overwrite: false,
288 inlinehtml: true,
289 readone: false,
290 reverse: false,
291 reverse_folders: false,
292 showheaders: true,
293 showbr: true,
294 showreplies: true,
295 indextable: false,
296 iquotes: true,
297 eurodate: true,
298 gmtime: false,
299 isodate: false,
300 require_msgids: true,
301 discard_dup_msgids: true,
302 usemeta: false,
303 uselock: true,
304 ietf_mbox: false,
305 linkquotes: false,
306 monthly_index: false,
307 yearly_index: false,
308 spamprotect: true,
309 spamprotect_id: true,
310 attachmentsindex: true,
311 usegdbm: false,
312 writehaof: false,
313 append: false,
314 nonsequential: false,
315 warn_surpressions: true,
316 files_by_thread: false,
317 href_detection: true,
318 mbox_shortened: false,
319 report_new_file: false,
320 report_new_folder: false,
321 use_sender_date: false,
322 inline_addlink: true,
323 iso2022jp: false,
324 delete_incremental: true,
325 showgenerator: true,
326 show_warnings: false,
327 increment: 0,
328 showhtml: 1,
329 show_msg_links: 1,
330 show_index_links: 1,
331 thrdlevels: 50, dirmode: 0o755,
333 filemode: 0o644,
334 locktime: 3600,
335 searchbackmsgnum: 500,
336 quote_hide_threshold: 100,
337 thread_file_depth: 0,
338 startmsgnum: 0,
339 msgsperfolder: 0,
340 save_alts: 0,
341 delete_level: DELETE_LEAVES_TEXT,
342 progress: PROGRESS,
343 max_message_size: 100 * 1024 * 1024,
344 show_headers: HmList::new(),
345 avoid_indices: HmList::new(),
346 avoid_top_indices: HmList::new(),
347 skip_headers: HmList::new(),
348 text_types: HmList::new(),
349 inline_types: HmList::from_whitespace_str(INLINE_TYPES),
350 deleted: HmList::from_whitespace_str("X-Hypermail-Deleted X-No-Archive"),
351 expires: HmList::from_whitespace_str("Expires"),
352 delete_msgnum: HmList::new(),
353 filter_out: HmList::new(),
354 filter_require: HmList::new(),
355 filter_out_full_body: HmList::new(),
356 filter_require_full_body: HmList::new(),
357 prefered_types: HmList::new(),
358 ignore_types: HmList::new(),
359 }
360 }
361}
362
363impl Config {
364 pub fn set_string(&mut self, key: &str, val: &str) -> Result<()> {
366 match key {
367 "fragment_prefix" => self.fragment_prefix = val.to_string(),
368 "htmlmessage_deleted" => self.htmlmessage_deleted = Some(val.to_string()),
369 "antispam_at" => self.antispam_at = val.to_string(),
370 "antispamdomain" => {
371 if val == "NONE" || val.is_empty() {
372 self.antispamdomain = None;
373 } else {
374 self.antispamdomain = Some(val.to_string());
375 }
376 },
377 "language" => self.language = val.to_string(),
378 "htmlsuffix" => self.htmlsuffix = val.to_string(),
379 "mbox" => {
380 if val == "NONE" {
381 self.mbox = None;
382 } else {
383 self.mbox = Some(val.to_string());
384 }
385 },
386 "archives" => {
387 if val == "NONE" {
388 self.archives = None;
389 } else {
390 self.archives = Some(val.to_string());
391 }
392 },
393 "custom_archives" => {
394 if val == "NONE" {
395 self.custom_archives = None;
396 } else {
397 self.custom_archives = Some(val.to_string());
398 }
399 },
400 "about" => {
401 if val == "NONE" {
402 self.about = None;
403 } else {
404 self.about = Some(val.to_string());
405 }
406 },
407 "label" => {
408 if val == "NONE" {
409 self.label = None;
410 } else {
411 self.label = Some(val.to_string());
412 }
413 },
414 "dir" => {
415 if val == "NONE" {
416 self.dir = None;
417 } else {
418 self.dir = Some(val.to_string());
419 }
420 },
421 "defaultindex" => self.defaultindex = val.to_string(),
422 "default_top_index" => self.default_top_index = val.to_string(),
423 "mailcommand" => self.mailcommand = val.to_string(),
424 "newmsg_command" => self.newmsg_command = val.to_string(),
425 "replymsg_command" => self.replymsg_command = val.to_string(),
426 "inreplyto_command" => self.inreplyto_command = Some(val.to_string()),
427 "mailto" => {
428 if val == "NONE" {
429 self.mailto = None;
430 } else {
431 self.mailto = Some(val.to_string());
432 }
433 },
434 "hmail" => {
435 if val == "NONE" {
436 self.hmail = None;
437 } else {
438 self.hmail = Some(val.to_string());
439 }
440 },
441 "domainaddr" => {
442 if val == "NONE" {
443 self.domainaddr = None;
444 } else {
445 self.domainaddr = Some(val.to_string());
446 }
447 },
448 "css" => self.css = Some(val.to_string()),
449 "icss_url" => self.icss_url = Some(val.to_string()),
450 "mcss_url" => self.mcss_url = Some(val.to_string()),
451 "dateformat" => self.dateformat = Some(val.to_string()),
452 "indexdateformat" => self.indexdateformat = Some(val.to_string()),
453 "stripsubject" => self.stripsubject = Some(val.to_string()),
454 "link_to_replies" => self.link_to_replies = Some(val.to_string()),
455 "quote_link_string" => self.quote_link_string = Some(val.to_string()),
456 "ihtmlheaderfile" => self.ihtmlheader = Some(val.to_string()),
457 "ihtmlfooterfile" => self.ihtmlfooter = Some(val.to_string()),
458 "ihtmlheadfile" => self.ihtmlhead = Some(val.to_string()),
459 "ihtmlhelpupfile" => self.ihtmlhelpup = Some(val.to_string()),
460 "ihtmlhelplowfile" => self.ihtmlhelplow = Some(val.to_string()),
461 "ihtmlnavbar2upfile" => self.ihtmlnavbar2up = Some(val.to_string()),
462 "mhtmlheaderfile" => self.mhtmlheader = Some(val.to_string()),
463 "mhtmlfooterfile" => self.mhtmlfooter = Some(val.to_string()),
464 "attachmentlink" => self.attachmentlink = Some(val.to_string()),
465 "unsafe_chars" => self.unsafe_chars = Some(val.to_string()),
466 "description" => self.description = Some(val.to_string()),
467 "theme" => self.theme = Some(val.to_string()),
468 "bodyheader" => self.bodyheader = Some(val.to_string()),
469 "bodyheaderend" => self.bodyheaderend = Some(val.to_string()),
470 "bodyfooter" => self.bodyfooter = Some(val.to_string()),
471 "filename_base" => self.filename_base = Some(val.to_string()),
472 "folder_by_date" => {
473 if val.is_empty() || val == "NONE" {
474 self.folder_by_date = None;
475 } else {
476 self.folder_by_date = Some(val.to_string());
477 }
478 },
479 "latest_folder" => self.latest_folder = Some(val.to_string()),
480 "base_url" => self.base_url = Some(val.to_string()),
481 "describe_folder" => self.describe_folder = Some(val.to_string()),
482 "delete_older" => self.delete_older = Some(val.to_string()),
483 "delete_newer" => self.delete_newer = Some(val.to_string()),
484 "alts_text" => self.alts_text = Some(val.to_string()),
485 "append_filename" => self.append_filename = Some(val.to_string()),
486 "txtsuffix" => self.txtsuffix = Some(val.to_string()),
487 _ => {
488 return Err(HypermailError::InvalidConfigValue {
489 key: key.to_string(),
490 message: format!("unknown string config key: {}", key),
491 })
492 },
493 }
494 Ok(())
495 }
496
497 pub fn set_switch(&mut self, key: &str, val: bool) -> Result<()> {
499 match key {
500 "email_address_obfuscation" => self.email_address_obfuscation = val,
501 "i18n" => self.i18n = val,
502 "i18n_body" => self.i18n_body = val,
503 "overwrite" => self.overwrite = val,
504 "inlinehtml" => self.inlinehtml = val,
505 "readone" => self.readone = val,
506 "reverse" => self.reverse = val,
507 "reverse_folders" => self.reverse_folders = val,
508 "showheaders" => self.showheaders = val,
509 "showbr" => self.showbr = val,
510 "showreplies" => self.showreplies = val,
511 "indextable" => self.indextable = val,
512 "iquotes" => self.iquotes = val,
513 "eurodate" => self.eurodate = val,
514 "gmtime" => self.gmtime = val,
515 "isodate" => self.isodate = val,
516 "require_msgids" => self.require_msgids = val,
517 "discard_dup_msgids" => self.discard_dup_msgids = val,
518 "usemeta" => self.usemeta = val,
519 "uselock" => self.uselock = val,
520 "ietf_mbox" => self.ietf_mbox = val,
521 "linkquotes" => self.linkquotes = val,
522 "monthly_index" => self.monthly_index = val,
523 "yearly_index" => self.yearly_index = val,
524 "spamprotect" => self.spamprotect = val,
525 "spamprotect_id" => self.spamprotect_id = val,
526 "attachmentsindex" => self.attachmentsindex = val,
527 "usegdbm" => self.usegdbm = val,
528 "writehaof" => self.writehaof = val,
529 "append" => self.append = val,
530 "nonsequential" => self.nonsequential = val,
531 "warn_surpressions" => self.warn_surpressions = val,
532 "files_by_thread" => self.files_by_thread = val,
533 "href_detection" => self.href_detection = val,
534 "mbox_shortened" => self.mbox_shortened = val,
535 "report_new_file" => self.report_new_file = val,
536 "report_new_folder" => self.report_new_folder = val,
537 "use_sender_date" => self.use_sender_date = val,
538 "inline_addlink" => self.inline_addlink = val,
539 "iso2022jp" => self.iso2022jp = val,
540 "delete_incremental" => self.delete_incremental = val,
541 "showgenerator" => self.showgenerator = val,
542 "show_warnings" => self.show_warnings = val,
543 _ => {
544 return Err(HypermailError::InvalidConfigValue {
545 key: key.to_string(),
546 message: format!("unknown switch config key: {}", key),
547 })
548 },
549 }
550 Ok(())
551 }
552
553 pub fn set_integer(&mut self, key: &str, val: i64) -> Result<()> {
555 match key {
556 "increment" => self.increment = val as i32,
557 "showhtml" => self.showhtml = val as i32,
558 "show_msg_links" => self.show_msg_links = val as i32,
559 "show_index_links" => self.show_index_links = val as i32,
560 "thrdlevels" => self.thrdlevels = val as i32,
561 "dirmode" => self.dirmode = val as i32,
562 "filemode" => self.filemode = val as i32,
563 "locktime" => self.locktime = val as i32,
564 "searchbackmsgnum" => self.searchbackmsgnum = val as i32,
565 "quote_hide_threshold" => self.quote_hide_threshold = val as i32,
566 "thread_file_depth" => self.thread_file_depth = val as i32,
567 "startmsgnum" => self.startmsgnum = val as i32,
568 "msgsperfolder" => self.msgsperfolder = val as i32,
569 "save_alts" => self.save_alts = val as i32,
570 "delete_level" => self.delete_level = val as i32,
571 "progress" => self.progress = val as i32,
572 "max_message_size" => self.max_message_size = val as usize,
573 _ => {
574 return Err(HypermailError::InvalidConfigValue {
575 key: key.to_string(),
576 message: format!("unknown integer config key: {}", key),
577 })
578 },
579 }
580 Ok(())
581 }
582
583 pub fn set_list(&mut self, key: &str, val: &str) -> Result<()> {
585 let list = match key {
586 "show_headers" => &mut self.show_headers,
587 "avoid_indices" => &mut self.avoid_indices,
588 "avoid_top_indices" => &mut self.avoid_top_indices,
589 "text_types" => &mut self.text_types,
590 "inline_types" => &mut self.inline_types,
591 "prefered_types" => &mut self.prefered_types,
592 "ignore_types" => &mut self.ignore_types,
593 "filter_out" => &mut self.filter_out,
594 "filter_require" => &mut self.filter_require,
595 "filter_out_full_body" => &mut self.filter_out_full_body,
596 "filter_require_full_body" => &mut self.filter_require_full_body,
597 "deleted" => &mut self.deleted,
598 "expires" => &mut self.expires,
599 "delete_msgnum" => &mut self.delete_msgnum,
600 _ => {
601 return Err(HypermailError::InvalidConfigValue {
602 key: key.to_string(),
603 message: format!("unknown list config key: {}", key),
604 })
605 },
606 };
607 list.add_list(val);
608 Ok(())
609 }
610
611 pub fn apply_cli_arg(&mut self, key: &str, val: &str) -> Result<()> {
615 let (actual_key, actual_val) = if let Some(eq_pos) = key.find('=') {
616 let k = &key[..eq_pos];
617 let v = &key[eq_pos + 1..];
618 (k, v)
619 } else {
620 (key, val)
621 };
622
623 let actual_key = actual_key.strip_prefix("hm_").unwrap_or(actual_key);
624 let actual_key = actual_key.strip_prefix("set_").unwrap_or(actual_key);
625 let actual_val = actual_val.trim();
626
627 if actual_val == "ON"
628 || actual_val == "YES"
629 || actual_val == "On"
630 || actual_val == "Yes"
631 || actual_val == "on"
632 || actual_val == "yes"
633 {
634 if let Ok(()) = self.set_switch(actual_key, true) {
635 return Ok(());
636 }
637 return self
638 .set_integer(actual_key, 1)
639 .or_else(|_| self.set_string(actual_key, actual_val));
640 }
641 if (actual_val == "OFF"
642 || actual_val == "NO"
643 || actual_val == "Off"
644 || actual_val == "No"
645 || actual_val == "off"
646 || actual_val == "no")
647 && self.set_switch(actual_key, false).is_ok()
648 {
649 return Ok(());
650 }
651
652 if actual_key == "dirmode" || actual_key == "filemode" {
655 let octal_str = actual_val.strip_prefix('0').unwrap_or(actual_val);
656 if let Ok(i) = i64::from_str_radix(octal_str, 8) {
657 if self.set_integer(actual_key, i).is_ok() {
658 return Ok(());
659 }
660 }
661 }
662
663 let int_val = actual_val.parse::<i64>();
664 if let Ok(i) = int_val {
665 if self.set_integer(actual_key, i).is_ok() {
666 return Ok(());
667 }
668 }
669 if self.set_string(actual_key, actual_val).is_ok() {
670 return Ok(());
671 }
672 if self.set_list(actual_key, actual_val).is_ok() {
673 return Ok(());
674 }
675
676 Err(HypermailError::InvalidConfigValue {
677 key: actual_key.to_string(),
678 message: format!("unrecognized config key or invalid value: {}", actual_val),
679 })
680 }
681
682 pub fn load_env(&mut self) {
684 for (key, val) in std::env::vars() {
685 if let Some(stripped) = key.strip_prefix("HM_") {
686 let config_key = stripped.to_lowercase();
687 let _ = self.apply_cli_arg(&config_key, &val);
688 }
689 }
690 }
691
692 pub fn post_process(&mut self) {
694 self.skip_headers.add("from");
695 self.skip_headers.add("date");
696 self.skip_headers.add("subject");
697 }
698
699 pub fn css_path(&self) -> String {
701 if let Some(ref css) = self.css {
702 if css.starts_with("http") || Path::new(css).is_absolute() {
703 css.clone()
704 } else if let Some(ref dir) = self.dir {
705 PathBuf::from(dir).join(css).to_string_lossy().into_owned()
706 } else {
707 css.clone()
708 }
709 } else {
710 String::new()
711 }
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn test_default_config() {
721 let cfg = Config::default();
722 assert_eq!(cfg.language, "en");
723 assert_eq!(cfg.htmlsuffix, "html");
724 assert_eq!(cfg.defaultindex, "date");
725 assert!(cfg.inlinehtml);
726 assert!(!cfg.overwrite);
727 assert_eq!(cfg.showhtml, 1);
728 assert_eq!(cfg.thrdlevels, 50); assert_eq!(cfg.dirmode, 0o755);
730 assert_eq!(cfg.filemode, 0o644);
731 assert_eq!(cfg.locktime, 3600);
732 assert_eq!(cfg.searchbackmsgnum, 500);
733 assert_eq!(cfg.quote_hide_threshold, 100);
734 assert_eq!(cfg.delete_level, DELETE_LEAVES_TEXT);
735 assert!(cfg.discard_dup_msgids);
736 assert!(cfg.require_msgids);
737 assert!(cfg.uselock);
738 assert!(cfg.href_detection);
739 assert!(cfg.warn_surpressions);
740 assert!(cfg.attachmentsindex);
741 assert!(cfg.spamprotect);
742 assert!(cfg.spamprotect_id);
743 assert!(cfg.showbr);
744 assert!(cfg.showreplies);
745 assert!(cfg.inline_addlink);
746 assert!(cfg.delete_incremental);
747 assert_eq!(cfg.fragment_prefix, "msg");
748 assert_eq!(cfg.antispam_at, "@");
749 assert_eq!(cfg.progress, 0);
750 assert!(cfg.inline_types.contains("image/gif"));
751 assert!(cfg.deleted.contains("X-Hypermail-Deleted"));
752 assert!(cfg.expires.contains("Expires"));
753 }
754
755 #[test]
756 fn test_set_string() {
757 let mut cfg = Config::default();
758 cfg.set_string("language", "de").unwrap();
759 assert_eq!(cfg.language, "de");
760 cfg.set_string("mbox", "NONE").unwrap();
761 assert!(cfg.mbox.is_none());
762 cfg.set_string("label", "test list").unwrap();
763 assert_eq!(cfg.label.as_deref(), Some("test list"));
764 }
765
766 #[test]
767 fn test_set_switch() {
768 let mut cfg = Config::default();
769 assert!(!cfg.overwrite);
770 cfg.set_switch("overwrite", true).unwrap();
771 assert!(cfg.overwrite);
772 }
773
774 #[test]
775 fn test_set_integer() {
776 let mut cfg = Config::default();
777 cfg.set_integer("showhtml", 2).unwrap();
778 assert_eq!(cfg.showhtml, 2);
779 cfg.set_integer("thrdlevels", 8).unwrap();
780 assert_eq!(cfg.thrdlevels, 8);
781 }
782
783 #[test]
784 fn test_set_list() {
785 let mut cfg = Config::default();
786 cfg.set_list("text_types", "text/html text/plain").unwrap();
787 assert!(cfg.text_types.contains("text/html"));
788 assert!(cfg.text_types.contains("text/plain"));
789 }
790
791 #[test]
792 fn test_apply_cli_arg() {
793 let mut cfg = Config::default();
794 cfg.apply_cli_arg("overwrite", "On").unwrap();
795 assert!(cfg.overwrite);
796 cfg.apply_cli_arg("showhtml", "2").unwrap();
797 assert_eq!(cfg.showhtml, 2);
798 cfg.apply_cli_arg("language=de", "").unwrap();
799 assert_eq!(cfg.language, "de");
800 }
801
802 #[test]
803 fn test_post_process() {
804 let mut cfg = Config::default();
805 cfg.post_process();
806 assert!(cfg.skip_headers.contains("from"));
807 assert!(cfg.skip_headers.contains("date"));
808 assert!(cfg.skip_headers.contains("subject"));
809 }
810
811 #[test]
812 fn test_unknown_key() {
813 let mut cfg = Config::default();
814 assert!(cfg.set_string("nonexistent", "value").is_err());
815 assert!(cfg.set_switch("nonexistent", true).is_err());
816 assert!(cfg.set_integer("nonexistent", 42).is_err());
817 }
818
819 #[test]
820 fn test_none_strings() {
821 let mut cfg = Config::default();
822 cfg.set_string("archives", "NONE").unwrap();
823 assert!(cfg.archives.is_none());
824 cfg.set_string("about", "NONE").unwrap();
825 assert!(cfg.about.is_none());
826 cfg.set_string("custom_archives", "NONE").unwrap();
827 assert!(cfg.custom_archives.is_none());
828 }
829
830 #[test]
831 fn test_apply_cli_hm_prefix() {
832 let mut cfg = Config::default();
833 cfg.apply_cli_arg("hm_overwrite", "On").unwrap();
834 assert!(cfg.overwrite);
835 }
836
837 #[test]
838 fn test_apply_cli_set_prefix() {
839 let mut cfg = Config::default();
840 cfg.apply_cli_arg("set_overwrite", "On").unwrap();
841 assert!(cfg.overwrite);
842 }
843
844 #[test]
845 fn test_apply_cli_bool_yes() {
846 let mut cfg = Config::default();
847 cfg.apply_cli_arg("overwrite", "YES").unwrap();
848 assert!(cfg.overwrite);
849 }
850
851 #[test]
852 fn test_apply_cli_bool_no() {
853 let mut cfg = Config::default();
854 assert!(cfg.inlinehtml);
855 cfg.apply_cli_arg("inlinehtml", "OFF").unwrap();
856 assert!(!cfg.inlinehtml);
857 }
858
859 #[test]
860 fn test_apply_cli_octal_dirmode() {
861 let mut cfg = Config::default();
862 cfg.apply_cli_arg("dirmode", "0755").unwrap();
863 assert_eq!(cfg.dirmode, 0o755);
864 }
865
866 #[test]
867 fn test_apply_cli_octal_filemode() {
868 let mut cfg = Config::default();
869 cfg.apply_cli_arg("filemode", "0644").unwrap();
870 assert_eq!(cfg.filemode, 0o644);
871 }
872
873 #[test]
874 fn test_apply_cli_decimal_dirmode() {
875 let mut cfg = Config::default();
876 cfg.apply_cli_arg("dirmode", "755").unwrap();
878 assert_eq!(cfg.dirmode, 0o755);
879 }
880
881 #[test]
882 fn test_apply_cli_inline_eq() {
883 let mut cfg = Config::default();
884 cfg.apply_cli_arg("language=fr", "").unwrap();
885 assert_eq!(cfg.language, "fr");
886 }
887
888 #[test]
889 fn test_apply_cli_list() {
890 let mut cfg = Config::default();
891 cfg.apply_cli_arg("filter_out", "spam").unwrap();
892 assert!(cfg.filter_out.contains("spam"));
893 }
894
895 #[test]
896 fn test_apply_cli_with_quoted_value() {
897 let mut cfg = Config::default();
898 cfg.apply_cli_arg("label", "\"My Archive\"").unwrap();
899 assert_eq!(cfg.label.as_deref(), Some("\"My Archive\""));
900 }
901
902 #[test]
903 fn test_apply_cli_unknown_key() {
904 let mut cfg = Config::default();
905 assert!(cfg.apply_cli_arg("nonexistent", "value").is_err());
906 }
907
908 #[test]
909 fn test_env_loading() {
910 let mut cfg = Config::default();
911 std::env::set_var("HM_LANGUAGE", "de");
912 cfg.load_env();
913 std::env::remove_var("HM_LANGUAGE");
914 assert_eq!(cfg.language, "de");
915 }
916
917 #[test]
918 fn test_set_all_switches() {
919 let mut cfg = Config::default();
920 for (key, initial) in &[
921 ("email_address_obfuscation", false),
922 ("i18n", true),
923 ("i18n_body", false),
924 ("overwrite", false),
925 ("inlinehtml", true),
926 ("readone", false),
927 ("reverse", false),
928 ("reverse_folders", false),
929 ("showheaders", true),
930 ("showbr", true),
931 ("showreplies", true),
932 ("indextable", false),
933 ("iquotes", true),
934 ("eurodate", true),
935 ("gmtime", false),
936 ("isodate", false),
937 ("require_msgids", true),
938 ("discard_dup_msgids", true),
939 ("usemeta", false),
940 ("uselock", true),
941 ("ietf_mbox", false),
942 ("linkquotes", false),
943 ("monthly_index", false),
944 ("yearly_index", false),
945 ("spamprotect", true),
946 ("spamprotect_id", true),
947 ("attachmentsindex", true),
948 ("usegdbm", false),
949 ("writehaof", false),
950 ("append", false),
951 ("nonsequential", false),
952 ("warn_surpressions", true),
953 ("files_by_thread", false),
954 ("href_detection", true),
955 ("mbox_shortened", false),
956 ("report_new_file", false),
957 ("report_new_folder", false),
958 ("use_sender_date", false),
959 ("inline_addlink", true),
960 ("iso2022jp", false),
961 ("delete_incremental", true),
962 ("showgenerator", true),
963 ("show_warnings", false),
964 ] {
965 assert!(cfg.set_switch(key, *initial).is_ok(), "switch {} should exist", key);
966 }
967 }
968
969 #[test]
970 fn test_set_all_integers() {
971 let mut cfg = Config::default();
972 for key in &[
973 "increment",
974 "showhtml",
975 "show_msg_links",
976 "show_index_links",
977 "thrdlevels",
978 "dirmode",
979 "filemode",
980 "locktime",
981 "searchbackmsgnum",
982 "quote_hide_threshold",
983 "thread_file_depth",
984 "startmsgnum",
985 "msgsperfolder",
986 "save_alts",
987 "delete_level",
988 "progress",
989 "max_message_size",
990 ] {
991 assert!(cfg.set_integer(key, 1).is_ok(), "integer {} should exist", key);
992 }
993 }
994
995 #[test]
996 fn test_set_all_strings() {
997 let mut cfg = Config::default();
998 for key in &[
999 "fragment_prefix",
1000 "htmlmessage_deleted",
1001 "antispam_at",
1002 "antispamdomain",
1003 "language",
1004 "htmlsuffix",
1005 "mbox",
1006 "archives",
1007 "custom_archives",
1008 "about",
1009 "label",
1010 "dir",
1011 "defaultindex",
1012 "default_top_index",
1013 "mailcommand",
1014 "newmsg_command",
1015 "replymsg_command",
1016 "inreplyto_command",
1017 "mailto",
1018 "hmail",
1019 "domainaddr",
1020 "css",
1021 "icss_url",
1022 "mcss_url",
1023 "dateformat",
1024 "indexdateformat",
1025 "stripsubject",
1026 "link_to_replies",
1027 "quote_link_string",
1028 "ihtmlheaderfile",
1029 "ihtmlfooterfile",
1030 "ihtmlheadfile",
1031 "ihtmlhelpupfile",
1032 "ihtmlhelplowfile",
1033 "ihtmlnavbar2upfile",
1034 "mhtmlheaderfile",
1035 "mhtmlfooterfile",
1036 "attachmentlink",
1037 "bodyheader",
1038 "bodyheaderend",
1039 "bodyfooter",
1040 "unsafe_chars",
1041 "filename_base",
1042 "folder_by_date",
1043 "latest_folder",
1044 "base_url",
1045 "describe_folder",
1046 "delete_older",
1047 "delete_newer",
1048 "alts_text",
1049 "description",
1050 "theme",
1051 "append_filename",
1052 "txtsuffix",
1053 ] {
1054 assert!(cfg.set_string(key, "test").is_ok(), "string {} should exist", key);
1055 }
1056 }
1057
1058 #[test]
1059 fn test_set_all_lists() {
1060 let mut cfg = Config::default();
1061 for key in &[
1062 "show_headers",
1063 "avoid_indices",
1064 "avoid_top_indices",
1065 "text_types",
1066 "inline_types",
1067 "prefered_types",
1068 "ignore_types",
1069 "filter_out",
1070 "filter_require",
1071 "filter_out_full_body",
1072 "filter_require_full_body",
1073 "deleted",
1074 "expires",
1075 "delete_msgnum",
1076 ] {
1077 assert!(cfg.set_list(key, "test").is_ok(), "list {} should exist", key);
1078 }
1079 }
1080
1081 #[test]
1082 fn test_config_file_content_can_parse() {
1083 let config_content = "\
1084# comment
1085set language=de
1086hm_overwrite=On
1087nonsequential On
1088dirmode 0755
1089mbox mailbox/test
1090label \"My Archive\"
1091";
1092 let mut cfg = Config::default();
1094 for line in config_content.lines() {
1095 let line = line.trim();
1096 if line.is_empty() || line.starts_with('#') {
1097 continue;
1098 }
1099 let line = line.strip_prefix("set ").unwrap_or(line);
1100 let eq_pos = line.find('=').or_else(|| line.find(':'));
1101 if let Some(eq_pos) = eq_pos {
1102 let key = line[..eq_pos].trim();
1103 let val = line[eq_pos + 1..].trim();
1104 let val = val.trim_matches('"');
1105 cfg.apply_cli_arg(key, val).unwrap_or_else(|e| {
1106 panic!("Failed to parse line '{}': {e}", line);
1107 });
1108 }
1109 }
1110 assert_eq!(cfg.language, "de");
1111 assert!(cfg.overwrite);
1112 }
1113
1114 #[test]
1115 fn test_antispamdomain_roundtrip() {
1116 let mut cfg = Config::default();
1117 cfg.set_string("antispamdomain", "nospam.invalid").unwrap();
1118 assert_eq!(cfg.antispamdomain.as_deref(), Some("nospam.invalid"));
1119 }
1120
1121 #[test]
1122 fn test_antispamdomain_none_on_empty() {
1123 let mut cfg = Config::default();
1124 cfg.set_string("antispamdomain", "").unwrap();
1125 assert!(cfg.antispamdomain.is_none());
1126 }
1127
1128 #[test]
1129 fn test_antispamdomain_none_on_keyword() {
1130 let mut cfg = Config::default();
1131 cfg.set_string("antispamdomain", "NONE").unwrap();
1132 assert!(cfg.antispamdomain.is_none());
1133 }
1134}