summaryrefslogtreecommitdiffstats
path: root/src/regex.rs
blob: 1dce30da73567787bd5b2428a14103ead21720b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2019 Alexandros Frantzis
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// SPDX-License-Identifier: MPL-2.0

//! Convenience functionality for regex searches of email data.

use std::str;

use regex::bytes::{RegexBuilder, RegexSetBuilder, SetMatches, Captures};

use crate::Result;

/// Trait providing convenience methods for regular expression searching
/// in emails. The trait methods can be use with the byte data returned by
/// the `Email::header`, `Email::body` and `Email::data` methods.
///
/// This trait treats and searches the email contents as bytes. The regular
/// expression parsing is configured for case-insensitive and multi-line
/// search (i.e., `^` and `$` match beginning and end of lines respectively).
///
/// In addition to the single regular expression searching, a method for
/// matching regular expression sets is provided. This can be more
/// efficient than matching multiple regular expressions independently.
///
/// All the trait methods will fail if the regular expression is
/// invalid, or the searched email data isn't valid utf-8.
pub trait EmailRegex {
    /// Returns whether the contents match a regular expression.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use mda::{Email, EmailRegex};
    /// let email = Email::from_stdin()?;
    /// if email.header().search(r"^To:.*me@example.com")? {
    ///     email.deliver_to_maildir("/my/maildir/path")?;
    /// }
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    fn search(&self, regex: &str) -> Result<bool>;

    /// Returns the capture groups matched from a regular expression.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use std::path::Path;
    /// use mda::{Email, EmailRegex};
    /// let email = Email::from_stdin()?;
    /// if let Some(captures) = email.header().search_with_captures(r"^X-Product: name=(\w+)")? {
    ///     let name = std::str::from_utf8(captures.get(1).unwrap().as_bytes()).unwrap();
    ///     email.deliver_to_maildir(Path::new("/my/maildir/").join(name))?;
    /// }
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    fn search_with_captures(&self, regex: &str) -> Result<Option<Captures>>;

    /// Returns the matches from a set of regular expression. This can be
    /// more efficient than matching multiple regular expressions independently.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use mda::{Email, EmailRegex};
    /// let email = Email::from_stdin()?;
    /// let matched_sets = email.header().search_set(
    ///     &[
    ///         r"^To: confidential <confidential@example.com>",
    ///         r"^X-Confidential: true",
    ///     ]
    /// )?;
    /// if matched_sets.matched_any() {
    ///     email.deliver_to_maildir("/my/mail/confidential/")?;
    /// }
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    fn search_set(&self, regex_set: &[&str]) -> Result<SetMatches>;
}

impl EmailRegex for &[u8] {
    fn search(&self, regex: &str) -> Result<bool> {
        Ok(
            RegexBuilder::new(regex)
                .multi_line(true)
                .case_insensitive(true)
                .build()?
                .is_match(self)
        )
    }

    fn search_with_captures(&self, regex: &str) -> Result<Option<Captures>> {
        Ok(
            RegexBuilder::new(regex)
                .multi_line(true)
                .case_insensitive(true)
                .build()?
                .captures(self)
        )
    }

    fn search_set(&self, regex_set: &[&str]) -> Result<SetMatches> {
        Ok(
            RegexSetBuilder::new(regex_set)
                .multi_line(true)
                .case_insensitive(true)
                .build()?
                .matches(self)
        )
    }
}