aboutsummaryrefslogtreecommitdiff
path: root/src/imap/session.rs
blob: 12bbfeec10cf18c406959496481e7d882d7e274c (plain) (blame)
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anonymous, authenticated, selected};
use crate::imap::flow;
use crate::imap::request::Request;
use crate::imap::response::{Response, ResponseOrIdle};
use crate::login::ArcLoginProvider;
use anyhow::{anyhow, bail, Result};
use imap_codec::imap_types::command::Command;

//-----
pub struct Instance {
    pub login_provider: ArcLoginProvider,
    pub server_capabilities: ServerCapability,
    pub client_capabilities: ClientCapability,
    pub state: flow::State,
}
impl Instance {
    pub fn new(login_provider: ArcLoginProvider, cap: ServerCapability) -> Self {
        let client_cap = ClientCapability::new(&cap);
        Self {
            login_provider,
            state: flow::State::NotAuthenticated,
            server_capabilities: cap,
            client_capabilities: client_cap,
        }
    }

    pub async fn request(&mut self, req: Request) -> ResponseOrIdle {
        match req {
            Request::Idle => self.idle().await,
            Request::ImapCommand(cmd) => self.command(cmd).await,
        }
    }

    pub async fn idle(&mut self) -> ResponseOrIdle {
        match self.idle_happy().await {
            Ok(r) => r,
            Err(e) => {
                tracing::error!(err=?e, "something bad happened in idle");
                ResponseOrIdle::Response(Response::bye().unwrap())
            }
        }
    }

    pub async fn idle_happy(&mut self) -> Result<ResponseOrIdle> {
        let (mbx, tag, stop) = match &mut self.state {
            flow::State::Idle(_, ref mut mbx, _, tag, stop) => (mbx, tag.clone(), stop.clone()),
            _ => bail!("Invalid session state, can't idle"),
        };

        tokio::select! {
            _ = stop.notified() => {
                self.state.apply(flow::Transition::UnIdle)?;
                return Ok(ResponseOrIdle::Response(Response::build()
                    .tag(tag.clone())
                    .message("IDLE completed")
                    .ok()?))
            },
            change = mbx.idle_sync() => {
                tracing::debug!("idle event");
                return Ok(ResponseOrIdle::IdleEvent(change?));
            }
        }
    }

    pub async fn command(&mut self, cmd: Command<'static>) -> ResponseOrIdle {
        // Command behavior is modulated by the state.
        // To prevent state error, we handle the same command in separate code paths.
        let (resp, tr) = match &mut self.state {
            flow::State::NotAuthenticated => {
                let ctx = anonymous::AnonymousContext {
                    req: &cmd,
                    login_provider: &self.login_provider,
                    server_capabilities: &self.server_capabilities,
                };
                anonymous::dispatch(ctx).await
            }
            flow::State::Authenticated(ref user) => {
                let ctx = authenticated::AuthenticatedContext {
                    req: &cmd,
                    server_capabilities: &self.server_capabilities,
                    client_capabilities: &mut self.client_capabilities,
                    user,
                };
                authenticated::dispatch(ctx).await
            }
            flow::State::Selected(ref user, ref mut mailbox, ref perm) => {
                let ctx = selected::SelectedContext {
                    req: &cmd,
                    server_capabilities: &self.server_capabilities,
                    client_capabilities: &mut self.client_capabilities,
                    user,
                    mailbox,
                    perm,
                };
                selected::dispatch(ctx).await
            }
            flow::State::Idle(..) => Err(anyhow!("can not receive command while idling")),
            flow::State::Logout => Response::build()
                .tag(cmd.tag.clone())
                .message("No commands are allowed in the LOGOUT state.")
                .bad()
                .map(|r| (r, flow::Transition::None)),
        }
        .unwrap_or_else(|err| {
            tracing::error!("Command error {:?} occured while processing {:?}", err, cmd);
            (
                Response::build()
                    .to_req(&cmd)
                    .message("Internal error while processing command")
                    .bad()
                    .unwrap(),
                flow::Transition::None,
            )
        });

        if let Err(e) = self.state.apply(tr) {
            tracing::error!(
                "Transition error {:?} occured while processing on command {:?}",
                e,
                cmd
            );
            return ResponseOrIdle::Response(Response::build()
                .to_req(&cmd)
                .message(
                    "Internal error, processing command triggered an illegal IMAP state transition",
                )
                .bad()
                .unwrap());
        }

        match &self.state {
            flow::State::Idle(_, _, _, _, n) => ResponseOrIdle::StartIdle(n.clone()),
            _ => ResponseOrIdle::Response(resp),
        }
    }
}