75.2 IMAP: новый сервер на Reactor Netty (STARTTLS, 993, IDLE, UIDPLUS, MOVE)
Старый IMAP (выключен, .block() на сетевом потоке) удалён, написан заново:
- ImapServer: декодер целых команд с литералами ({n} после "+ Ready", {n+}),
команды по одной на рабочем потоке; STARTTLS (без выполнения открытого текста
после него) и неявный TLS; IDLE с опросом раз в 5 с до DONE
- ImapSession: IMAP4rev1 + LITERAL+, SASL-IR, IDLE, UIDPLUS, MOVE, UNSELECT,
NAMESPACE, SPECIAL-USE, ID; FETCH (ENVELOPE, BODYSTRUCTURE, BODY[section]<partial>),
SEARCH, STORE, COPY/MOVE с COPYUID, APPEND с APPENDUID, EXPUNGE/UID EXPUNGE
- MimeTree: части письма по байтам — размеры BODYSTRUCTURE совпадают с BODY[]
- V4: UID на папку выдаёт триггер (перенос из веба тоже получает новый UID),
флаги \Answered/\Deleted, message_sources — письмо как пришло (SMTP, APPEND)
или собранное один раз (MimeSource); правка черновика сбрасывает его
- тесты: настоящий клиент Angus IMAP, сырые литералы/IDLE/подмешивание (47 всего)
- живая проверка: обновление V3 -> V4 на PostgreSQL с письмами, Python imaplib:
STARTTLS, 993, папки, вложения, поиск, APPEND/COPY/MOVE/EXPUNGE, IDLE — 0 ошибок
α
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,9 +38,12 @@ public class LocalDelivery implements DeliveryWorker.BounceSender {
|
||||
private final SpfService spfService;
|
||||
private final DmarcService dmarcService;
|
||||
private final String hostname;
|
||||
private final MessageSources sources;
|
||||
|
||||
public LocalDelivery(MailService mailService, DkimService dkimService, SpfService spfService,
|
||||
DmarcService dmarcService, @Value("${mailserver.smtp.hostname:localhost}") String hostname) {
|
||||
DmarcService dmarcService, @Value("${mailserver.smtp.hostname:localhost}") String hostname,
|
||||
MessageSources sources) {
|
||||
this.sources = sources;
|
||||
this.mailService = mailService;
|
||||
this.dkimService = dkimService;
|
||||
this.spfService = spfService;
|
||||
@@ -82,11 +85,13 @@ public class LocalDelivery implements DeliveryWorker.BounceSender {
|
||||
for (FilePart file : attachments(mime)) {
|
||||
mailService.storeIncomingAttachment(stored.getId(), file.name(), file.contentType(), file.bytes());
|
||||
}
|
||||
// IMAP serves the message exactly as it arrived
|
||||
sources.put(stored.getId(), raw).block();
|
||||
}
|
||||
}
|
||||
|
||||
/** "a@x, b@y" from a header, or null. A broken header is ignored, not a reason to lose the message. */
|
||||
static String addresses(MimeMessage mime, jakarta.mail.Message.RecipientType type) {
|
||||
public static String addresses(MimeMessage mime, jakarta.mail.Message.RecipientType type) {
|
||||
try {
|
||||
jakarta.mail.Address[] list = mime.getRecipients(type);
|
||||
if (list == null || list.length == 0) {
|
||||
@@ -103,11 +108,11 @@ public class LocalDelivery implements DeliveryWorker.BounceSender {
|
||||
}
|
||||
}
|
||||
|
||||
record FilePart(String name, String contentType, byte[] bytes) {
|
||||
public record FilePart(String name, String contentType, byte[] bytes) {
|
||||
}
|
||||
|
||||
/** Parts with a file name (attachments and inline images), nested multiparts walked. */
|
||||
static List<FilePart> attachments(Part part) throws MessagingException, IOException {
|
||||
public static List<FilePart> attachments(Part part) throws MessagingException, IOException {
|
||||
List<FilePart> files = new ArrayList<>();
|
||||
collect(part, files);
|
||||
return files;
|
||||
@@ -144,11 +149,11 @@ public class LocalDelivery implements DeliveryWorker.BounceSender {
|
||||
}
|
||||
}
|
||||
|
||||
record Body(String text, boolean html) {
|
||||
public record Body(String text, boolean html) {
|
||||
}
|
||||
|
||||
/** HTML part when the message has one, otherwise the plain text; nested multiparts are walked. */
|
||||
static Body body(Part part) throws MessagingException, IOException {
|
||||
public static Body body(Part part) throws MessagingException, IOException {
|
||||
String html = find(part, "text/html");
|
||||
if (html != null) {
|
||||
return new Body(html, true);
|
||||
|
||||
Reference in New Issue
Block a user