Skip to content

Commit

Permalink
Deal with excessively long lines, see PHPMailer#314
Browse files Browse the repository at this point in the history
  • Loading branch information
Synchro committed Nov 14, 2014
1 parent de5c035 commit aa8e499
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 3 deletions.
35 changes: 34 additions & 1 deletion class.phpmailer.php
Expand Up @@ -570,6 +570,12 @@ class PHPMailer
*/
const CRLF = "\r\n";

/**
* The maximum line length allowed by RFC 2822 section 2.1.1
* @type integer
*/
const MAX_LINE_LENGTH = 998;

/**
* Constructor.
* @param boolean $exceptions Should we throw external exceptions?
Expand Down Expand Up @@ -1001,8 +1007,9 @@ public function preSend()
throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
}

$this->MIMEHeader = $this->createHeader();
//Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
$this->MIMEBody = $this->createBody();
$this->MIMEHeader = $this->createHeader();

// To capture the complete message when using mail(), create
// an extra header list which createHeader() doesn't fold in
Expand Down Expand Up @@ -1823,16 +1830,30 @@ public function createBody()

$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
$bodyEncoding = '7bit';
$bodyCharSet = 'us-ascii';
}
//If lines are too long, change to quoted-printable transfer encoding
if (self::hasLineLongerThanMax($this->Body)) {
$this->Encoding = 'quoted-printable';
$bodyEncoding = 'quoted-printable';
$bodyCharSet = 'us-ascii'; //qp always fits into ascii
}

$altBodyEncoding = $this->Encoding;
$altBodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
$altBodyEncoding = '7bit';
$altBodyCharSet = 'us-ascii';
}
//If lines are too long, change to quoted-printable transfer encoding
if (self::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = 'quoted-printable';
$altBodyCharSet = 'us-ascii';
}
switch ($this->message_type) {
case 'inline':
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
Expand Down Expand Up @@ -3371,6 +3392,18 @@ public function DKIM_Add($headers_line, $subject, $body)
return $dkimhdrs . $signed . "\r\n";
}

/**
* Detect if a string contains a line longer than the maximum line length allowed.
* @param $str
* @return boolean
* @static
*/
public static function hasLineLongerThanMax($str)
{
//+2 to include CRLF line break for a 1000 total
return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
}

/**
* Allows for public read access to 'to' property.
* @access public
Expand Down
69 changes: 67 additions & 2 deletions test/phpmailerTest.php
Expand Up @@ -146,12 +146,12 @@ public function buildBody()

// Determine line endings for message
if ($this->Mail->ContentType == 'text/html' || strlen($this->Mail->AltBody) > 0) {
$eol = '<br/>';
$eol = "<br/>". PHPMailer::CRLF;
$bullet = '<li>';
$bullet_start = '<ul>';
$bullet_end = '</ul>';
} else {
$eol = "\n";
$eol = PHPMailer::CRLF;
$bullet = ' - ';
$bullet_start = '';
$bullet_end = '';
Expand Down Expand Up @@ -1040,6 +1040,57 @@ public function testEmptyBody()
$this->assertFalse($this->Mail->send(), $this->Mail->ErrorInfo);
}

/**
* Test constructing a message that contains lines that are too long for RFC compliance.
*/
public function testLongBody()
{
$oklen = str_repeat(str_repeat('0', PHPMailer::MAX_LINE_LENGTH) . PHPMailer::CRLF, 10);
$badlen = str_repeat(str_repeat('1', PHPMailer::MAX_LINE_LENGTH + 1) . PHPMailer::CRLF, 2);

$this->Mail->Body = "This message contains lines that are too long.".
PHPMailer::CRLF . PHPMailer::CRLF . $oklen . $badlen . $oklen;
$this->assertTrue(
PHPMailer::hasLineLongerThanMax($this->Mail->Body),
'Test content does not contain long lines!'
);
$this->buildBody();
$this->Mail->Encoding = '8bit';
$this->Mail->preSend();
$message = $this->Mail->getSentMIMEMessage();
$this->assertFalse(PHPMailer::hasLineLongerThanMax($message), 'Long line not corrected.');
$this->assertContains(
'Content-Transfer-Encoding: quoted-printable',
$message,
'Long line did not cause transfer encoding switch.'
);
}

/**
* Test constructing a message that does NOT contain lines that are too long for RFC compliance.
*/
public function testShortBody()
{
$oklen = str_repeat(str_repeat('0', PHPMailer::MAX_LINE_LENGTH) . PHPMailer::CRLF, 10);

$this->Mail->Body = "This message does not contain lines that are too long.".
PHPMailer::CRLF . PHPMailer::CRLF . $oklen;
$this->assertFalse(
PHPMailer::hasLineLongerThanMax($this->Mail->Body),
'Test content contains long lines!'
);
$this->buildBody();
$this->Mail->Encoding = '8bit';
$this->Mail->preSend();
$message = $this->Mail->getSentMIMEMessage();
$this->assertFalse(PHPMailer::hasLineLongerThanMax($message), 'Long line not corrected.');
$this->assertNotContains(
'Content-Transfer-Encoding: quoted-printable',
$message,
'Short line caused transfer encoding switch.'
);
}

/**
* Test keepalive (sending multiple messages in a single connection)
*/
Expand Down Expand Up @@ -1288,6 +1339,20 @@ public function testLineBreaks()
$this->assertEquals($target, PHPMailer::normalizeBreaks($mixedsrc), 'Mixed break reformatting failed');
}

/**
* Test line length detection
*/
public function testLineLength()
{
$oklen = str_repeat(str_repeat('0', PHPMailer::MAX_LINE_LENGTH)."\r\n", 10);
$badlen = str_repeat(str_repeat('1', PHPMailer::MAX_LINE_LENGTH + 1) . "\r\n", 2);
$this->assertTrue(PHPMailer::hasLineLongerThanMax($badlen), 'Long line not detected (only)');
$this->assertTrue(PHPMailer::hasLineLongerThanMax($oklen . $badlen), 'Long line not detected (first)');
$this->assertTrue(PHPMailer::hasLineLongerThanMax($badlen . $oklen), 'Long line not detected (last)');
$this->assertTrue(PHPMailer::hasLineLongerThanMax($oklen . $badlen . $oklen), 'Long line not detected (middle)');
$this->assertFalse(PHPMailer::hasLineLongerThanMax($oklen), 'Long line false positive');
}

/**
* Test setting and retrieving message ID
*/
Expand Down

0 comments on commit aa8e499

Please sign in to comment.