Skip to content

Commit 87a5a0e

Browse files
committed
Merge branch 'PHP-8.0'
* PHP-8.0: Fix #80751: Comma in recipient name breaks email delivery
2 parents 1cb823c + 37c9728 commit 87a5a0e

File tree

2 files changed

+179
-16
lines changed

2 files changed

+179
-16
lines changed

ext/standard/tests/mail/bug80751.phpt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
--TEST--
2+
Bug #80751 (Comma in recipient name breaks email delivery)
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY !== 'Windows') die('skip Windows only test');
6+
if (getenv("SKIP_SLOW_TESTS")) die('skip slow test');
7+
require_once __DIR__ . '/mail_skipif.inc';
8+
?>
9+
--INI--
10+
SMTP=localhost
11+
smtp_port=25
12+
--FILE--
13+
<?php
14+
require_once __DIR__ . '/mail_include.inc';
15+
16+
function find_and_delete_message($username, $subject) {
17+
global $default_mailbox, $password;
18+
19+
$imap_stream = imap_open($default_mailbox, $username, $password);
20+
if ($imap_stream === false) {
21+
die("Cannot connect to IMAP server $server: " . imap_last_error() . "\n");
22+
}
23+
24+
$found = false;
25+
$repeat_count = 20; // we will repeat a max of 20 times
26+
while (!$found && $repeat_count > 0) {
27+
// sleep for a while to allow msg to be delivered
28+
sleep(1);
29+
30+
$num_messages = imap_check($imap_stream)->Nmsgs;
31+
for ($i = $num_messages; $i > 0; $i--) {
32+
$info = imap_headerinfo($imap_stream, $i);
33+
if ($info->subject === $subject) {
34+
$header = imap_fetchheader($imap_stream, $i);
35+
echo "Return-Path header found: ";
36+
var_dump(strpos($header, 'Return-Path: [email protected]') !== false);
37+
echo "To header found: ";
38+
var_dump(strpos($header, 'To: "<[email protected]>" <[email protected]>') !== false);
39+
echo "From header found: ";
40+
var_dump(strpos($header, 'From: "<[email protected]>" <[email protected]>') !== false);
41+
echo "Cc header found: ";
42+
var_dump(strpos($header, 'Cc: "Lastname, Firstname\\\\" <[email protected]>') !== false);
43+
imap_delete($imap_stream, $i);
44+
$found = true;
45+
break;
46+
}
47+
}
48+
$repeat_count--;
49+
}
50+
51+
imap_close($imap_stream, CL_EXPUNGE);
52+
return $found;
53+
}
54+
55+
$to = "\"<[email protected]>\" <{$users[1]}@$domain>";
56+
$subject = bin2hex(random_bytes(16));
57+
$message = 'hello';
58+
$headers = "From: \"<[email protected]>\" <[email protected]>\r\n"
59+
. "Cc: \"Lastname, Firstname\\\\\" <{$users[2]}@$domain>\r\n"
60+
. "Bcc: \"Firstname \\\"Ni,ck\\\" Lastname\" <{$users[3]}@$domain>\r\n";
61+
62+
$res = mail($to, $subject, $message, $headers);
63+
if ($res !== true) {
64+
die("TEST FAILED : Unable to send test email\n");
65+
} else {
66+
echo "Message sent OK\n";
67+
}
68+
69+
foreach ([$users[1], $users[2], $users[3]] as $user) {
70+
if (!find_and_delete_message("$user@$domain", $subject)) {
71+
echo "TEST FAILED: email not delivered\n";
72+
} else {
73+
echo "TEST PASSED: Message sent and deleted OK\n";
74+
}
75+
}
76+
?>
77+
--EXPECT--
78+
Message sent OK
79+
Return-Path header found: bool(true)
80+
To header found: bool(true)
81+
From header found: bool(true)
82+
Cc header found: bool(true)
83+
TEST PASSED: Message sent and deleted OK
84+
Return-Path header found: bool(true)
85+
To header found: bool(true)
86+
From header found: bool(true)
87+
Cc header found: bool(true)
88+
TEST PASSED: Message sent and deleted OK
89+
Return-Path header found: bool(true)
90+
To header found: bool(true)
91+
From header found: bool(true)
92+
Cc header found: bool(true)
93+
TEST PASSED: Message sent and deleted OK

