1 use super::mailbox::MailboxStore;
2 use super::state::ImapState;
3
4 pub struct ImapCommand {
5 pub tag: String,
6 pub command: String,
7 pub args: Vec<String>,
8 }
9
10 impl ImapCommand {
11 pub fn parse(line: &str) -> Result<Self, String> {
12 let parts: Vec<&str> = line.split_whitespace().collect();
13
14 if parts.len() < 2 {
15 return Err("Invalid command format".to_string());
16 }
17
18 let tag = parts[0].to_string();
19 let command = parts[1].to_uppercase();
20 let args = parts[2..].iter().map(|s| s.to_string()).collect();
21
22 Ok(ImapCommand { tag, command, args })
23 }
24 }
25
26 pub struct ImapResponse {
27 pub tag: Option<String>,
28 pub status: String,
29 pub text: String,
30 }
31
32 impl ImapResponse {
33 pub fn ok(tag: String, text: String) -> Self {
34 ImapResponse {
35 tag: Some(tag),
36 status: "OK".to_string(),
37 text,
38 }
39 }
40
41 pub fn no(tag: String, text: String) -> Self {
42 ImapResponse {
43 tag: Some(tag),
44 status: "NO".to_string(),
45 text,
46 }
47 }
48
49 pub fn bad(tag: String, text: String) -> Self {
50 ImapResponse {
51 tag: Some(tag),
52 status: "BAD".to_string(),
53 text,
54 }
55 }
56
57 pub fn untagged(text: String) -> Self {
58 ImapResponse {
59 tag: None,
60 status: "*".to_string(),
61 text,
62 }
63 }
64
65 pub fn format(&self) -> String {
66 if let Some(ref tag) = self.tag {
67 format!("{} {} {}\r\n", tag, self.status, self.text)
68 } else {
69 format!("{} {}\r\n", self.status, self.text)
70 }
71 }
72 }
73
74 pub struct ImapCommandHandler {
75 pub store: MailboxStore,
76 }
77
78 impl ImapCommandHandler {
79 pub fn new(store: MailboxStore) -> Self {
80 ImapCommandHandler { store }
81 }
82
83 pub fn handle_capability(&self, cmd: &ImapCommand) -> Vec<ImapResponse> {
84 vec![
85 ImapResponse::untagged("CAPABILITY IMAP4rev1 AUTH=PLAIN".to_string()),
86 ImapResponse::ok(cmd.tag.clone(), "CAPABILITY completed".to_string()),
87 ]
88 }
89
90 pub fn handle_login(&self, cmd: &ImapCommand, state: &mut ImapState) -> Vec<ImapResponse> {
91 if cmd.args.len() < 2 {
92 return vec![ImapResponse::bad(cmd.tag.clone(), "Invalid LOGIN command".to_string())];
93 }
94
95 // Simple authentication - accept any username/password for demo
96 *state = ImapState::Authenticated;
97
98 vec![ImapResponse::ok(cmd.tag.clone(), "LOGIN completed".to_string())]
99 }
100
101 pub fn handle_list(&self, cmd: &ImapCommand, state: &ImapState) -> Vec<ImapResponse> {
102 if !state.is_authenticated() {
103 return vec![ImapResponse::no(cmd.tag.clone(), "Not authenticated".to_string())];
104 }
105
106 let mut responses = Vec::new();
107 let mailboxes = self.store.list_mailboxes();
108
109 for mailbox in mailboxes {
110 responses.push(ImapResponse::untagged(
111 format!("LIST () \"/\" \"{}\"", mailbox)
112 ));
113 }
114
115 responses.push(ImapResponse::ok(cmd.tag.clone(), "LIST completed".to_string()));
116 responses
117 }
118
119 pub fn handle_select(&self, cmd: &ImapCommand, state: &mut ImapState, readonly: bool) -> Vec<ImapResponse> {
120 if !state.is_authenticated() {
121 return vec![ImapResponse::no(cmd.tag.clone(), "Not authenticated".to_string())];
122 }
123
124 if cmd.args.is_empty() {
125 return vec![ImapResponse::bad(cmd.tag.clone(), "No mailbox specified".to_string())];
126 }
127
128 let mailbox_name = &cmd.args[0];
129
130 if let Some(mailbox) = self.store.get_mailbox(mailbox_name) {
131 let mut responses = Vec::new();
132
133 responses.push(ImapResponse::untagged(format!("{} EXISTS", mailbox.exists())));
134 responses.push(ImapResponse::untagged(format!("{} RECENT", mailbox.recent())));
135 responses.push(ImapResponse::untagged(
136 format!("OK [UNSEEN {}] Message {} is first unseen", mailbox.unseen(), mailbox.unseen())
137 ));
138 responses.push(ImapResponse::untagged(
139 format!("OK [UIDVALIDITY {}] UIDs valid", mailbox.uidvalidity())
140 ));
141 responses.push(ImapResponse::untagged(
142 format!("OK [UIDNEXT {}] Predicted next UID", mailbox.uidnext())
143 ));
144 responses.push(ImapResponse::untagged(
145 "FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)".to_string()
146 ));
147
148 *state = ImapState::Selected(mailbox.clone());
149
150 let mode = if readonly { "[READ-ONLY]" } else { "[READ-WRITE]" };
151 responses.push(ImapResponse::ok(
152 cmd.tag.clone(),
153 format!("{} SELECT completed", mode)
154 ));
155
156 responses
157 } else {
158 vec![ImapResponse::no(cmd.tag.clone(), "Mailbox does not exist".to_string())]
159 }
160 }
161
162 pub fn handle_examine(&self, cmd: &ImapCommand, state: &mut ImapState) -> Vec<ImapResponse> {
163 self.handle_select(cmd, state, true)
164 }
165
166 pub fn handle_fetch(&self, cmd: &ImapCommand, state: &ImapState) -> Vec<ImapResponse> {
167 if !state.is_selected() {
168 return vec![ImapResponse::no(cmd.tag.clone(), "No mailbox selected".to_string())];
169 }
170
171 if cmd.args.len() < 2 {
172 return vec![ImapResponse::bad(cmd.tag.clone(), "Invalid FETCH command".to_string())];
173 }
174
175 let mailbox = state.get_selected_mailbox().unwrap();
176 let sequence_set = &cmd.args[0];
177 let data_items = cmd.args[1..].join(" ");
178
179 let mut responses = Vec::new();
180 let messages = mailbox.get_messages();
181
182 // Parse sequence set (simplified - only handles single numbers or ranges)
183 let sequences = parse_sequence_set(sequence_set, messages.len() as u32);
184
185 for seq in sequences {
186 if let Some(message) = mailbox.get_message_by_sequence(seq) {
187 let fetch_data = build_fetch_response(&message, &data_items);
188 responses.push(ImapResponse::untagged(
189 format!("{} FETCH {}", seq, fetch_data)
190 ));
191 }
192 }
193
194 responses.push(ImapResponse::ok(cmd.tag.clone(), "FETCH completed".to_string()));
195 responses
196 }
197
198 pub fn handle_search(&self, cmd: &ImapCommand, state: &ImapState) -> Vec<ImapResponse> {
199 if !state.is_selected() {
200 return vec![ImapResponse::no(cmd.tag.clone(), "No mailbox selected".to_string())];
201 }
202
203 let mailbox = state.get_selected_mailbox().unwrap();
204 let messages = mailbox.get_messages();
205
206 // Simplified SEARCH - for demo, return all messages
207 let sequence_numbers: Vec<String> = messages.iter()
208 .map(|m| m.sequence_number.to_string())
209 .collect();
210
211 let mut responses = Vec::new();
212 responses.push(ImapResponse::untagged(
213 format!("SEARCH {}", sequence_numbers.join(" "))
214 ));
215 responses.push(ImapResponse::ok(cmd.tag.clone(), "SEARCH completed".to_string()));
216
217 responses
218 }
219
220 pub fn handle_status(&self, cmd: &ImapCommand, state: &ImapState) -> Vec<ImapResponse> {
221 if !state.is_authenticated() {
222 return vec![ImapResponse::no(cmd.tag.clone(), "Not authenticated".to_string())];
223 }
224
225 if cmd.args.is_empty() {
226 return vec![ImapResponse::bad(cmd.tag.clone(), "No mailbox specified".to_string())];
227 }
228
229 let mailbox_name = &cmd.args[0];
230
231 if let Some(mailbox) = self.store.get_mailbox(mailbox_name) {
232 let status_items = format!(
233 "MESSAGES {} RECENT {} UNSEEN {} UIDNEXT {} UIDVALIDITY {}",
234 mailbox.exists(),
235 mailbox.recent(),
236 mailbox.unseen(),
237 mailbox.uidnext(),
238 mailbox.uidvalidity()
239 );
240
241 vec![
242 ImapResponse::untagged(format!("STATUS \"{}\" ({})", mailbox_name, status_items)),
243 ImapResponse::ok(cmd.tag.clone(), "STATUS completed".to_string()),
244 ]
245 } else {
246 vec![ImapResponse::no(cmd.tag.clone(), "Mailbox does not exist".to_string())]
247 }
248 }
249
250 pub fn handle_logout(&self, cmd: &ImapCommand, state: &mut ImapState) -> Vec<ImapResponse> {
251 *state = ImapState::Logout;
252
253 vec![
254 ImapResponse::untagged("BYE IMAP4rev1 Server logging out".to_string()),
255 ImapResponse::ok(cmd.tag.clone(), "LOGOUT completed".to_string()),
256 ]
257 }
258
259 pub fn handle_noop(&self, cmd: &ImapCommand) -> Vec<ImapResponse> {
260 vec![ImapResponse::ok(cmd.tag.clone(), "NOOP completed".to_string())]
261 }
262 }
263
264 fn parse_sequence_set(sequence_set: &str, max: u32) -> Vec<u32> {
265 let mut sequences = Vec::new();
266
267 // Handle "*" (all messages)
268 if sequence_set == "*" {
269 sequences.push(max);
270 return sequences;
271 }
272
273 // Handle ranges like "1:5" or single numbers like "3"
274 if sequence_set.contains(':') {
275 let parts: Vec<&str> = sequence_set.split(':').collect();
276 if parts.len() == 2 {
277 let start = if parts[0] == "*" {
278 max
279 } else {
280 parts[0].parse::<u32>().unwrap_or(1)
281 };
282
283 let end = if parts[1] == "*" {
284 max
285 } else {
286 parts[1].parse::<u32>().unwrap_or(max)
287 };
288
289 for i in start..=end.min(max) {
290 sequences.push(i);
291 }
292 }
293 } else if let Ok(seq) = sequence_set.parse::<u32>() {
294 sequences.push(seq);
295 }
296
297 sequences
298 }
299
300 fn build_fetch_response(message: &super::message::Message, data_items: &str) -> String {
301 let data_items_upper = data_items.to_uppercase();
302 let mut parts = Vec::new();
303
304 if data_items_upper.contains("FLAGS") {
305 parts.push(format!("FLAGS {}", message.flags_string()));
306 }
307
308 if data_items_upper.contains("UID") {
309 parts.push(format!("UID {}", message.uid));
310 }
311
312 if data_items_upper.contains("RFC822.SIZE") || data_items_upper.contains("BODY") {
313 parts.push(format!("RFC822.SIZE {}", message.raw_content.len()));
314 }
315
316 if data_items_upper.contains("BODY[]") || data_items_upper.contains("RFC822") {
317 parts.push(format!("BODY[] {{{}}}\r\n{}", message.raw_content.len(), message.raw_content));
318 } else if data_items_upper.contains("BODY[HEADER]") {
319 let header = extract_header(&message.raw_content);
320 parts.push(format!("BODY[HEADER] {{{}}}\r\n{}", header.len(), header));
321 } else if data_items_upper.contains("BODY[TEXT]") {
322 parts.push(format!("BODY[TEXT] {{{}}}\r\n{}", message.body.len(), message.body));
323 }
324
325 if data_items_upper.contains("ENVELOPE") {
326 let envelope = build_envelope(message);
327 parts.push(format!("ENVELOPE {}", envelope));
328 }
329
330 format!("({})", parts.join(" "))
331 }
332
333 fn extract_header(content: &str) -> String {
334 let mut header = String::new();
335 for line in content.lines() {
336 if line.trim().is_empty() {
337 break;
338 }
339 header.push_str(line);
340 header.push_str("\r\n");
341 }
342 header
343 }
344
345 fn build_envelope(message: &super::message::Message) -> String {
346 // Simplified envelope structure
347 format!(
348 "(\"{}\" \"{}\" ((NIL NIL \"{}\" NIL)) ((NIL NIL \"{}\" NIL)) ((NIL NIL \"{}\" NIL)) ((NIL NIL \"{}\" NIL)) NIL NIL NIL NIL)",
349 "", // date
350 message.subject,
351 message.from,
352 message.from,
353 message.from,
354 message.to.first().unwrap_or(&"".to_string())
355 )
356 }