Browse Source

Allow URL images from foreign locations for Email sending.

euromark 11 years ago
parent
commit
702d39b20f
4 changed files with 156 additions and 15 deletions
  1. 33 13
      Lib/EmailLib.php
  2. 66 0
      Lib/Utility/Utility.php
  3. 6 2
      Test/Case/Lib/EmailLibTest.php
  4. 51 0
      Test/Case/Lib/Utility/UtilityTest.php

+ 33 - 13
Lib/EmailLib.php

@@ -1,6 +1,7 @@
 <?php
 <?php
 App::uses('CakeEmail', 'Network/Email');
 App::uses('CakeEmail', 'Network/Email');
 App::uses('CakeLog', 'Log');
 App::uses('CakeLog', 'Log');
+App::uses('Utility', 'Tools.Utility');
 App::uses('MimeLib', 'Tools.Lib');
 App::uses('MimeLib', 'Tools.Lib');
 
 
 if (!defined('BR')) {
 if (!defined('BR')) {
@@ -149,15 +150,14 @@ class EmailLib extends CakeEmail {
 	 * @return mixed resource $EmailLib or string $contentId
 	 * @return mixed resource $EmailLib or string $contentId
 	 */
 	 */
 	public function addEmbeddedAttachment($file, $name = null, $contentId = null, $options = array()) {
 	public function addEmbeddedAttachment($file, $name = null, $contentId = null, $options = array()) {
-		$path = realpath($file);
 		if (empty($name)) {
 		if (empty($name)) {
 			$name = basename($file);
 			$name = basename($file);
 		}
 		}
-		if ($contentId === null && ($cid = $this->_isEmbeddedAttachment($path, $name))) {
+		if ($contentId === null && ($cid = $this->_isEmbeddedAttachment($file, $name))) {
 			return $cid;
 			return $cid;
 		}
 		}
 
 
-		$options['file'] = $path;
+		$options['file'] = $file;
 		if (empty($options['mimetype'])) {
 		if (empty($options['mimetype'])) {
 			$options['mimetype'] = $this->_getMime($file);
 			$options['mimetype'] = $this->_getMime($file);
 		}
 		}
@@ -217,15 +217,11 @@ class EmailLib extends CakeEmail {
 	 * Uses finfo_open() if availble, otherwise guesses it via file extension.
 	 * Uses finfo_open() if availble, otherwise guesses it via file extension.
 	 *
 	 *
 	 * @param string $filename
 	 * @param string $filename
-	 * @param string Mimetype
+	 * @return string Mimetype
 	 */
 	 */
 	protected function _getMime($filename) {
 	protected function _getMime($filename) {
-		if (function_exists('finfo_open')) {
-			$finfo = finfo_open(FILEINFO_MIME);
-			$mimetype = finfo_file($finfo, $filename);
-			finfo_close($finfo);
-		} else {
-			//TODO: improve
+		$mimetype = Utility::getMimeType($filename);
+		if (!$mimetype) {
 			$ext = pathinfo($filename, PATHINFO_EXTENSION);
 			$ext = pathinfo($filename, PATHINFO_EXTENSION);
 			$mimetype = $this->_getMimeByExtension($ext);
 			$mimetype = $this->_getMimeByExtension($ext);
 		}
 		}
@@ -240,6 +236,9 @@ class EmailLib extends CakeEmail {
 	 * @return string Mimetype (falls back to `application/octet-stream`)
 	 * @return string Mimetype (falls back to `application/octet-stream`)
 	 */
 	 */
 	protected function _getMimeByExtension($ext, $default = 'application/octet-stream') {
 	protected function _getMimeByExtension($ext, $default = 'application/octet-stream') {
+		if (!$ext) {
+			return $default;
+		}
 		if (!isset($this->_Mime)) {
 		if (!isset($this->_Mime)) {
 			$this->_Mime = new MimeLib();
 			$this->_Mime = new MimeLib();
 		}
 		}
@@ -251,6 +250,24 @@ class EmailLib extends CakeEmail {
 	}
 	}
 
 
 	/**
 	/**
+	 * Read the file contents and return a base64 version of the file contents.
+	 * Overwrite parent to avoid File class and file_exists to false negative existent
+	 * remove images.
+	 *
+	 * @param string $path The absolute path to the file to read.
+	 * @return string File contents in base64 encoding
+	 */
+	protected function _readFile($path) {
+		$context = stream_context_create(
+			array('http' => array('header' => 'Connection: close')));
+		$content = file_get_contents($path, 0, $context);
+		if (!$content) {
+			trigger_error('No content found for ' . $path);
+		}
+		return chunk_split(base64_encode($content));
+	}
+
+	/**
 	 * Validate if the email has the required fields necessary to make send() work.
 	 * Validate if the email has the required fields necessary to make send() work.
 	 * Assumes layouting (does not check on content to be present or if view/layout files are missing).
 	 * Assumes layouting (does not check on content to be present or if view/layout files are missing).
 	 *
 	 *
@@ -354,7 +371,8 @@ class EmailLib extends CakeEmail {
 	/**
 	/**
 	 * Add attachments to the email message
 	 * Add attachments to the email message
 	 *
 	 *
-	 * CUSTOM FIX: blob data support
+	 * CUSTOM FIX: Allow URLs
+	 * CUSTOM FIX: Blob data support
 	 *
 	 *
 	 * Attachments can be defined in a few forms depending on how much control you need:
 	 * Attachments can be defined in a few forms depending on how much control you need:
 	 *
 	 *
@@ -402,8 +420,10 @@ class EmailLib extends CakeEmail {
 					throw new SocketException(__d('cake_dev', 'File not specified.'));
 					throw new SocketException(__d('cake_dev', 'File not specified.'));
 				}
 				}
 				$fileName = $fileInfo['file'];
 				$fileName = $fileInfo['file'];
-				$fileInfo['file'] = realpath($fileInfo['file']);
-				if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
+				if (!preg_match('~^https?://~i', $fileInfo['file'])) {
+					$fileInfo['file'] = realpath($fileInfo['file']);
+				}
+				if ($fileInfo['file'] === false || !Utility::fileExists($fileInfo['file'])) {
 					throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileName));
 					throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileName));
 				}
 				}
 				if (is_int($name)) {
 				if (is_int($name)) {

+ 66 - 0
Lib/Utility/Utility.php

@@ -212,6 +212,72 @@ class Utility {
 	}
 	}
 
 
 	/**
 	/**
+	 * A more robust wrapper around for file_exists() which easily
+	 * fails to return true for existent remote files.
+	 * Per default it allows http/https images to be looked up via urlExists()
+	 * for a better result.
+	 *
+	 * @param string $file File
+	 * @return bool Success
+	 */
+	public static function fileExists($file, $pattern = '~^https?://~i') {
+		if (!preg_match($pattern, $file)) {
+			return file_exists($file);
+		}
+		return self::urlExists($file);
+	}
+
+	/**
+	 * file_exists() does not always work with URLs.
+	 * So if you check on strpos(http) === 0 you can use this
+	 * to check for URLs instead.
+	 *
+	 * @param string $url Absolute URL
+	 * @return bool Success
+	 */
+	public static function urlExists($url) {
+		$headers = @get_headers($url);
+		if ($headers && preg_match('|\b200\b|', $headers[0])) {
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Utility::getMimeType()
+	 *
+	 * @param string $file File
+	 * @return string Mime type
+	 */
+	public static function getMimeType($file) {
+		if (!function_exists('finfo_open')) {
+			throw new InternalErrorException('finfo_open() required - please enable');
+		}
+
+		// Treat non local files differently
+		$pattern = '~^https?://~i';
+		if (preg_match($pattern, $file)) {
+			$headers = @get_headers($file);
+			if (!preg_match("|\b200\b|", $headers[0])) {
+				return '';
+			}
+			foreach ($headers as $header) {
+				if (strpos($header, 'Content-Type:') === 0) {
+					return trim(substr($header, 13));
+				}
+			}
+			return '';
+		}
+
+		$finfo = finfo_open(FILEINFO_MIME);
+		$mimetype = finfo_file($finfo, $file);
+		if (($pos = strpos($mimetype, ';')) !== false) {
+			$mimetype = substr($mimetype, 0, $pos);
+		}
+		return $mimetype;
+	}
+
+	/**
 	 * Parse headers from a specific URL content.
 	 * Parse headers from a specific URL content.
 	 *
 	 *
 	 * @param string $url
 	 * @param string $url

+ 6 - 2
Test/Case/Lib/EmailLibTest.php

@@ -53,9 +53,13 @@ class EmailLibTest extends MyCakeTestCase {
 		$this->Email->template('default', 'default');
 		$this->Email->template('default', 'default');
 		$this->Email->viewVars(array('x' => 'y', 'xx' => 'yy', 'text' => ''));
 		$this->Email->viewVars(array('x' => 'y', 'xx' => 'yy', 'text' => ''));
 		$this->Email->addAttachments(array(CakePlugin::path('Tools') . 'Test' . DS . 'test_files' . DS . 'img' . DS . 'edit.gif'));
 		$this->Email->addAttachments(array(CakePlugin::path('Tools') . 'Test' . DS . 'test_files' . DS . 'img' . DS . 'edit.gif'));
+		$this->Email->addAttachments(array('http://www.spiegel.de/static/sys/v10/icons/home_v2.png'));
 
 
 		$res = $this->Email->send('xyz');
 		$res = $this->Email->send('xyz');
-		// end
+		$debug = $this->Email->getDebug();
+		$this->assertTextContains('Content-Disposition: attachment; filename="edit.gif"', $debug['message']);
+		$this->assertTextContains('Content-Disposition: attachment; filename="home_v2.png"', $debug['message']);
+
 		if ($error = $this->Email->getError()) {
 		if ($error = $this->Email->getError()) {
 			$this->out($error);
 			$this->out($error);
 		}
 		}
@@ -270,7 +274,7 @@ class EmailLibTest extends MyCakeTestCase {
 		$expected = array(
 		$expected = array(
 			'hotel.png' => array(
 			'hotel.png' => array(
 				'file' => $file,
 				'file' => $file,
-				'mimetype' => 'image/png; charset=binary',
+				'mimetype' => 'image/png',
 				'contentId' => $cid
 				'contentId' => $cid
 			)
 			)
 		);
 		);

+ 51 - 0
Test/Case/Lib/Utility/UtilityTest.php

@@ -193,6 +193,57 @@ class UtilityTest extends MyCakeTestCase {
 	}
 	}
 
 
 	/**
 	/**
+	 * UtilityTest::testGetMimeType()
+	 *
+	 * @covers Utility::getMimeType
+	 * @return void
+	 */
+	public function testGetMimeType() {
+		$res = Utility::getMimeType('http://www.spiegel.de/static/sys/v10/icons/home_v2.png');
+		$this->assertEquals('image/png', $res);
+
+		$res = Utility::getMimeType('http://www.spiegel.de/static/sys/v10/icons/home_v2_inexistent.png');
+		$this->assertEquals('', $res);
+
+		$res = Utility::getMimeType(CakePlugin::path('Tools') . 'Test' . DS . 'test_files' . DS . 'img' . DS . 'hotel.jpg');
+		$this->assertEquals('image/jpeg', $res);
+	}
+
+	/**
+	 * UtilityTest::testFileExists()
+	 *
+	 * @covers Utility::fileExists
+	 * @return void
+	 */
+	public function testFileExists() {
+		$res = Utility::fileExists('http://www.spiegel.de/static/sys/v10/icons/home_v2.png');
+		$this->assertTrue($res);
+
+		$res = Utility::fileExists(CakePlugin::path('Tools') . 'Test' . DS . 'test_files' . DS . 'img' . DS . 'hotel.jpg');
+		$this->assertTrue($res);
+
+		$res = Utility::fileExists('http://www.spiegel.de/static/sys/v10/icons/home_v2_inexistent.png');
+		$this->assertFalse($res);
+
+		$res = Utility::fileExists(CakePlugin::path('Tools') . 'Test' . DS . 'test_files' . DS . 'img' . DS . 'fooooo.jpg');
+		$this->assertFalse($res);
+	}
+
+	/**
+	 * UtilityTest::testUrlExists()
+	 *
+	 * @covers Utility::urlExists
+	 * @return void
+	 */
+	public function testUrlExists() {
+		$res = Utility::urlExists('http://www.spiegel.de');
+		$this->assertTrue($res);
+
+		$res = Utility::urlExists('http://www.spiegel.de/some/inexistent.img');
+		$this->assertFalse($res);
+	}
+
+	/**
 	 * UtilityTest::testGetReferer()
 	 * UtilityTest::testGetReferer()
 	 *
 	 *
 	 * @covers Utility::getReferer
 	 * @covers Utility::getReferer