From f7033d68b6c96c7c574e2732c7b0b21a540d438e Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Mon, 2 May 2016 18:27:44 +0200 Subject: [PATCH] Remove obsolete QP-encode function Improve & fix data URL handling Remove old class loader from phpunit bootstrap Improve test coverage Remove some unnecessary trailing line breaks from attachment MIME structure --- examples/contentsutf8.html | 2 ++ src/PHPMailer.php | 73 ++++++++++++++++++++++---------------- test/bootstrap.php | 5 --- test/phpmailerTest.php | 55 +++++++++++++++++++++++----- 4 files changed, 91 insertions(+), 44 deletions(-) diff --git a/examples/contentsutf8.html b/examples/contentsutf8.html index d9fa1d21..bc3ecafa 100644 --- a/examples/contentsutf8.html +++ b/examples/contentsutf8.html @@ -16,6 +16,8 @@

Armenian text: Հաղորդագրությունը դատարկ է

Czech text: Prázdné tělo zprávy

Emoji: 🐿💥⛷🚀📱❤️

+

Image data URL (base64)#

+

Image data URL (URL-encoded)#

diff --git a/src/PHPMailer.php b/src/PHPMailer.php index b8e9454b..e6591883 100644 --- a/src/PHPMailer.php +++ b/src/PHPMailer.php @@ -2513,13 +2513,13 @@ class PHPMailer if ($this->isError()) { return ''; } - $mime[] = $this->LE . $this->LE; + $mime[] = $this->LE; } else { $mime[] = $this->encodeFile($path, $encoding); if ($this->isError()) { return ''; } - $mime[] = $this->LE . $this->LE; + $mime[] = $this->LE; } } } @@ -2728,23 +2728,12 @@ class PHPMailer * According to RFC2045 section 6.7. * @access public * @param string $string The text to encode - * @param integer $line_max Number of chars allowed on a line before wrapping + * @param integer $line_max Number of chars allowed on a line before wrapping, for compatibility with old versions * @return string - * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment */ public function encodeQP($string, $line_max = 76) { - // Use native function if it's available (>= PHP5.3) - if (function_exists('quoted_printable_encode')) { - return quoted_printable_encode($string); - } - // Fall back to a pure PHP implementation - $string = str_replace( - ['%20', '%0D%0A.', '%0D%0A', '%'], - [' ', "\r\n=2E", "\r\n", '='], - rawurlencode($string) - ); - return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); + return quoted_printable_encode($string); } /** @@ -2918,6 +2907,22 @@ class PHPMailer return true; } + /** + * Check if an embedded attachment is present with this cid. + * @access protected + * @param $cid + * @return bool + */ + protected function cidExists($cid) + { + foreach ($this->attachment as $attachment) { + if ('inline' == $attachment[6] and $cid == $attachment[7]) { + return true; + } + } + return false; + } + /** * Check if an inline attachment is present. * @access public @@ -3214,21 +3219,28 @@ class PHPMailer if (array_key_exists(2, $images)) { foreach ($images[2] as $imgindex => $url) { // Convert data URIs into embedded images - if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { - $data = substr($url, strpos($url, ',')); - if ($match[2]) { - $data = base64_decode($data); + //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) { + if (count($match) == 4 and 'base64' == $match[2]) { + $data = base64_decode($match[3]); + } elseif ('' == $match[2]) { + $data = rawurldecode($match[3]); } else { - $data = rawurldecode($data); + //Not recognised so leave it alone + continue; } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { - $message = str_replace( - $images[0][$imgindex], - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); + //Hash the decoded data, not the URL so that the same data-URI image used in multiple places + //will only be embedded once, even if it used a different encoding + $cid = md5($data) . '@phpmailer.0'; // RFC2392 S 2 + + if (!$this->cidExists($cid)) { + $this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1]); } + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); } elseif (substr($url, 0, 4) !== 'cid:' and !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) { // Do not change urls for absolute images (thanks to corvuscorax) // Do not change urls that are already inline images @@ -3433,14 +3445,13 @@ class PHPMailer if (false !== $qpos) { $filename = substr($filename, 0, $qpos); } - $pathinfo = self::mb_pathinfo($filename); - return self::_mime_types($pathinfo['extension']); + $ext = self::mb_pathinfo($filename, PATHINFO_EXTENSION); + return self::_mime_types($ext); } /** * Multi-byte-safe pathinfo replacement. - * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. - * Works similarly to the one in PHP >= 5.2.0 + * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe * @link http://www.php.net/manual/en/function.pathinfo.php#107461 * @param string $path A filename or path, does not need to exist as a file * @param integer|string $options Either a PATHINFO_* constant, diff --git a/test/bootstrap.php b/test/bootstrap.php index 1b9cdec2..e2141250 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -5,8 +5,3 @@ ini_set('sendmail_path', '/usr/sbin/sendmail -t -i '); require_once '../vendor/autoload.php'; -spl_autoload_register( - function ($class) { - require_once strtr($class, '\\_', '//') . '.php'; - } -); diff --git a/test/phpmailerTest.php b/test/phpmailerTest.php index cf7e7bda..eb678019 100644 --- a/test/phpmailerTest.php +++ b/test/phpmailerTest.php @@ -851,7 +851,7 @@ EOT; public function testHtmlUtf8() { $this->Mail->isHTML(true); - $this->Mail->Subject .= ": UTF-8 HTML"; + $this->Mail->Subject .= ": UTF-8 HTML Пустое тело сообщения"; $this->Mail->CharSet = 'UTF-8'; $this->Mail->Body = <<Mail->AltBody = ''; //Uses internal HTML to text conversion $this->Mail->msgHTML($message, realpath($this->INCLUDE_DIR . '/examples')); - $this->Mail->Subject .= ': msgHTML'; - $this->Mail->addAddress('user@example.com'); + $sub = $this->Mail->Subject . ': msgHTML'; + $this->Mail->Subject .= $sub; $this->assertNotEmpty($this->Mail->Body, 'Body not set by msgHTML'); $this->assertNotEmpty($this->Mail->AltBody, 'AltBody not set by msgHTML'); @@ -957,9 +957,7 @@ EOT; return strtoupper(strip_tags($html)); } ); - $this->Mail->Subject .= ' + custom html2text'; - $this->assertNotEmpty($this->Mail->AltBody, 'Custom AltBody not set by msgHTML'); - + $this->Mail->Subject = $sub . ' + custom html2text'; $this->assertTrue($this->Mail->send(), $this->Mail->ErrorInfo); } @@ -1000,9 +998,9 @@ EOT; if (!$this->Mail->addStringEmbeddedImage( file_get_contents(realpath($this->INCLUDE_DIR . '/examples/images/phpmailer_mini.png')), md5('phpmailer_mini.png').'@phpmailer.0', - '', //intentionally empty name + '', //Intentionally empty name 'base64', - 'image/png', + '', //Intentionally empty MIME type 'inline' )) { $this->assertTrue(false, $this->Mail->ErrorInfo); @@ -1544,6 +1542,11 @@ EOT; $this->Mail->encodeQ("Nov\xc3\xa1=", 'text'), 'Q Encoding (text) failed 2' ); + + $this->assertEquals($this->Mail->encodeString('hello', 'binary'), 'hello', 'Binary encoding changed input'); + $this->Mail->ErrorInfo = ''; + $this->Mail->encodeString('hello', 'asdfghjkl'); + $this->assertNotEmpty($this->Mail->ErrorInfo, 'Invalid encoding not detected'); } /** @@ -1854,6 +1857,11 @@ EOT; '/mnt/files', 'Dirname path element not matched' ); + $this->assertEquals( + PHPMailer::mb_pathinfo($a, PATHINFO_BASENAME), + '飛兒樂 團光茫.mp3', + 'Basename path element not matched' + ); $this->assertEquals(PHPMailer::mb_pathinfo($a, 'filename'), '飛兒樂 團光茫', 'Filename path element not matched'); $a = 'c:\mnt\files\飛兒樂 團光茫.mp3'; $q = PHPMailer::mb_pathinfo($a); @@ -1861,7 +1869,37 @@ EOT; $this->assertEquals($q['basename'], '飛兒樂 團光茫.mp3', 'Windows basename not matched'); $this->assertEquals($q['extension'], 'mp3', 'Windows extension not matched'); $this->assertEquals($q['filename'], '飛兒樂 團光茫', 'Windows filename not matched'); + + $this->assertEquals( + PHPMailer::filenameToType('abc.jpg?xyz=1'), + 'image/jpeg', + 'Query string not ignored in filename' + ); + $this->assertEquals( + PHPMailer::filenameToType('abc.xyzpdq'), + 'application/octet-stream', + 'Default MIME type not applied to unknown extension' + ); + + //Line break normalization + $eol = $this->Mail->LE; + $b1 = "1\r2\r3\r"; + $b2 = "1\n2\n3\n"; + $b3 = "1\r\n2\r3\n"; + $this->Mail->LE = "\n"; + $t1 = "1{$this->Mail->LE}2{$this->Mail->LE}3{$this->Mail->LE}"; + $this->assertEquals($this->Mail->fixEOL($b1), $t1, 'Failed to normalize line breaks (1)'); + $this->assertEquals($this->Mail->fixEOL($b2), $t1, 'Failed to normalize line breaks (2)'); + $this->assertEquals($this->Mail->fixEOL($b3), $t1, 'Failed to normalize line breaks (3)'); + $this->Mail->LE = "\r\n"; + $t1 = "1{$this->Mail->LE}2{$this->Mail->LE}3{$this->Mail->LE}"; + $this->assertEquals($this->Mail->fixEOL($b1), $t1, 'Failed to normalize line breaks (4)'); + $this->assertEquals($this->Mail->fixEOL($b2), $t1, 'Failed to normalize line breaks (5)'); + $this->assertEquals($this->Mail->fixEOL($b3), $t1, 'Failed to normalize line breaks (6)'); + $this->Mail->LE = $eol; + } + public function testBadSMTP() { $this->Mail->smtpConnect(); @@ -2087,6 +2125,7 @@ EOT; /** * Test SMTP host connections. * This test can take a long time, so run it last + * @group slow */ public function testSmtpConnect() {