Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/upstream/longlines'
Browse files Browse the repository at this point in the history
  • Loading branch information
Synchro committed Apr 29, 2015
2 parents ab36b53 + 1365c49 commit 4e8a80a
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 8 deletions.
55 changes: 47 additions & 8 deletions class.phpmailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,13 @@ class PHPMailer
*/
protected $exceptions = false;

/**
* Unique ID used for message ID and boundaries.
* @type string
* @access protected
*/
protected $uniqueid = '';

/**
* Error severity: message only, continue processing.
*/
Expand All @@ -584,6 +591,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 @@ -1016,8 +1029,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 @@ -1683,12 +1697,6 @@ public function createHeader()
{
$result = '';

// Set the boundaries
$uniq_id = md5(uniqid(time()));
$this->boundary[1] = 'b1_' . $uniq_id;
$this->boundary[2] = 'b2_' . $uniq_id;
$this->boundary[3] = 'b3_' . $uniq_id;

if ($this->MessageDate == '') {
$this->MessageDate = self::rfcDate();
}
Expand Down Expand Up @@ -1740,7 +1748,7 @@ public function createHeader()
if ($this->MessageID != '') {
$this->lastMessageID = $this->MessageID;
} else {
$this->lastMessageID = sprintf('<%s@%s>', $uniq_id, $this->ServerHostname());
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->ServerHostname());
}
$result .= $this->headerLine('Message-ID', $this->lastMessageID);
$result .= $this->headerLine('X-Priority', $this->Priority);
Expand Down Expand Up @@ -1850,6 +1858,11 @@ public function getSentMIMEMessage()
public function createBody()
{
$body = '';
//Create unique IDs and preset boundaries
$this->uniqueid = md5(uniqid(time()));
$this->boundary[1] = 'b1_' . $this->uniqueid;
$this->boundary[2] = 'b2_' . $this->uniqueid;
$this->boundary[3] = 'b3_' . $this->uniqueid;

if ($this->sign_key_file) {
$body .= $this->getMailMIME() . $this->LE;
Expand All @@ -1859,16 +1872,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';
}
//Use this as a preamble in all multipart message types
$mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
switch ($this->message_type) {
Expand Down Expand Up @@ -3471,6 +3498,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
76 changes: 76 additions & 0 deletions test/phpmailerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,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 @@ -1557,6 +1608,31 @@ 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');
$this->Mail->isHTML(false);
$this->Mail->Subject .= ": Line length test";
$this->Mail->CharSet = 'UTF-8';
$this->Mail->Encoding = '8bit';
$this->Mail->Body = $oklen . $badlen . $oklen . $badlen;
$this->buildBody();
$this->assertTrue($this->Mail->send(), $this->Mail->ErrorInfo);
$this->assertEquals('quoted-printable', $this->Mail->Encoding, 'Long line did not override transfer encoding');
}

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

0 comments on commit 4e8a80a

Please sign in to comment.