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
//! The IMAP COMPRESS Extension

// Additional changes:
//
// command-auth   =/ compress
// capability     =/ "COMPRESS=" algorithm
// resp-text-code =/ "COMPRESSIONACTIVE"

use std::io::Write;

/// Re-export everything from imap-types.
pub use imap_types::extensions::compress::*;
use nom::{
    bytes::streaming::tag_no_case,
    combinator::{map, value},
    sequence::preceded,
};

use crate::{
    codec::{EncodeContext, Encoder, IMAPResult},
    command::CommandBody,
};

/// `algorithm = "DEFLATE"`
pub(crate) fn algorithm(input: &[u8]) -> IMAPResult<&[u8], CompressionAlgorithm> {
    value(CompressionAlgorithm::Deflate, tag_no_case("DEFLATE"))(input)
}

/// `compress = "COMPRESS" SP algorithm`
pub(crate) fn compress(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
    map(preceded(tag_no_case("COMPRESS "), algorithm), |algorithm| {
        CommandBody::Compress { algorithm }
    })(input)
}

impl Encoder for CompressionAlgorithm {
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
        match self {
            CompressionAlgorithm::Deflate => ctx.write_all(b"DEFLATE"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        command::{Command, CommandBody},
        testing::kat_inverse_command,
    };

    #[test]
    fn test_parse_compress() {
        let tests = [
            (
                b"compress deflate ".as_ref(),
                Ok((
                    b" ".as_ref(),
                    CommandBody::compress(CompressionAlgorithm::Deflate),
                )),
            ),
            (b"compress deflat ".as_ref(), Err(())),
            (b"compres deflate ".as_ref(), Err(())),
            (b"compress  deflate ".as_ref(), Err(())),
        ];

        for (test, expected) in tests {
            match expected {
                Ok((expected_rem, expected_object)) => {
                    let (got_rem, got_object) = compress(test).unwrap();
                    assert_eq!(expected_object, got_object);
                    assert_eq!(expected_rem, got_rem);
                }
                Err(_) => {
                    assert!(compress(test).is_err())
                }
            }
        }
    }

    #[test]
    fn test_kat_inverse_body_compress() {
        kat_inverse_command(&[
            (
                b"A COMPRESS DEFLATE\r\n".as_ref(),
                b"".as_ref(),
                Command::new("A", CommandBody::compress(CompressionAlgorithm::Deflate)).unwrap(),
            ),
            (
                b"A COMPRESS DEFLATE\r\n?".as_ref(),
                b"?".as_ref(),
                Command::new("A", CommandBody::compress(CompressionAlgorithm::Deflate)).unwrap(),
            ),
        ]);
    }
}