1 use super::commands::{ImapCommand, ImapCommandHandler, ImapResponse};
2 use super::mailbox::MailboxStore;
3 use super::state::ImapState;
4 use tokio::io::{AsyncRead, AsyncWrite};
5 use tokio_util::codec::{Framed, LinesCodec};
6 use futures::StreamExt;
7 use futures::SinkExt;
8
9 pub struct ImapSession {
10 state: ImapState,
11 handler: ImapCommandHandler,
12 }
13
14 impl ImapSession {
15 pub fn new(store: MailboxStore) -> Self {
16 ImapSession {
17 state: ImapState::NotAuthenticated,
18 handler: ImapCommandHandler::new(store),
19 }
20 }
21
22 pub async fn handle<S>(&mut self, stream: S) -> anyhow::Result<()>
23 where
24 S: AsyncRead + AsyncWrite + Unpin,
25 {
26 let mut framed = Framed::new(stream, LinesCodec::new());
27
28 // Send greeting
29 framed.send("* OK IMAP4rev1 Server Ready".to_string()).await?;
30
31 while let Some(line_result) = framed.next().await {
32 let line = line_result?;
33
34 tracing::debug!("IMAP received: {}", line);
35
36 // Parse command
37 let cmd = match ImapCommand::parse(&line) {
38 Ok(cmd) => cmd,
39 Err(e) => {
40 let response = ImapResponse::bad("*".to_string(), format!("Parse error: {}", e));
41 framed.send(response.format().trim_end().to_string()).await?;
42 continue;
43 }
44 };
45
46 // Handle command
47 let responses = self.handle_command(&cmd);
48
49 // Send responses
50 for response in responses {
51 let formatted = response.format();
52 tracing::debug!("IMAP sending: {}", formatted.trim_end());
53 framed.send(formatted.trim_end().to_string()).await?;
54 }
55
56 // Check if we should logout
57 if self.state == ImapState::Logout {
58 break;
59 }
60 }
61
62 Ok(())
63 }
64
65 fn handle_command(&mut self, cmd: &ImapCommand) -> Vec<ImapResponse> {
66 match cmd.command.as_str() {
67 "CAPABILITY" => self.handler.handle_capability(cmd),
68 "LOGIN" => self.handler.handle_login(cmd, &mut self.state),
69 "LIST" | "LSUB" => self.handler.handle_list(cmd, &self.state),
70 "SELECT" => self.handler.handle_select(cmd, &mut self.state, false),
71 "EXAMINE" => self.handler.handle_examine(cmd, &mut self.state),
72 "FETCH" => self.handler.handle_fetch(cmd, &self.state),
73 "SEARCH" => self.handler.handle_search(cmd, &self.state),
74 "STATUS" => self.handler.handle_status(cmd, &self.state),
75 "LOGOUT" => self.handler.handle_logout(cmd, &mut self.state),
76 "NOOP" => self.handler.handle_noop(cmd),
77 _ => vec![ImapResponse::bad(
78 cmd.tag.clone(),
79 format!("Unknown command: {}", cmd.command),
80 )],
81 }
82 }
83 }