Skip to main content

rusmes_imap/
handler.rs

1//! IMAP command handler dispatcher
2//!
3//! This module contains the top-level [`HandlerContext`] and the [`handle_command`]
4//! dispatcher. Actual implementations live in sub-modules:
5//!
6//! - [`crate::handler_auth`]    – LOGIN, LOGOUT
7//! - [`crate::handler_mailbox`] – SELECT/EXAMINE, LIST, LSUB, SUBSCRIBE/UNSUBSCRIBE,
8//!   CREATE, DELETE, RENAME, IDLE, NAMESPACE
9//! - [`crate::handler_message`] – FETCH, STORE, SEARCH, APPEND, COPY, MOVE, EXPUNGE,
10//!   CLOSE, UID sub-commands
11
12use crate::command::ImapCommand;
13use crate::handler_auth::{handle_login, handle_logout};
14use crate::handler_mailbox::{
15    handle_create, handle_create_special_use, handle_delete, handle_idle, handle_list, handle_lsub,
16    handle_namespace, handle_rename, handle_select, handle_subscribe, handle_unsubscribe,
17};
18use crate::handler_message::{
19    handle_append, handle_close, handle_copy, handle_expunge, handle_fetch, handle_move,
20    handle_search, handle_store, handle_uid,
21};
22use crate::response::ImapResponse;
23use crate::session::ImapSession;
24use rusmes_auth::AuthBackend;
25use rusmes_storage::{MailboxStore, MessageStore, MetadataStore};
26use std::sync::Arc;
27
28/// Handler context for IMAP commands
29pub struct HandlerContext {
30    pub mailbox_store: Arc<dyn MailboxStore>,
31    pub message_store: Arc<dyn MessageStore>,
32    pub metadata_store: Arc<dyn MetadataStore>,
33    pub auth_backend: Arc<dyn AuthBackend>,
34}
35
36impl HandlerContext {
37    /// Create a new handler context
38    pub fn new(
39        mailbox_store: Arc<dyn MailboxStore>,
40        message_store: Arc<dyn MessageStore>,
41        metadata_store: Arc<dyn MetadataStore>,
42        auth_backend: Arc<dyn AuthBackend>,
43    ) -> Self {
44        Self {
45            mailbox_store,
46            message_store,
47            metadata_store,
48            auth_backend,
49        }
50    }
51}
52
53/// Handle an IMAP command — dispatches to the appropriate sub-handler
54#[allow(clippy::too_many_arguments)]
55pub async fn handle_command(
56    ctx: &HandlerContext,
57    session: &mut ImapSession,
58    tag: &str,
59    command: ImapCommand,
60) -> anyhow::Result<ImapResponse> {
61    match command {
62        ImapCommand::Capability => handle_capability(tag).await,
63        ImapCommand::Noop => handle_noop(tag).await,
64        ImapCommand::Login { user, password } => {
65            handle_login(ctx, session, tag, &user, &password).await
66        }
67        ImapCommand::Authenticate {
68            mechanism,
69            initial_response,
70        } => {
71            // Note: This is a placeholder. AUTHENTICATE requires special handling
72            // in the server loop because it's a multi-step process.
73            // The actual implementation is in crate::authenticate module.
74            let _ = (mechanism, initial_response);
75            Ok(ImapResponse::bad(
76                tag,
77                "AUTHENTICATE must be handled by server loop",
78            ))
79        }
80        ImapCommand::Logout => handle_logout(session, tag).await,
81        ImapCommand::Select { mailbox } => handle_select(ctx, session, tag, &mailbox, false).await,
82        ImapCommand::Examine { mailbox } => handle_select(ctx, session, tag, &mailbox, true).await,
83        ImapCommand::Fetch { sequence, items } => {
84            handle_fetch(ctx, session, tag, &sequence, &items).await
85        }
86        ImapCommand::Store {
87            sequence,
88            mode,
89            flags,
90        } => handle_store(ctx, session, tag, &sequence, mode, &flags).await,
91        ImapCommand::Search { criteria } => handle_search(ctx, session, tag, &criteria).await,
92        ImapCommand::List { reference, mailbox } => {
93            handle_list(ctx, session, tag, &reference, &mailbox).await
94        }
95        ImapCommand::Lsub { reference, mailbox } => {
96            handle_lsub(ctx, session, tag, &reference, &mailbox).await
97        }
98        ImapCommand::Subscribe { mailbox } => handle_subscribe(ctx, session, tag, &mailbox).await,
99        ImapCommand::Unsubscribe { mailbox } => {
100            handle_unsubscribe(ctx, session, tag, &mailbox).await
101        }
102        ImapCommand::Create { mailbox } => handle_create(ctx, session, tag, &mailbox).await,
103        ImapCommand::CreateSpecialUse {
104            mailbox,
105            special_use,
106        } => handle_create_special_use(ctx, session, tag, &mailbox, &special_use).await,
107        ImapCommand::Delete { mailbox } => handle_delete(ctx, session, tag, &mailbox).await,
108        ImapCommand::Rename { old, new } => handle_rename(ctx, session, tag, &old, &new).await,
109        ImapCommand::Append {
110            mailbox,
111            flags,
112            date_time,
113            message_literal,
114        } => {
115            handle_append(
116                ctx,
117                session,
118                tag,
119                &mailbox,
120                &flags,
121                date_time.as_deref(),
122                &message_literal,
123            )
124            .await
125        }
126        ImapCommand::Copy { sequence, mailbox } => {
127            handle_copy(ctx, session, tag, &sequence, &mailbox).await
128        }
129        ImapCommand::Move { sequence, mailbox } => {
130            handle_move(ctx, session, tag, &sequence, &mailbox).await
131        }
132        ImapCommand::Expunge => handle_expunge(ctx, session, tag).await,
133        ImapCommand::Close => handle_close(ctx, session, tag).await,
134        ImapCommand::Idle => handle_idle(ctx, session, tag).await,
135        ImapCommand::Namespace => handle_namespace(tag, session).await,
136        ImapCommand::Uid { subcommand } => handle_uid(ctx, session, tag, subcommand.as_ref()).await,
137    }
138}
139
140/// Handle CAPABILITY command
141async fn handle_capability(tag: &str) -> anyhow::Result<ImapResponse> {
142    // Return basic IMAP4rev1 capabilities
143    let capabilities = vec![
144        "IMAP4rev1",
145        "LITERAL+",
146        "SASL-IR",
147        "LOGIN-REFERRALS",
148        "ID",
149        "ENABLE",
150        "IDLE",
151        "NAMESPACE",
152        "UIDPLUS",
153        "LIST-EXTENDED",
154        "UNSELECT",
155        "CHILDREN",
156        "SPECIAL-USE",
157        "MOVE",
158        // SASL authentication mechanisms (RFC 3501 Section 6.2.2)
159        "AUTH=PLAIN",
160        "AUTH=LOGIN",
161        "AUTH=CRAM-MD5",
162        "AUTH=SCRAM-SHA-256",
163        "AUTH=XOAUTH2",
164    ];
165
166    let cap_list = capabilities.join(" ");
167    Ok(ImapResponse::ok(
168        tag,
169        format!("[CAPABILITY {}] Capability completed", cap_list),
170    ))
171}
172
173/// Handle NOOP command
174async fn handle_noop(tag: &str) -> anyhow::Result<ImapResponse> {
175    Ok(ImapResponse::ok(tag, "NOOP completed"))
176}