From d7197f1b7b71879b8acfca45d56062327757c2eb Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 3 Feb 2013 21:24:31 -0500 Subject: [PATCH] First pass at making Email\Email use Multipart classes. There is still work to be done around a more generic multipart builder, but I think that might require a separate Mime building sub-package. --- lib/Cake/Network/Email/Email.php | 36 +++++---- lib/Cake/Network/Http/FormData/Part.php | 80 ++++++++++++++++++- .../Test/TestCase/Network/Email/EmailTest.php | 30 +++++-- 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/lib/Cake/Network/Email/Email.php b/lib/Cake/Network/Email/Email.php index dcfd914904c..09bcdae8afe 100644 --- a/lib/Cake/Network/Email/Email.php +++ b/lib/Cake/Network/Email/Email.php @@ -18,6 +18,7 @@ use Cake\Core\Configure; use Cake\Error; use Cake\Log\Log; +use Cake\Network\Http\FormData\Part; use Cake\Utility\Hash; use Cake\Utility\String; use Cake\Utility\Validation; @@ -1162,7 +1163,7 @@ protected function _applyConfig($config) { } /** - * Reset all EmailComponent internal variables to be able to send out a new email. + * Reset all the internal variables to be able to send out a new email. * * @return Cake\Network\Email\Email $this */ @@ -1352,18 +1353,21 @@ protected function _attachFiles($boundary = null) { continue; } $data = $this->_readFile($fileInfo['file']); - - $msg[] = '--' . $boundary; - $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; - $msg[] = 'Content-Transfer-Encoding: base64'; - if ( + $hasDisposition = ( !isset($fileInfo['contentDisposition']) || $fileInfo['contentDisposition'] - ) { - $msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"'; + ); + $part = new Part(false, $data, false); + + if ($hasDisposition) { + $part->disposition('attachment'); + $part->filename($filename); } - $msg[] = ''; - $msg[] = $data; + $part->transferEncoding('base64'); + $part->type($fileInfo['mimetype']); + + $msg[] = '--' . $boundary; + $msg[] = (string)$part; $msg[] = ''; } return $msg; @@ -1402,12 +1406,12 @@ protected function _attachInlineFiles($boundary = null) { $data = $this->_readFile($fileInfo['file']); $msg[] = '--' . $boundary; - $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; - $msg[] = 'Content-Transfer-Encoding: base64'; - $msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>'; - $msg[] = 'Content-Disposition: inline; filename="' . $filename . '"'; - $msg[] = ''; - $msg[] = $data; + $part = new Part(false, $data, 'inline'); + $part->type($fileInfo['mimetype']); + $part->transferEncoding('base64'); + $part->contentId($fileInfo['contentId']); + $part->filename($filename); + $msg[] = (string)$part; $msg[] = ''; } return $msg; diff --git a/lib/Cake/Network/Http/FormData/Part.php b/lib/Cake/Network/Http/FormData/Part.php index 68ebd0e3b33..48108257104 100644 --- a/lib/Cake/Network/Http/FormData/Part.php +++ b/lib/Cake/Network/Http/FormData/Part.php @@ -57,6 +57,20 @@ class Part { */ protected $_filename; +/** + * The encoding used in this part. + * + * @var string + */ + protected $_transferEncoding; + +/** + * The contentId for the part + * + * @var string + */ + protected $_contentId; + /** * Constructor * @@ -71,9 +85,41 @@ public function __construct($name, $value, $disposition = 'form-data') { $this->_disposition = $disposition; } +/** + * Get/set the disposition type + * + * By passing in `false` you can disable the disposition + * header from being added. + * + * @param null|string $disposition Use null to get/string to set. + * @return mixed + */ + public function disposition($disposition = null) { + if ($disposition === null) { + return $this->_disposition; + } + $this->_disposition = $disposition; + } + +/** + * Get/set the contentId for a part. + * + * @param null|string $id The content id. + * @return mixed. + */ + public function contentId($id = null) { + if ($id === null) { + return $this->_contentId = $id; + } + $this->_contentId = $id; + } + /** * Get/set the filename. * + * Setting the filname to `false` will exclude it from the + * generated output. + * * @param null|string $filename Use null to get/string to set. * @return mixed */ @@ -97,6 +143,21 @@ public function type($type) { $this->_type = $type; } +/** + * Set the transfer-encoding for multipart. + * + * Useful when content bodies are in encodings like base64. + * + * @param null|string $type The type of encoding the value has. + * @return mixed + */ + public function transferEncoding($type) { + if ($type === null) { + return $this->_transferEncoding; + } + $this->_transferEncoding = $type; + } + /** * Convert the part into a string. * @@ -106,14 +167,25 @@ public function type($type) { */ public function __toString() { $out = ''; - $out .= sprintf('Content-Disposition: %s; name="%s"', $this->_disposition, $this->_name); - if ($this->_filename) { - $out .= '; filename="' . $this->_filename . '"'; + if ($this->_disposition) { + $out .= 'Content-Disposition: ' . $this->_disposition; + if ($this->_name) { + $out .= '; name="' . $this->_name . '"'; + } + if ($this->_filename) { + $out .= '; filename="' . $this->_filename . '"'; + } + $out .= "\r\n"; } - $out .= "\r\n"; if ($this->_type) { $out .= 'Content-Type: ' . $this->_type . "\r\n"; } + if ($this->_transferEncoding) { + $out .= 'Content-Transfer-Encoding: ' . $this->_transferEncoding . "\r\n"; + } + if ($this->_contentId) { + $out .= 'Content-ID: <' . $this->_contentId . ">\r\n"; + } $out .= "\r\n"; $out .= (string)$this->_value; return $out; diff --git a/lib/Cake/Test/TestCase/Network/Email/EmailTest.php b/lib/Cake/Test/TestCase/Network/Email/EmailTest.php index 8f822195e8c..356123daa55 100644 --- a/lib/Cake/Test/TestCase/Network/Email/EmailTest.php +++ b/lib/Cake/Test/TestCase/Network/Email/EmailTest.php @@ -848,9 +848,10 @@ public function testSendNoTemplateWithAttachments() { "\r\n" . "\r\n" . "--$boundary\r\n" . + "Content-Disposition: attachment; filename=\"basics.php\"\r\n" . "Content-Type: application/octet-stream\r\n" . "Content-Transfer-Encoding: base64\r\n" . - "Content-Disposition: attachment; filename=\"basics.php\"\r\n\r\n"; + "\r\n"; $this->assertContains($expected, $result['message']); } @@ -892,9 +893,10 @@ public function testSendNoTemplateWithAttachmentsAsBoth() { "--alt-{$boundary}--\r\n" . "\r\n" . "--$boundary\r\n" . + "Content-Disposition: attachment; filename=\"VERSION.txt\"\r\n" . "Content-Type: application/octet-stream\r\n" . "Content-Transfer-Encoding: base64\r\n" . - "Content-Disposition: attachment; filename=\"VERSION.txt\"\r\n\r\n"; + "\r\n"; $this->assertContains($expected, $result['message']); } @@ -944,10 +946,11 @@ public function testSendWithInlineAttachments() { "--alt-{$boundary}--\r\n" . "\r\n" . "--rel-$boundary\r\n" . + "Content-Disposition: inline; filename=\"cake.png\"\r\n" . "Content-Type: application/octet-stream\r\n" . "Content-Transfer-Encoding: base64\r\n" . "Content-ID: \r\n" . - "Content-Disposition: inline; filename=\"cake.png\"\r\n\r\n"; + "\r\n"; $this->assertContains($expected, $result['message']); $this->assertContains('--rel-' . $boundary . '--', $result['message']); $this->assertContains('--' . $boundary . '--', $result['message']); @@ -1280,19 +1283,32 @@ public function testSendAttachment() { $this->CakeEmail->config(array()); $this->CakeEmail->attachments(array(CAKE . 'basics.php')); $result = $this->CakeEmail->send('body'); - $this->assertContains("Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"basics.php\"", $result['message']); + $expected = "Content-Disposition: attachment; filename=\"basics.php\"\r\n" . + "Content-Type: application/octet-stream\r\n" . + "Content-Transfer-Encoding: base64\r\n"; + $this->assertContains($expected, $result['message']); $this->CakeEmail->attachments(array('my.file.txt' => CAKE . 'basics.php')); $result = $this->CakeEmail->send('body'); - $this->assertContains("Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"my.file.txt\"", $result['message']); + $expected = "Content-Disposition: attachment; filename=\"my.file.txt\"\r\n" . + "Content-Type: application/octet-stream\r\n" . + "Content-Transfer-Encoding: base64\r\n"; + $this->assertContains($expected, $result['message']); $this->CakeEmail->attachments(array('file.txt' => array('file' => CAKE . 'basics.php', 'mimetype' => 'text/plain'))); $result = $this->CakeEmail->send('body'); - $this->assertContains("Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"file.txt\"", $result['message']); + $expected = "Content-Disposition: attachment; filename=\"file.txt\"\r\n" . + "Content-Type: text/plain\r\n" . + "Content-Transfer-Encoding: base64\r\n"; + $this->assertContains($expected, $result['message']); $this->CakeEmail->attachments(array('file2.txt' => array('file' => CAKE . 'basics.php', 'mimetype' => 'text/plain', 'contentId' => 'a1b1c1'))); $result = $this->CakeEmail->send('body'); - $this->assertContains("Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-ID: \r\nContent-Disposition: inline; filename=\"file2.txt\"", $result['message']); + $expected = "Content-Disposition: inline; filename=\"file2.txt\"\r\n" . + "Content-Type: text/plain\r\n" . + "Content-Transfer-Encoding: base64\r\n" . + "Content-ID: \r\n"; + $this->assertContains($expected, $result['message']); } /**