annotate libervia/backend/plugins/plugin_comp_email_gateway/imap.py @ 4321:2246eeeccc74

tests (unit): fix tests: - test_ap-gateway: fix missing implementation of `client.is_local` - test_plugin_xep_0215: fix missing return value of `has_feature`
author Goffi <goffi@goffi.org>
date Mon, 30 Sep 2024 14:15:47 +0200
parents a7ec325246fb
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
4303
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
1 #!/usr/bin/env python3
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
2
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
3 # Libervia Email Gateway Component
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
4 # Copyright (C) 2009-2024 Jérôme Poisson (goffi@goffi.org)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
5
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
6 # This program is free software: you can redistribute it and/or modify
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
7 # it under the terms of the GNU Affero General Public License as published by
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
8 # the Free Software Foundation, either version 3 of the License, or
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
9 # (at your option) any later version.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
10
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
11 # This program is distributed in the hope that it will be useful,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
14 # GNU Affero General Public License for more details.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
15
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
16 # You should have received a copy of the GNU Affero General Public License
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
18
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
19 from email.message import EmailMessage
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
20 from email.parser import BytesParser, Parser
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
21 from email import policy
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
22 from typing import Callable, cast
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
23 from twisted.internet import defer, protocol, reactor
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
24 from twisted.internet.base import DelayedCall
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
25 from twisted.mail import imap4
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
26 from twisted.python.failure import Failure
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
27
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
28 from libervia.backend.core import exceptions
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
29 from libervia.backend.core.i18n import _
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
30 from libervia.backend.core.log import getLogger
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
31 from .models import UserData
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
32
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
33 log = getLogger(__name__)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
34
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
35
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
36 class IMAPClient(imap4.IMAP4Client):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
37 _idling = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
38 _idle_timer: DelayedCall | None = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
39
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
40 def __init__(self, connected: defer.Deferred, *args, **kwargs) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
41 super().__init__(*args, **kwargs)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
42 self._connected = connected
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
43
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
44 def serverGreeting(self, caps: dict) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
45 """Handle the server greeting and capabilities.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
46
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
47 @param caps: Server capabilities.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
48 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
49 defer.ensureDeferred(self.on_server_greeting(caps))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
50
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
51 async def on_server_greeting(self, caps: dict) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
52 """Async method called when server greeting is received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
53
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
54 @param caps: Server capabilities.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
55 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
56 self.server_capabilities = caps
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
57 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
58 await self.authenticate(self.factory.password.encode())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
59 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
60 log.warning(f"Can't authenticate: {e}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
61 self._connected.errback(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
62 exceptions.PasswordError("Authentication error for IMAP server.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
63 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
64 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
65 log.debug("Authenticated.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
66 self._connected.callback(None)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
67 if b"IDLE" in caps:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
68 # We use "examine" for read-only access for now, will probably change in the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
69 # future.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
70 await self.examine(b"INBOX")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
71 log.debug("Activating IDLE mode")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
72 await self.idle()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
73 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
74 log.warning(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
75 f'"IDLE" mode is not supported by your server, this gateways needs a '
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
76 "server supporting this mode."
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
77 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
78 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
79
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
80 async def idle(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
81 """Enter the IDLE mode to receive real-time updates from the server."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
82 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
83 # We are already in idle state.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
84 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
85 self._idling = True
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
86 self._idle_timer = reactor.callLater(29 * 60, self.on_idle_timeout)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
87 await self.sendCommand(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
88 imap4.Command(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
89 b"IDLE",
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
90 continuation=lambda *a, **kw: log.debug(f"continuation: {a=} {kw=}"),
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
91 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
92 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
93
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
94 def idle_exit(self) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
95 """Exit the IDLE mode."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
96 assert self._idling
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
97 assert self._idle_timer is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
98 if not self._idle_timer.called:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
99 self._idle_timer.cancel()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
100 self._idle_timer = None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
101 # Send DONE command to exit IDLE mode.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
102 self.sendLine(b"DONE")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
103 self._idling = False
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
104 log.debug("IDLE mode terminated")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
105
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
106 def on_idle_timeout(self):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
107 """Called when IDLE mode timeout is reached."""
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
108 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
109 # We've reached 29 min of IDLE mode, we restart it as recommended in the
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
110 # specifications.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
111 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
112 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
113
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
114 def newMessages(self, exists: int | None, recent: int | None):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
115 """Called when new messages are received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
116
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
117 @param exists: Number of existing messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
118 @param recent: Number of recent messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
119 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
120 defer.ensureDeferred(self.on_new_emails(exists, recent))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
121
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
122 async def on_new_emails(self, exists: int | None, recent: int | None) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
123 """Async method called when new messages are received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
124
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
125 @param exists: Number of existing messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
126 @param recent: Number of recent messages.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
127 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
128 log.debug(f"New messages: {exists}, Recent messages: {recent}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
129 log.debug("Retrieving last message.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
130 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
131 mess_data = await self.fetchMessage("*")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
132 for message in mess_data.values():
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
133 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
134 content = message["RFC822"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
135 except KeyError:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
136 log.warning(f"Can't find content for {message}.")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
137 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
138 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
139 if isinstance(content, str):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
140 parser = Parser(policy=policy.default)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
141 parser_method = parser.parsestr
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
142 elif isinstance(content, bytes):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
143 parser = BytesParser(policy=policy.default)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
144 parser_method = parser.parsebytes
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
145 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
146 log.error(f"Invalid content: {content}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
147 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
148 try:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
149 parsed = parser_method(content)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
150 except Exception as e:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
151 log.warning(f"Can't parse content of email: {e}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
152 continue
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
153 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
154 assert self.factory is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
155 factory = cast(IMAPClientFactory, self.factory)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
156 await factory.on_new_email(parsed)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
157
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
158 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
159
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
160 def connectionLost(self, reason: Failure) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
161 """Called when the connection is lost.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
162
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
163 @param reason: The reason for the lost connection.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
164 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
165 log.debug(f"connectionLost {reason=}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
166 if not self._connected.called:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
167 self._connected.errback(reason)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
168 super().connectionLost(reason)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
169
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
170 def lineReceived(self, line: bytes) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
171 """Called when a line is received from the server.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
172
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
173 @param line: The received line.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
174 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
175 if self._idling:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
176 if line == b"* OK Still here":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
177 pass
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
178 elif line == b"+ idling":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
179 pass
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
180 elif line.startswith(b"* "):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
181 # Handle unsolicited responses during IDLE
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
182 self._extraInfo([imap4.parseNestedParens(line[2:])])
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
183 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
184 log.warning(f"Unexpected line received: {line!r}")
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
185
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
186 return
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
187
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
188 return super().lineReceived(line)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
189
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
190 def sendCommand(self, cmd: imap4.Command) -> defer.Deferred:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
191 """Send a command to the server.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
192
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
193 This method is overriden to stop and restart IDLE mode when a command is received.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
194
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
195 @param cmd: The command to send.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
196 @return: A deferred that fires when the command is sent.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
197 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
198 if self._idling and cmd.command != b"IDLE":
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
199 self.idle_exit()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
200 d = super().sendCommand(cmd)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
201
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
202 def restart_idle_mode(ret):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
203 defer.ensureDeferred(self.idle())
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
204 return ret
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
205
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
206 d.addCallback(restart_idle_mode)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
207 return d
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
208 else:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
209 return super().sendCommand(cmd)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
210
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
211
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
212 class IMAPClientFactory(protocol.ClientFactory):
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
213 protocol = IMAPClient
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
214
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
215 def __init__(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
216 self,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
217 user_data: UserData,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
218 on_new_email: Callable[[EmailMessage], None],
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
219 connected: defer.Deferred,
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
220 ) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
221 """Initialize the IMAP client factory.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
222
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
223 @param username: The username to use for authentication.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
224 @param password: The password to use for authentication.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
225 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
226 credentials = user_data.credentials
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
227 self.user_data = user_data
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
228 self.username = credentials["imap_username"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
229 self.password = credentials["imap_password"]
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
230 self.on_new_email = on_new_email
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
231 self._connected = connected
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
232
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
233 def buildProtocol(self, addr) -> IMAPClient:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
234 """Build the IMAP client protocol.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
235
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
236 @return: The IMAP client protocol.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
237 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
238 assert self.protocol is not None
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
239 assert isinstance(self.protocol, type(IMAPClient))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
240 protocol_ = self.protocol(self._connected)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
241 protocol_.factory = self
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
242 self.user_data.imap_client = protocol_
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
243 assert isinstance(protocol_, IMAPClient)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
244 protocol_.factory = self
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
245 encoded_username = self.username.encode()
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
246
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
247 protocol_.registerAuthenticator(imap4.PLAINAuthenticator(encoded_username))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
248 protocol_.registerAuthenticator(imap4.LOGINAuthenticator(encoded_username))
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
249 protocol_.registerAuthenticator(
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
250 imap4.CramMD5ClientAuthenticator(encoded_username)
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
251 )
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
252
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
253 return protocol_
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
254
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
255 def clientConnectionFailed(self, connector, reason: Failure) -> None:
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
256 """Called when the client connection fails.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
257
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
258 @param reason: The reason for the failure.
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
259 """
a7ec325246fb component email-gateway: first draft:
Goffi <goffi@goffi.org>
parents:
diff changeset
260 log.warning(f"Connection failed: {reason}")