Skip to main content

server/
server.rs

1use imap_codec::{
2    AuthenticateDataCodec, CommandCodec, IdleDoneCodec,
3    fragmentizer::{FragmentInfo, Fragmentizer, LiteralAnnouncement},
4};
5
6#[path = "common/common.rs"]
7mod common;
8
9use common::{COLOR_SERVER, RESET, read_more};
10use imap_types::{
11    IntoStatic,
12    command::{Command, CommandBody},
13    core::{LiteralMode, Tag},
14};
15
16use crate::common::Role;
17
18enum State {
19    Command,
20    Authenticate(Tag<'static>),
21    Idle,
22}
23
24const WELCOME: &str = r#"# Parsing of IMAP commands
25
26"C:" denotes the client,
27"S:" denotes the server, and
28".." denotes the continuation of an (incomplete) command, e.g., due to the use of an IMAP literal.
29
30Note: "\n" will be automatically replaced by "\r\n".
31
32--------------------------------------------------------------------------------------------------
33
34Enter IMAP commands (or "exit").
35"#;
36
37fn main() {
38    println!("{WELCOME}");
39
40    let mut fragmentizer = Fragmentizer::new(10 * 1024);
41    let mut state = State::Command;
42
43    // Send a greeting.
44    println!("S: {COLOR_SERVER}* OK ...{RESET}");
45
46    loop {
47        // Progress next fragment.
48        let Some(fragment_info) = fragmentizer.progress() else {
49            // Read more bytes ...
50            let bytes = read_more(Role::Client, fragmentizer.message_bytes().is_empty());
51
52            // ... and pass the bytes to the Fragmentizer ...
53            fragmentizer.enqueue_bytes(&bytes);
54
55            // ... and try again.
56            continue;
57        };
58
59        // The Fragmentizer detected a line that announces a sync literal.
60        if let FragmentInfo::Line {
61            announcement:
62                Some(LiteralAnnouncement {
63                    mode: LiteralMode::Sync,
64                    length,
65                }),
66            ..
67        } = fragment_info
68        {
69            // Check the length of the literal.
70            if length <= 1024 {
71                // Accept the literal ...
72                println!("S: {COLOR_SERVER}+ {RESET}");
73
74                // ... and continue with the remaining message.
75                continue;
76            } else if let Some(tag) = fragmentizer.decode_tag() {
77                // Reject the literal ...
78                println!("S: {COLOR_SERVER}{} BAD ...{RESET}", tag.as_ref());
79
80                // ... and skip the current message ...
81                fragmentizer.skip_message();
82
83                // ... and continue with the next message.
84                continue;
85            } else {
86                // The partially received message is malformed. It's unclear what will follow.
87                // To be on the safe side, prevent the message from being decoded ...
88                fragmentizer.poison_message();
89
90                // ... but continue parsing the message.
91                continue;
92            }
93        }
94
95        // Check whether the Fragmentizer detected a complete message.
96        if !fragmentizer.is_message_complete() {
97            // Read next fragment.
98            continue;
99        }
100
101        // The Fragmentizer detected a complete message.
102        match state {
103            State::Command => {
104                match fragmentizer.decode_message(&CommandCodec::default()) {
105                    Ok(Command {
106                        tag,
107                        body: CommandBody::Authenticate { .. },
108                    }) => {
109                        // Request another SASL round ...
110                        println!("S: {COLOR_SERVER}+ {RESET}");
111
112                        // ... and proceed with authenticate data.
113                        state = State::Authenticate(tag.into_static());
114                    }
115                    Ok(Command {
116                        body: CommandBody::Idle,
117                        ..
118                    }) => {
119                        // Accept the idle ...
120                        println!("S: {COLOR_SERVER}+ ...{RESET}");
121
122                        // ... and proceed with idle done.
123                        state = State::Idle;
124                    }
125                    Ok(command) => {
126                        // Do something with the command.
127                        println!("{command:#?}");
128                    }
129                    Err(err) => {
130                        println!("Error parsing command: {err:?}");
131                    }
132                };
133            }
134            State::Authenticate(ref tag) => {
135                match fragmentizer.decode_message(&AuthenticateDataCodec::default()) {
136                    Ok(_authenticate_data) => {
137                        // Accept the authentication after one SASL round.
138                        println!("S: {COLOR_SERVER}{} OK ...{RESET}", tag.as_ref());
139
140                        // ... and proceed with commands.
141                        state = State::Command;
142                    }
143                    Err(err) => {
144                        println!("Error parsing authenticate data: {err:?}");
145                    }
146                };
147            }
148            State::Idle => {
149                match fragmentizer.decode_message(&IdleDoneCodec::default()) {
150                    Ok(_idle_done) => {
151                        // End idle and proceed with commands.
152                        state = State::Command;
153                    }
154                    Err(err) => {
155                        println!("Error parsing idle done: {err:?}");
156                    }
157                };
158            }
159        }
160    }
161}