VulCode
Blog

PHPMailer: QUIT nie trafia już do treści przerwanej wiadomości

Gdy wysyłka maila urwała się w trakcie komendy DATA, PHPMailer wysyłał QUIT, a serwer brał go za kolejną linię wiadomości i czekał dalej. Teraz w takiej sytuacji połączenie jest po prostu zamykane.

DualFroz - VulCode CEODualFroz - VulCode CEO1 min czytania
PHPMailer: QUIT nie trafia już do treści przerwanej wiadomości - DualFroz - VulCode CEO
Tytuł zmiany w repozytorium
Don't send QUIT while DATA is in progress
Scalono
4.10.2026
Opisano tutaj
6.10.2026

Wpis powstał 2 dni po scaleniu zmiany.

PHPMailer
Technologie:PHPSMTP

Co nie działało

W protokole SMTP po komendzie DATA serwer traktuje każdą kolejną linię jako treść wiadomości, aż do linii z samą kropką. Jeśli wysyłka urwała się właśnie w tym momencie, na przykład przez wyjątek albo przekroczony czas, a kod aplikacji zamykał potem połączenie przez quit(), PHPMailer wysyłał komendę QUIT. Serwer nie widział w niej końca sesji, tylko kolejną linię maila, więc nie odpowiadał, a klient czekał na odpowiedź do upływu limitu czasu. Problem opisywało zgłoszenie #2994.

Co zmieniliśmy

Poszliśmy za pomysłem z dyskusji w #3121: klasa SMTP pamięta ostatnią wysłaną komendę, a quit() sprawdza ją przed wysłaniem czegokolwiek.

php
if ($this->state === 'DATA') {
    //The server would take QUIT as part of an unfinished message, so just close the connection
    $noerror = true;
} else {
    $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
}

Stan ustawia sendCommand() przy każdej komendzie, a close() go czyści. W opisie zmiany zaznaczyliśmy jedno ograniczenie: komenda DATA, którą serwer odrzucił, też zostawia stan DATA, i zaproponowaliśmy śledzenie tylko przyjętych komend, gdyby opiekun wolał takie zachowanie.

Jak to sprawdziliśmy

Nowy test testQuitDuringData otwiera połączenie, wysyła MAIL i RCPT, rozpoczyna DATA bez wysyłania treści, jakby wysyłka została przerwana, i wywołuje quit(). Test sprawdza w logu klienta, że QUIT nie został wysłany, oraz że połączenie jest zamknięte.

Efekt

Zmianę scalono 4 października 2026. Aplikacje, które sprzątają połączenie po nieudanej wysyłce, nie wiszą już na limicie czasu i nie dopisują QUIT do porzuconej wiadomości.

Klasyczna biblioteka PHP do wysyłania poczty, używana przez WordPressa i wiele innych systemów.

Język
PHP
Gwiazdki na GitHubie
22 310

Ten sam przypadek przed zmianą i po niej

Na przykładzie z wpisu: co wychodziło przed poprawką i co wychodzi teraz.

Kolejność z testu testQuitDuringData
MAIL
RCPT
DATA
quit()

DATA rozpoczęte bez wysłania treści, jakby wysyłka została przerwana, a potem sprzątanie połączenia.

Przed zmianą
QUIT

Serwer brał QUIT za kolejną linię wiadomości i nie odpowiadał, a klient czekał do upływu limitu czasu (zgłoszenie #2994).

Po zmianie

QUIT nie jest wysyłany, a połączenie jest zamykane. Test sprawdza to w logu klienta.

Czego nas to nauczyło

Protokół SMTP ma stany, a polecenie wysłane w złym stanie staje się treścią wiadomości. Przy wysyłce poczty zawsze sprawdzamy, w jakim stanie jest połączenie, zanim je zamkniemy.

Z opisu zmiany i testu w pull requeście #3362.

Pytania o zmianę #3362

Gdzie jest zmiana, jak ją sprawdziliśmy i kto ją przyjął.