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:
@@ -0,0 +1,38 @@
|
||||
package com.mailserver.service;
|
||||
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* The message exactly as received or appended (table message_sources). IMAP serves it as BODY[]; a message without
|
||||
* one gets its MIME text built once ({@link com.mailserver.imap.MimeSource}). A changed draft loses it.
|
||||
*/
|
||||
@Component
|
||||
public class MessageSources {
|
||||
|
||||
private final DatabaseClient db;
|
||||
|
||||
public MessageSources(DatabaseClient db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public Mono<byte[]> get(Long messageId) {
|
||||
return db.sql("SELECT raw FROM message_sources WHERE message_id = :id")
|
||||
.bind("id", messageId)
|
||||
.map(row -> row.get("raw", byte[].class))
|
||||
.one();
|
||||
}
|
||||
|
||||
public Mono<Void> put(Long messageId, byte[] raw) {
|
||||
return db.sql("INSERT INTO message_sources (message_id, raw) VALUES (:id, :raw) "
|
||||
+ "ON CONFLICT (message_id) DO UPDATE SET raw = EXCLUDED.raw")
|
||||
.bind("id", messageId)
|
||||
.bind("raw", raw)
|
||||
.then();
|
||||
}
|
||||
|
||||
public Mono<Void> remove(Long messageId) {
|
||||
return db.sql("DELETE FROM message_sources WHERE message_id = :id").bind("id", messageId).then();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user