win32/sendmail.c

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,37 @@ PHPAPI char *GetSMErrorText(int index)
333333
}
334334
}
335335

336+
/* strtok_r like, but recognizes quoted-strings */
337+
static char *find_address(char *list, char **state)
338+
{
339+
zend_bool in_quotes = 0;
340+
char *p = list;
341+
342+
if (list == NULL) {
343+
if (*state == NULL) {
344+
return NULL;
345+
}
346+
p = list = *state;
347+
}
348+
*state = NULL;
349+
while ((p = strpbrk(p, ",\"\\")) != NULL) {
350+
if (*p == '\\' && in_quotes) {
351+
if (p[1] == '\0') {
352+
/* invalid address; let SMTP server deal with it */
353+
break;
354+
}
355+
p++;
356+
} else if (*p == '"') {
357+
in_quotes = !in_quotes;
358+
} else if (*p == ',' && !in_quotes) {
359+
*p = '\0';
360+
*state = p + 1;
361+
break;
362+
}
363+
p++;
364+
}
365+
return list;
366+
}
336367

337368
/*********************************************************************
338369
// Name: SendText
@@ -357,7 +388,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
357388
{
358389
int res;
359390
char *p;
360-
char *tempMailTo, *token;
391+
char *tempMailTo, *token, *token_state;
361392
const char *pos1, *pos2;
362393
char *server_response = NULL;
363394
char *stripped_header = NULL;
@@ -411,7 +442,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
411442

412443
tempMailTo = estrdup(mailTo);
413444
/* Send mail to all rcpt's */
414-
token = strtok(tempMailTo, ",");
445+
token = find_address(tempMailTo, &token_state);
415446
while (token != NULL)
416447
{
417448
SMTP_SKIP_SPACE(token);
@@ -425,14 +456,14 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
425456
efree(tempMailTo);
426457
return (res);
427458
}
428-
token = strtok(NULL, ",");
459+
token = find_address(NULL, &token_state);
429460
}
430461
efree(tempMailTo);
431462

432463
if (mailCc && *mailCc) {
433464
tempMailTo = estrdup(mailCc);
434465
/* Send mail to all rcpt's */
435-
token = strtok(tempMailTo, ",");
466+
token = find_address(tempMailTo, &token_state);
436467
while (token != NULL)
437468
{
438469
SMTP_SKIP_SPACE(token);
@@ -446,7 +477,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
446477
efree(tempMailTo);
447478
return (res);
448479
}
449-
token = strtok(NULL, ",");
480+
token = find_address(NULL, &token_state);
450481
}
451482
efree(tempMailTo);
452483
}
@@ -472,7 +503,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
472503
tempMailTo = estrndup(pos1, pos2 - pos1);
473504
}
474505

475-
token = strtok(tempMailTo, ",");
506+
token = find_address(tempMailTo, &token_state);
476507
while (token != NULL)
477508
{
478509
SMTP_SKIP_SPACE(token);
@@ -486,7 +517,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
486517
efree(tempMailTo);
487518
return (res);
488519
}
489-
token = strtok(NULL, ",");
520+
token = find_address(NULL,&token_state);
490521
}
491522
efree(tempMailTo);
492523
}
@@ -497,7 +528,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
497528
if (mailBcc && *mailBcc) {
498529
tempMailTo = estrdup(mailBcc);
499530
/* Send mail to all rcpt's */
500-
token = strtok(tempMailTo, ",");
531+
token = find_address(tempMailTo, &token_state);
501532
while (token != NULL)
502533
{
503534
SMTP_SKIP_SPACE(token);
@@ -511,7 +542,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
511542
efree(tempMailTo);
512543
return (res);
513544
}
514-
token = strtok(NULL, ",");
545+
token = find_address(NULL, &token_state);
515546
}
516547
efree(tempMailTo);
517548
}
@@ -545,7 +576,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
545576
}
546577
}
547578

