Skip to main content

hypermail/
link.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3
4use crate::message::BodyChain;
5use crate::structs::EmailStore;
6
7#[allow(dead_code)]
8static MSGID_QUOTE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^On\s.*\n?\s*wrote:\s*$").unwrap());
9
10#[allow(dead_code)]
11static EMAIL_QUOTE_RE: Lazy<Regex> =
12    Lazy::new(|| Regex::new(r"<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>").unwrap());
13
14pub fn link_quotes(store: &mut EmailStore, ignore_types: bool, linkquotes: bool) {
15    if !linkquotes {
16        return;
17    }
18
19    let msgids: Vec<Option<String>> = store.emails.iter().map(|e| e.msgid.clone()).collect();
20
21    for i in 0..store.emails.len() {
22        let body_chain = &store.emails[i].bodylist;
23
24        if !ignore_types {
25            let from_text = collect_body_text(body_chain);
26            for (j, other_msgid) in msgids.iter().enumerate() {
27                if i == j || other_msgid.is_none() {
28                    continue;
29                }
30                if let Some(ref mid) = other_msgid {
31                    if from_text.contains(mid) {
32                        let email_info = &store.emails[i];
33                        let from_msgnum = store.emails[j].msgnum;
34                        let to_msgnum = email_info.msgnum;
35                        if from_msgnum != to_msgnum {
36                            store.replylist.push(crate::message::Reply {
37                                from_msgnum,
38                                msgnum: to_msgnum,
39                                data: None,
40                                maybe_reply: 1,
41                            });
42                        }
43                    }
44                }
45            }
46        }
47    }
48}
49
50fn collect_body_text(body_chain: &BodyChain) -> String {
51    let mut text = String::new();
52    for body in &body_chain.bodies {
53        if !body.attached && !body.header {
54            text.push_str(&body.line);
55            text.push(' ');
56        }
57    }
58    text
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::message::{Body, BodyChain, EmailInfo};
65
66    #[test]
67    fn test_link_quotes_empty_store() {
68        let mut store = EmailStore::new();
69        link_quotes(&mut store, false, true);
70        assert!(store.replylist.is_empty());
71    }
72
73    #[test]
74    fn test_link_quotes_disabled() {
75        let mut store = EmailStore::new();
76        let e1 = EmailInfo {
77            msgnum: 1,
78            msgid: Some("<a@b>".to_string()),
79            bodylist: BodyChain {
80                bodies: vec![Body {
81                    line: "reply to <a@b>".to_string(),
82                    html: false,
83                    header: false,
84                    parsed_header: false,
85                    attached: false,
86                    demimed: false,
87                    msgnum: 0,
88                }],
89            },
90            ..Default::default()
91        };
92        let e2 = EmailInfo {
93            msgnum: 2,
94            msgid: Some("<b@c>".to_string()),
95            bodylist: BodyChain { bodies: Vec::new() },
96            ..Default::default()
97        };
98        store.add_email(e1);
99        store.add_email(e2);
100        link_quotes(&mut store, false, false);
101        assert!(store.replylist.is_empty());
102    }
103
104    #[test]
105    fn test_collect_body_text() {
106        let chain = BodyChain {
107            bodies: vec![
108                Body {
109                    line: "hello".to_string(),
110                    html: false,
111                    header: false,
112                    parsed_header: false,
113                    attached: false,
114                    demimed: false,
115                    msgnum: 0,
116                },
117                Body {
118                    line: "world".to_string(),
119                    html: false,
120                    header: false,
121                    parsed_header: false,
122                    attached: true,
123                    demimed: false,
124                    msgnum: 0,
125                },
126            ],
127        };
128        let text = collect_body_text(&chain);
129        assert!(!text.contains("world"));
130        assert!(text.contains("hello"));
131    }
132}