summaryrefslogtreecommitdiffstats
path: root/core/src/mime.rs
blob: 7897fe7df657d60234d51a485b7c71ad4cfa241c (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
115
116
117
118
119
120
121
//! Module containing some utilities for MIME usage/creation.
use rand::{self, Rng};



// The maximal boundary with wich " boundary=\"...\"" fits into 78 chars line length limit
const MULTIPART_BOUNDARY_MAX_LENGTH: usize = 66;

// Does not include ' ' to remove special handling for last char.
static BOUNDARY_CHARS: &[char] = &[
                                        '\'',
    '(', ')',      '+', ',', '-', '.', '/',
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', ':',           '=',      '?',
         'A', 'B', 'C', 'D', 'E', 'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
    'X', 'Y', 'Z',                     '_',
         'a', 'b', 'c', 'd', 'e', 'f', 'g',
    'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
    'x', 'y', 'z',
];

/// Prevent collisions with Base64/Quoted-Printable
static ANTI_COLLISION_CHARS: &str = "=_^";

/// Generate a boundary from a counter, "=_^" and a random sequence of boundary chars.
///
/// # Usage Note
///
/// _Be aware that it might be required to quote the boundary._
///
/// # Implementation Details
///
/// The boundary  will start with `=_^` which is neither valid for base64 nor
/// quoted-printable encoding followed by a hex repr. of the given count,
/// a `.` and a random sequence of boundary chars.
///
/// The boundary will be 66 chars long, this is so that if a boundary parameter is
/// placed on it's own line it won't be more then 78 chars. (66 chars boundary,
/// + 2 chars quotation + 9 chars for 'boundary=' + 1 char because of `\r\n<WS>`
/// == 78 chars)
///
/// The remaining characters will be picked based one the grammar defined in rfc2046,
/// which relevant part is:
///
/// ```BNF
/// boundary := 0*69<bchars> bcharsnospace
/// bchars := bcharsnospace / " "
/// bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
///                  "+" / "_" / "," / "-" / "." /
///                  "/" / ":" / "=" / "?"
/// ```
///
/// Note that `' '` isn't used for simplicity.
///
pub fn create_structured_random_boundary(count: usize) -> String {
    let mut out = format!("{anti_collision}{count:x}.",
        anti_collision=ANTI_COLLISION_CHARS,
        count=count
    );

    let rem = MULTIPART_BOUNDARY_MAX_LENGTH-out.len();
    out.reserve(rem);

    let mut rng = rand::thread_rng();
    let len = BOUNDARY_CHARS.len();
    for _ in 0..rem {
        let idx = rng.gen_range(0, len);
        out.push(BOUNDARY_CHARS[idx]);
    }

    out
}


#[cfg(test)]
mod test {

    mod write_random_boundary_to {
        use super::super::*;

        #[test]
        fn boundary_is_not_quoted() {
            let out = create_structured_random_boundary(0);
            assert!(!out.starts_with("\""));
            assert!(!out.ends_with("\""));
        }

        #[test]
        fn boundary_start_special() {
            let out = create_structured_random_boundary(0);
            assert!(out.starts_with("=_^0."));
        }

        #[test]
        fn boundary_has_a_resonable_length() {
            let out = create_structured_random_boundary(0);
            assert!(out.len() > 22 && out.len() <= MULTIPART_BOUNDARY_MAX_LENGTH);
            let out = create_structured_random_boundary(1000);
            assert!(out.len() > 22 && out.len() <= MULTIPART_BOUNDARY_MAX_LENGTH);
        }

        #[test]
        fn boundary_does_not_contain_space_or_slach_or_quotes() {
            // while it could contain them it's recommended not to do it
            let out = create_structured_random_boundary(0);

            for ch in out[1..out.len()-1].chars() {
                assert!(ch as u32 >= 32);
                assert!(ch as u32 <= 126);
                assert_ne!(ch, '\t');
                assert_ne!(ch, '\\');
                assert_ne!(ch, '"');
            }

            assert_ne!(out.as_bytes()[out.len()-1], b' ');
        }
    }
}