548-
token = strtok(tempMailTo, ",");
579+
token = find_address(tempMailTo, &token_state);
549580
while (token != NULL)
550581
{
551582
SMTP_SKIP_SPACE(token);
@@ -559,7 +590,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char *
559590
efree(tempMailTo);
560591
return (res);
561592
}
562-
token = strtok(NULL, ",");
593+
token = find_address(NULL, &token_state);
563594
}
564595
efree(tempMailTo);
565596

@@ -979,6 +1010,46 @@ static unsigned long GetAddr(LPSTR szHost)
9791010
return (lAddr);
9801011
} /* end GetAddr() */
9811012

1013+
/* returns the contents of an angle-addr (caller needs to efree) or NULL */
1014+
static char *get_angle_addr(char *address)
1015+
{
1016+
zend_bool in_quotes = 0;
1017+
char *p1 = address, *p2;
1018+
1019+
while ((p1 = strpbrk(p1, "<\"\\")) != NULL) {
1020+
if (*p1 == '\\' && in_quotes) {
1021+
if (p1[1] == '\0') {
1022+
/* invalid address; let SMTP server deal with it */
1023+
return NULL;
1024+
}
1025+
p1++;
1026+
} else if (*p1 == '"') {
1027+
in_quotes = !in_quotes;
1028+
} else if (*p1 == '<' && !in_quotes) {
1029+
break;
1030+
}
1031+
p1++;
1032+
}
1033+
if (p1 == NULL) return NULL;
1034+
p2 = ++p1;
1035+
while ((p2 = strpbrk(p2, ">\"\\")) != NULL) {
1036+
if (*p2 == '\\' && in_quotes) {
1037+
if (p2[1] == '\0') {
1038+
/* invalid address; let SMTP server deal with it */
1039+
return NULL;
1040+
}
1041+
p2++;
1042+
} else if (*p2 == '"') {
1043+
in_quotes = !in_quotes;
1044+
} else if (*p2 == '>' && !in_quotes) {
1045+
break;
1046+
}
1047+
p2++;
1048+
}
1049+
if (p2 == NULL) return NULL;
1050+
1051+
return estrndup(p1, p2 - p1);
1052+
}
9821053

9831054
/*********************************************************************
9841055
// Name: int FormatEmailAddress
@@ -994,13 +1065,12 @@ static unsigned long GetAddr(LPSTR szHost)
9941065
// History:
9951066
//********************************************************************/
9961067
static int FormatEmailAddress(char* Buf, char* EmailAddress, char* FormatString) {
997-
char *tmpAddress1, *tmpAddress2;
1068+
char *tmpAddress;
9981069
int result;
9991070

1000-
if( (tmpAddress1 = strchr(EmailAddress, '<')) && (tmpAddress2 = strchr(tmpAddress1, '>')) ) {
1001-
*tmpAddress2 = 0; // terminate the string temporarily.
1002-
result = snprintf(Buf, MAIL_BUFFER_SIZE, FormatString , tmpAddress1+1);
1003-
*tmpAddress2 = '>'; // put it back the way it was.
1071+
if ((tmpAddress = get_angle_addr(EmailAddress)) != NULL) {
1072+
result = snprintf(Buf, MAIL_BUFFER_SIZE, FormatString, tmpAddress);
1073+
efree(tmpAddress);
10041074
return result;
10051075
}
10061076
return snprintf(Buf, MAIL_BUFFER_SIZE , FormatString , EmailAddress );

0 commit comments

Comments
 (0)