Skip to content

Replaced imap with mailpit and refreshed the test. #13219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/scripts/windows/build.bat
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ if /i "%GITHUB_ACTIONS%" neq "True" (
exit /b 3
)

set SDK_REMOTE=https://github.com/php/php-sdk-binary-tools.git
set SDK_BRANCH=%PHP_BUILD_SDK_BRANCH%
set SDK_REMOTE=https://github.com/SakiTakamachi/php-sdk-binary-tools.git
set SDK_BRANCH=test_mailpit
set SDK_RUNNER=%PHP_BUILD_CACHE_SDK_DIR%\phpsdk-%PHP_BUILD_CRT%-%PLATFORM%.bat

if not exist "%PHP_BUILD_CACHE_BASE_DIR%" (
Expand Down
5 changes: 1 addition & 4 deletions .github/scripts/windows/test_task.bat
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,7 @@ set PHP_BUILD_DIR=%PHP_BUILD_OBJ_DIR%\Release
if "%THREAD_SAFE%" equ "1" set PHP_BUILD_DIR=%PHP_BUILD_DIR%_TS

rem prepare for mail
curl -sLo hMailServer.exe https://www.hmailserver.com/download_file/?downloadid=271
hMailServer.exe /verysilent
cd %APPVEYOR_BUILD_FOLDER%
%PHP_BUILD_DIR%\php.exe -dextension_dir=%PHP_BUILD_DIR% -dextension=com_dotnet appveyor\setup_hmailserver.php
start %PHP_BUILD_CACHE_SDK_DIR%\bin\mailpit_amd.exe

mkdir %PHP_BUILD_DIR%\test_file_cache
rem generate php.ini
Expand Down
96 changes: 43 additions & 53 deletions ext/standard/tests/mail/bug72964.phpt
Original file line number Diff line number Diff line change
@@ -1,77 +1,67 @@
--TEST--
Bug #72964 (White space not unfolded for CC/Bcc headers)
--EXTENSIONS--
imap
--CONFLICTS--
imap
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only test');
if (getenv("SKIP_SLOW_TESTS")) die('skip slow test');
require_once __DIR__ . '/mail_skipif.inc';
require_once __DIR__.'/mail_windows_skipif.inc';
?>
--INI--
SMTP=localhost
smtp_port=25
smtp_port=1025
sendmail_from=from@example.com
--FILE--
<?php
require_once __DIR__ . '/mail_include.inc';

function find_and_delete_message($username, $subject) {
global $default_mailbox, $password;
require_once __DIR__.'/mailpit_utils.inc';

$imap_stream = imap_open($default_mailbox, $username, $password);
if ($imap_stream === false) {
die("Cannot connect to IMAP server $server: " . imap_last_error() . "\n");
}

$found = false;
$repeat_count = 20; // we will repeat a max of 20 times
while (!$found && $repeat_count > 0) {
// sleep for a while to allow msg to be delivered
sleep(1);

$num_messages = imap_check($imap_stream)->Nmsgs;
for ($i = $num_messages; $i > 0; $i--) {
$info = imap_headerinfo($imap_stream, $i);
if ($info->subject === $subject) {
imap_delete($imap_stream, $i);
$found = true;
break;
}
}
$repeat_count--;
}

imap_close($imap_stream, CL_EXPUNGE);
return $found;
}

$to = "{$users[2]}@$domain";
$to = 'bug72964_to@example.com';
$from = ini_get('sendmail_from');
$cc = ['bug72964_cc_1@example.com', 'bug72964_cc_2@example.com'];
$bcc = ['bug72964_bcc_1@example.com', 'bug72964_bcc_2@example.com'];
$subject = bin2hex(random_bytes(16));
$message = 'hello';
$headers = "From: webmaster@example.com\r\n"
. "Cc: {$users[0]}@$domain,\r\n\t{$users[1]}@$domain\r\n"
. "Bcc: {$users[2]}@$domain,\r\n {$users[3]}@$domain\r\n";
$headers = "From: {$from}\r\n"
. "Cc: {$cc[0]},\r\n\t{$cc[1]}\r\n"
. "Bcc: {$bcc[0]},\r\n {$bcc[1]}\r\n";

$res = mail($to, $subject, $message, $headers);

if ($res !== true) {
die("TEST FAILED : Unable to send test email\n");
exit("Unable to send the email.\n");
} else {
echo "Message sent OK\n";
echo "Sent the email.\n";
}

foreach ($users as $user) {
if (!find_and_delete_message("$user@$domain", $subject)) {
echo "TEST FAILED: email not delivered\n";
} else {
echo "TEST PASSED: Message sent and deleted OK\n";
$res = searchEmailByToAddress($to);

if (mailCheckResponse($res, $from, $to, $subject, $message)) {
echo "Received the email.\n";

$ccAddresses = getCcAddresses($res);
if (in_array($cc[0], $ccAddresses, true)) {
echo "cc1 Received the email.\n";
}

if (in_array($cc[1], $ccAddresses, true)) {
echo "cc2 Received the email.\n";
}

$bccAddresses = getBccAddresses($res);
if (in_array($bcc[0], $bccAddresses, true)) {
echo "bcc1 Received the email.\n";
}

if (in_array($bcc[1], $bccAddresses, true)) {
echo "bcc2 Received the email.";
}

deleteEmail($res);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it enough to just clear a single email - wouldn't be better to just always clear all emails. That should be also added to CLEAN section in case of failure so nothing is left there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was honestly confused about this too.

Deleting all emails causes problems with parallel tests, but aren't these tests ever run in parallel?

Also, I was hesitant about the CLEAN section because I would have to use the API several times to delete just one email.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I forgot that unlike mailhog, mailpit can be easily deleted. I can use the CLEAN section.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to other comments, this is data for outgoing emails, so even if there are multiple destinations, there will only be one email.

}
?>
--EXPECT--
Message sent OK
TEST PASSED: Message sent and deleted OK
TEST PASSED: Message sent and deleted OK
TEST PASSED: Message sent and deleted OK
TEST PASSED: Message sent and deleted OK
Sent the email.
Received the email.
cc1 Received the email.
cc2 Received the email.
bcc1 Received the email.
bcc2 Received the email.
91 changes: 34 additions & 57 deletions ext/standard/tests/mail/bug80706.phpt
Original file line number Diff line number Diff line change
@@ -1,80 +1,57 @@
--TEST--
Bug #72964 (White space not unfolded for CC/Bcc headers)
--EXTENSIONS--
imap
--CONFLICTS--
imap
Bug #80706 (Headers after Bcc headers may be ignored)
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only test');
if (getenv("SKIP_SLOW_TESTS")) die('skip slow test');
require_once __DIR__ . '/mail_skipif.inc';
require_once __DIR__.'/mail_windows_skipif.inc';
?>
--INI--
SMTP=localhost
smtp_port=25
smtp_port=1025
sendmail_from=from@example.com
--FILE--
<?php
require_once __DIR__ . '/mail_include.inc';

function find_and_delete_message($username, $subject) {
global $default_mailbox, $password;
require_once __DIR__.'/mailpit_utils.inc';

$imap_stream = imap_open($default_mailbox, $username, $password);
if ($imap_stream === false) {
die("Cannot connect to IMAP server $server: " . imap_last_error() . "\n");
}

$found = false;
$repeat_count = 20; // we will repeat a max of 20 times
while (!$found && $repeat_count > 0) {
// sleep for a while to allow msg to be delivered
sleep(1);

$num_messages = imap_check($imap_stream)->Nmsgs;
for ($i = $num_messages; $i > 0; $i--) {
$info = imap_headerinfo($imap_stream, $i);
if ($info->subject === $subject) {
$header = imap_fetchheader($imap_stream, $i);
echo "X-Mailer header found: ";
var_dump(strpos($header, 'X-Mailer: bug80706') !== false);
imap_delete($imap_stream, $i);
$found = true;
break;
}
}
$repeat_count--;
}

imap_close($imap_stream, CL_EXPUNGE);
return $found;
}

$to = "{$users[1]}@$domain";
$to = 'bug80706_to@example.com';
$from = ini_get('sendmail_from');
$bcc = 'bug80706_bcc@exsample.com';
$subject = bin2hex(random_bytes(16));
$message = 'hello';
$headers = "From: webmaster@example.com\r\n"
. "Bcc: {$users[2]}@$domain\r\n"
. "X-Mailer: bug80706";
$xMailer = 'bug80706_x_mailer';
$headers = "From: {$from}\r\n"
. "Bcc: {$bcc}\r\n"
. "X-Mailer: {$xMailer}";

$res = mail($to, $subject, $message, $headers);

if ($res !== true) {
die("TEST FAILED : Unable to send test email\n");
exit("Unable to send the email.\n");
} else {
echo "Message sent OK\n";
echo "Sent the email.\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
echo "Sent the email.\n";
echo "Email sent.\n";

and not much point to have else block for this...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right.

}

foreach ([$users[1], $users[2]] as $user) {
if (!find_and_delete_message("$user@$domain", $subject)) {
echo "TEST FAILED: email not delivered\n";
} else {
echo "TEST PASSED: Message sent and deleted OK\n";
$res = searchEmailByToAddress($to);

if (mailCheckResponse($res, $from, $to, $subject, $message)) {
echo "Received the email.\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
echo "Received the email.\n";
echo "Email received.\n";

just for consistency with email sent ... :)


$bccAddresses = getBccAddresses($res);
if (in_array($bcc, $bccAddresses, true)) {
echo "bcc Received the email.\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: This doesn't exactly tests that BCC received the email but more that BCC is set, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Maybe I should test that the email arrived correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah and might make sense to do the same for CC. Btw. shouldn't BCC be excluded from the original delivered email?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I was careless.

mailpit (and mailhog) is like an SMTP dummy, so I may not be able to check the behavior on the receiving side.

This test checks "sent mail", not received mail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but it's the sender who should send multiple emails. See https://stackoverflow.com/a/2750359 . So I would expect mailpit to have multiple emails received though. Might be worth to try to send BCC email with sendmail to see if it's behaving the same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but we need to verify that data is sent correctly. In this case it means that multiple RCPT TO are sent which was possible in the previous version. You can see that the client have special handling for BCC in that regard:

php-src/win32/sendmail.c

Lines 525 to 614 in 5da8335

/* Send mail to all Bcc rcpt's
This is basically a rip of the Cc code above.
Just don't forget to remove the Bcc: from the header afterwards. */
if (mailBcc && *mailBcc) {
tempMailTo = estrdup(mailBcc);
/* Send mail to all rcpt's */
token = find_address(tempMailTo, &token_state);
while (token != NULL)
{
SMTP_SKIP_SPACE(token);
FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
efree(tempMailTo);
return (res);
}
if ((res = Ack(&server_response)) != SUCCESS) {
SMTP_ERROR_RESPONSE(server_response);
efree(tempMailTo);
return (res);
}
token = find_address(NULL, &token_state);
}
efree(tempMailTo);
}
else if (headers) {
if ((pos1 = strstr(headers_lc, "bcc:")) && (pos1 == headers_lc || *(pos1-1) == '\n')) {
/* Real offset is memaddress from the original headers + difference of
* string found in the lowercase headers + 4 characters to jump over
* the bcc: */
pos1 = headers + (pos1 - headers_lc) + 4;
if (NULL == (pos2 = strstr(pos1, "\r\n"))) {
tempMailTo = estrndup(pos1, strlen(pos1));
/* Later, when we remove the Bcc: out of the
header we know it was the last thing. */
pos2 = pos1;
} else {
const char *pos3 = pos2;
while (pos2[2] == ' ' || pos2[2] == '\t') {
pos3 = strstr(pos2 + 2, "\r\n");
if (pos3 != NULL) {
pos2 = pos3;
} else {
pos2 += strlen(pos2);
break;
}
}
tempMailTo = estrndup(pos1, pos2 - pos1);
if (pos3 == NULL) {
/* Later, when we remove the Bcc: out of the
header we know it was the last thing. */
pos2 = pos1;
}
}
token = find_address(tempMailTo, &token_state);
while (token != NULL)
{
SMTP_SKIP_SPACE(token);
FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n");
if ((res = Post(PW32G(mail_buffer))) != SUCCESS) {
efree(tempMailTo);
return (res);
}
if ((res = Ack(&server_response)) != SUCCESS) {
SMTP_ERROR_RESPONSE(server_response);
efree(tempMailTo);
return (res);
}
token = find_address(NULL, &token_state);
}
efree(tempMailTo);
/* Now that we've identified that we've a Bcc list,
remove it from the current header. */
stripped_header = ecalloc(1, strlen(headers));
/* headers = point to string start of header
pos1 = pointer IN headers where the Bcc starts
'4' = Length of the characters 'bcc:'
Because we've added +4 above for parsing the Emails
we've to subtract them here. */
memcpy(stripped_header, headers, pos1 - headers - 4);
if (pos1 != pos2) {
/* if pos1 != pos2 , pos2 points to the rest of the headers.
Since pos1 != pos2 if "\r\n" was found, we know those characters
are there and so we jump over them (else we would generate a new header
which would look like "\r\n\r\n". */
memcpy(stripped_header + (pos1 - headers - 4), pos2 + 2, strlen(pos2) - 2);
}
}
}

The client actually strips BCC but seems that mailpit probably adds it back axllent/mailpit@9c8329a

If it's the case, it seems to me that mailpit might be good more for testing the actual email messages rather than making sure that client works as expected. For that it's maybe better to keep the actual real server and possibly just only replace imap client to check it out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. It's already late today, so I'll try it tomorrow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done some checking but only CLI clients seem to be like complete CLI apps (e.g. mutt and alpine) but didn't find anything that would not be probably ideal.

But then I found also this project: https://github.com/nodemailer/wildduck which offers API. I guess it can work just with SMTP mailserver but we already got so maybe that would be a better option. We would basically just replace usage of imap ext with calls to this service that could check the same thing potentially.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you. mailpit is a dummy SMTP server, so I may need something else to actually send.

If we want to test both sending and receiving behavior, things can get a little complicated.

I'll look into it a little more too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In previous tests, the sending/receiving server was the hmail server, and the IMAP extension was acting as an IMAP/POP3 client to the hmail server. wildduck apparently primarily functions as a receiving server, so it needs to be able to receive emails for tests, which can get quite complicated. (In fact, if I look at the documentation, there will be instructions to configure DNS.)

I'll continue investigating.

}

$headers = getHeaders($res);
if ($headers['X-Mailer'][0] === $xMailer) {
echo "The specified x-Mailer exists.";
}

deleteEmail($res);
}
?>
--EXPECT--
Message sent OK
X-Mailer header found: bool(true)
TEST PASSED: Message sent and deleted OK
X-Mailer header found: bool(true)
TEST PASSED: Message sent and deleted OK
Sent the email.
Received the email.
bcc Received the email.
The specified x-Mailer exists.
Loading