7
7
import uuid
8
8
from email import header , message_from_bytes , message_from_string , policy
9
9
from email .errors import MessageDefect
10
+ from email .message import Message
10
11
from email .mime .base import MIMEBase
11
12
from email .mime .multipart import MIMEMultipart
12
13
from email .mime .text import MIMEText
16
17
import aiohttp
17
18
from aiohttp import web
18
19
from aiohttp .web import Request , Response , WebSocketResponse
19
- from pymap .backend .dict .mailbox import Message
20
+ from pymap .backend .dict .mailbox import Message as PyMapMessage
20
21
from pymap .parsing .specials import FetchRequirement
21
22
from pymap .parsing .specials .flag import Flag
22
23
24
+ from .builder import Builder
23
25
from .mailbox import TestMailboxDict
26
+ from .utils import VERSION
24
27
25
28
_logger = logging .getLogger (__name__ )
26
29
@@ -54,6 +57,7 @@ async def run_app(
54
57
ssl_context = ssl_context ,
55
58
)
56
59
await site .start ()
60
+ return app
57
61
58
62
59
63
class Frontend :
@@ -94,7 +98,7 @@ def load_resource(self, resource: str) -> str:
94
98
95
99
return res .read_text (encoding = "utf-8" )
96
100
97
- async def start (self ) -> None :
101
+ async def start (self ) -> web . AppRunner :
98
102
self .api = web .Application (client_max_size = self .client_max_size )
99
103
100
104
self .api .add_routes (
@@ -168,7 +172,7 @@ async def _page_static(self, request: Request) -> Response:
168
172
_logger .error (f"File { static !r} not in resources" )
169
173
raise web .HTTPNotFound () from e
170
174
171
- async def _message_content (self , msg : Message ) -> bytes :
175
+ async def _message_content (self , msg : PyMapMessage ) -> bytes :
172
176
return bytes ((await msg .load_content (FetchRequirement .CONTENT )).content )
173
177
174
178
def message_hash (self , content : bytes | str ) -> str :
@@ -178,11 +182,20 @@ def message_hash(self, content: bytes | str) -> str:
178
182
return hashlib .sha512 (content ).hexdigest ()
179
183
180
184
async def _convert_message (
181
- self , msg : Message , * , account : str , mailbox : str , full : bool = False
185
+ self ,
186
+ msg : PyMapMessage ,
187
+ * ,
188
+ account : str ,
189
+ mailbox : str ,
190
+ full : bool = False ,
191
+ message : Message | None = None ,
182
192
) -> dict :
183
- content = await self ._message_content (msg )
193
+ if not message :
194
+ content = await self ._message_content (msg )
195
+ message = message_from_bytes (content )
196
+ else :
197
+ content = message .as_bytes ()
184
198
185
- message = message_from_bytes (content )
186
199
result = {
187
200
"uid" : msg .uid ,
188
201
"flags" : flags_to_api (msg .permanent_flags ),
@@ -196,15 +209,15 @@ async def _convert_message(
196
209
msg_hash = self .message_hash (content )
197
210
self .mail_cache [msg_hash ] = (account , mailbox , msg .uid )
198
211
199
- result [ " attachments" ] = []
212
+ attachments = []
200
213
if message .is_multipart ():
201
214
for part in message .walk ():
202
215
ctype = part .get_content_type ()
203
216
cdispo = part .get_content_disposition ()
204
217
205
218
if cdispo == "attachment" :
206
219
name = part .get_filename ()
207
- result [ " attachments" ] .append (
220
+ attachments .append (
208
221
{"name" : name , "url" : f"/attachment/{ msg_hash } /{ name } " }
209
222
)
210
223
elif ctype == "text/plain" :
@@ -216,6 +229,7 @@ async def _convert_message(
216
229
else :
217
230
result ["body_plain" ] = message .get_payload (decode = True ).decode ()
218
231
232
+ result ["attachments" ] = attachments
219
233
result ["content" ] = bytes (content ).decode ()
220
234
return result
221
235
@@ -226,6 +240,7 @@ async def on_config(self, ws: WebSocketResponse) -> None:
226
240
"data" : {
227
241
"multi_user" : self .multi_user ,
228
242
"flagged_seen" : self .flagged_seen ,
243
+ "version" : VERSION ,
229
244
},
230
245
}
231
246
)
@@ -315,9 +330,9 @@ async def on_random_mail(
315
330
) -> None :
316
331
headers = {
317
332
"subject" : f"Random Subject [{ secrets .token_hex (8 )} ]" ,
318
- "message-id" : f" { uuid . uuid4 () } @mail-devel" ,
333
+ "message-id" : Builder . message_id () ,
319
334
"to" : account ,
320
- "from" : f" { secrets . token_hex ( 8 ) } @mail-devel" ,
335
+ "from" : Builder . mail_address () ,
321
336
}
322
337
_logger .info ("Randomized mail" )
323
338
await ws .send_json (
@@ -344,27 +359,17 @@ async def on_reply_mail(
344
359
345
360
async for msg in mbox .messages ():
346
361
if msg .uid == uid :
362
+ content = await self ._message_content (msg )
363
+ reply = Builder .reply_mail (message_from_bytes (content ))
364
+
347
365
message = await self ._convert_message (
348
366
msg ,
349
367
account = account ,
350
368
mailbox = mailbox ,
351
369
full = True ,
370
+ message = reply ,
352
371
)
353
372
354
- headers = message ["header" ]
355
- headers ["subject" ] = f"RE: { headers ['subject' ]} "
356
- msg_id = headers .get ("message-id" , None )
357
- if msg_id :
358
- headers ["in-reply-to" ] = msg_id
359
- headers ["references" ] = f"{ msg_id } { headers .get ('references' , '' )} "
360
- headers ["message-id" ] = f"{ uuid .uuid4 ()} @mail-devel"
361
- headers ["to" ] = headers ["from" ]
362
- headers ["from" ] = self .user
363
- headers .pop ("content-type" , None )
364
- for key in list (headers ):
365
- if key .startswith ("x-" ):
366
- headers .pop (key , None )
367
-
368
373
await ws .send_json (
369
374
{
370
375
"command" : "reply_mail" ,
@@ -428,7 +433,7 @@ async def on_upload_mails(
428
433
msg = message_from_string (mail ["data" ], policy = compat_strict )
429
434
430
435
if not msg ["Message-Id" ] and self .ensure_message_id :
431
- msg .add_header ("Message-Id" , f" { uuid . uuid4 () } @mail-devel" )
436
+ msg .add_header ("Message-Id" , Builder . message_id () )
432
437
433
438
await self .mailboxes .append (
434
439
msg ,
@@ -462,7 +467,7 @@ async def on_send_mail(
462
467
message .add_header (key .title (), value )
463
468
464
469
if not message ["Message-Id" ] and self .ensure_message_id :
465
- message .add_header ("Message-Id" , f" { uuid . uuid4 () } @mail-devel" )
470
+ message .add_header ("Message-Id" , Builder . message_id () )
466
471
467
472
for att in mail .get ("attachments" , []):
468
473
part = MIMEBase (* (att ["mimetype" ] or "text/plain" ).split ("/" ))
@@ -514,8 +519,8 @@ async def _download_attachment(self, request: Request) -> Response:
514
519
return web .Response (
515
520
body = body ,
516
521
headers = {
517
- "Content-Type" : part .get ("Content-Type" ),
518
- "Content-Disposition" : part .get ("Content-Disposition" ),
522
+ "Content-Type" : part .get ("Content-Type" , "" ),
523
+ "Content-Disposition" : part .get ("Content-Disposition" , "" ),
519
524
},
520
525
)
521
526
0 commit comments