MessageTest.php 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE
  9. * Redistributions of files must retain the above copyright notice
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 4.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Mailer;
  17. use Cake\Core\Configure;
  18. use Cake\Mailer\Message;
  19. use Cake\Mailer\Transport\DebugTransport;
  20. use Cake\TestSuite\TestCase;
  21. use InvalidArgumentException;
  22. use Laminas\Diactoros\UploadedFile;
  23. use PHPUnit\Framework\Attributes\DataProvider;
  24. use ReflectionClass;
  25. use TestApp\Mailer\TestMessage;
  26. use function Cake\Core\env;
  27. /**
  28. * MessageTest class
  29. */
  30. class MessageTest extends TestCase
  31. {
  32. /**
  33. * @var \Cake\Mailer\Message
  34. */
  35. protected $message;
  36. public function setUp(): void
  37. {
  38. parent::setUp();
  39. $this->message = new TestMessage();
  40. }
  41. /**
  42. * testWrap method
  43. */
  44. public function testWrap(): void
  45. {
  46. $renderer = new TestMessage();
  47. $text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac turpis orci, non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.';
  48. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  49. $expected = [
  50. 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac turpis orci,',
  51. 'non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.',
  52. '',
  53. ];
  54. $this->assertSame($expected, $result);
  55. $text = 'Lorem ipsum dolor sit amet, consectetur < adipiscing elit. Donec ac turpis orci, non commodo odio. Morbi nibh nisi, vehicula > pellentesque accumsan amet.';
  56. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  57. $expected = [
  58. 'Lorem ipsum dolor sit amet, consectetur < adipiscing elit. Donec ac turpis',
  59. 'orci, non commodo odio. Morbi nibh nisi, vehicula > pellentesque accumsan',
  60. 'amet.',
  61. '',
  62. ];
  63. $this->assertSame($expected, $result);
  64. $text = '<p>Lorem ipsum dolor sit amet,<br> consectetur adipiscing elit.<br> Donec ac turpis orci, non <b>commodo</b> odio. <br /> Morbi nibh nisi, vehicula pellentesque accumsan amet.<hr></p>';
  65. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  66. $expected = [
  67. '<p>Lorem ipsum dolor sit amet,<br> consectetur adipiscing elit.<br> Donec ac',
  68. 'turpis orci, non <b>commodo</b> odio. <br /> Morbi nibh nisi, vehicula',
  69. 'pellentesque accumsan amet.<hr></p>',
  70. '',
  71. ];
  72. $this->assertSame($expected, $result);
  73. $text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac <a href="http://cakephp.org">turpis</a> orci, non commodo odio. Morbi nibh nisi, vehicula pellentesque accumsan amet.';
  74. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  75. $expected = [
  76. 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac',
  77. '<a href="http://cakephp.org">turpis</a> orci, non commodo odio. Morbi nibh',
  78. 'nisi, vehicula pellentesque accumsan amet.',
  79. '',
  80. ];
  81. $this->assertSame($expected, $result);
  82. $text = 'Lorem ipsum <a href="http://www.cakephp.org/controller/action/param1/param2" class="nice cool fine amazing awesome">ok</a>';
  83. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  84. $expected = [
  85. 'Lorem ipsum',
  86. '<a href="http://www.cakephp.org/controller/action/param1/param2" class="nice cool fine amazing awesome">',
  87. 'ok</a>',
  88. '',
  89. ];
  90. $this->assertSame($expected, $result);
  91. $text = 'Lorem ipsum withonewordverybigMorethanthelineshouldsizeofrfcspecificationbyieeeavailableonieeesite ok.';
  92. $result = $renderer->doWrap($text, Message::LINE_LENGTH_SHOULD);
  93. $expected = [
  94. 'Lorem ipsum',
  95. 'withonewordverybigMorethanthelineshouldsizeofrfcspecificationbyieeeavailableonieeesite',
  96. 'ok.',
  97. '',
  98. ];
  99. $this->assertSame($expected, $result);
  100. /** @see https://github.com/cakephp/cakephp/issues/14459 */
  101. $line = 'some text <b>with html</b>';
  102. $trailing = str_repeat('X', Message::LINE_LENGTH_MUST - strlen($line));
  103. $result = $renderer->doWrap($line . $trailing, Message::LINE_LENGTH_MUST);
  104. $expected = [
  105. 'some text <b>with',
  106. 'html</b>' . $trailing,
  107. '',
  108. ];
  109. $this->assertSame($expected, $result);
  110. }
  111. /**
  112. * testWrapLongLine()
  113. */
  114. public function testWrapLongLine(): void
  115. {
  116. $transort = new DebugTransport();
  117. $message = '<a href="http://cakephp.org">' . str_repeat('x', Message::LINE_LENGTH_MUST) . '</a>';
  118. $this->message->setFrom('cake@cakephp.org');
  119. $this->message->setTo('cake@cakephp.org');
  120. $this->message->setSubject('Wordwrap Test');
  121. $this->message->setBodyText($message);
  122. $result = $transort->send($this->message);
  123. $expected = "<a\r\n" . 'href="http://cakephp.org">' . str_repeat('x', Message::LINE_LENGTH_MUST - 26) . "\r\n" .
  124. str_repeat('x', 26) . "\r\n</a>\r\n\r\n";
  125. $this->assertSame($expected, $result['message']);
  126. $this->assertLineLengths($result['message']);
  127. $str1 = 'a ';
  128. $str2 = ' b';
  129. $length = strlen($str1) + strlen($str2);
  130. $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length - 1) . $str2;
  131. $this->message->setBodyText($message);
  132. $result = $transort->send($this->message);
  133. $expected = "{$message}\r\n\r\n";
  134. $this->assertSame($expected, $result['message']);
  135. $this->assertLineLengths($result['message']);
  136. $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length) . $str2;
  137. $this->message->setBodyText($message);
  138. $result = $transort->send($this->message);
  139. $expected = "{$message}\r\n\r\n";
  140. $this->assertSame($expected, $result['message']);
  141. $this->assertLineLengths($result['message']);
  142. $message = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1) . $str2;
  143. $this->message->setBodyText($message);
  144. $result = $transort->send($this->message);
  145. $expected = $str1 . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1) . sprintf("\r\n%s\r\n\r\n", trim($str2));
  146. $this->assertSame($expected, $result['message']);
  147. $this->assertLineLengths($result['message']);
  148. }
  149. /**
  150. * testWrapWithTagsAcrossLines()
  151. */
  152. public function testWrapWithTagsAcrossLines(): void
  153. {
  154. $str = <<<HTML
  155. <table>
  156. <th align="right" valign="top"
  157. style="font-weight: bold">The tag is across multiple lines</th>
  158. </table>
  159. HTML;
  160. $message = $str . str_repeat('x', Message::LINE_LENGTH_MUST + 1);
  161. $this->message->setFrom('cake@cakephp.org');
  162. $this->message->setTo('cake@cakephp.org');
  163. $this->message->setSubject('Wordwrap Test');
  164. $this->message->setBodyText($message);
  165. $result = (new DebugTransport())->send($this->message);
  166. $message = str_replace("\r\n", "\n", substr($message, 0, -9));
  167. $message = str_replace("\n", "\r\n", $message);
  168. $expected = "{$message}\r\nxxxxxxxxx\r\n\r\n";
  169. $this->assertSame($expected, $result['message']);
  170. $this->assertLineLengths($result['message']);
  171. }
  172. /**
  173. * CakeEmailTest::testWrapIncludeLessThanSign()
  174. */
  175. public function testWrapIncludeLessThanSign(): void
  176. {
  177. $str = 'foo<bar';
  178. $length = strlen($str);
  179. $message = $str . str_repeat('x', Message::LINE_LENGTH_MUST - $length + 1);
  180. $this->message->setFrom('cake@cakephp.org');
  181. $this->message->setTo('cake@cakephp.org');
  182. $this->message->setSubject('Wordwrap Test');
  183. $this->message->setBodyText($message);
  184. $result = (new DebugTransport())->send($this->message);
  185. $message = substr($message, 0, -1);
  186. $expected = "{$message}\r\nx\r\n\r\n";
  187. $this->assertSame($expected, $result['message']);
  188. $this->assertLineLengths($result['message']);
  189. }
  190. /**
  191. * CakeEmailTest::testWrapForJapaneseEncoding()
  192. */
  193. public function testWrapForJapaneseEncoding(): void
  194. {
  195. $this->skipIf(!function_exists('mb_convert_encoding'));
  196. $message = mb_convert_encoding('受け付けました', 'iso-2022-jp', 'UTF-8');
  197. $this->message->setFrom('cake@cakephp.org');
  198. $this->message->setTo('cake@cakephp.org');
  199. $this->message->setSubject('Wordwrap Test');
  200. $this->message->setCharset('iso-2022-jp');
  201. $this->message->setHeaderCharset('iso-2022-jp');
  202. $this->message->setBodyText($message);
  203. $result = (new DebugTransport())->send($this->message);
  204. $expected = "{$message}\r\n\r\n";
  205. $this->assertSame($expected, $result['message']);
  206. }
  207. /**
  208. * testHeaders method
  209. */
  210. public function testHeaders(): void
  211. {
  212. $this->message->setMessageId(false);
  213. $this->message->setHeaders(['X-Something' => 'nice']);
  214. $expected = [
  215. 'X-Something' => 'nice',
  216. 'Date' => date(DATE_RFC2822),
  217. 'MIME-Version' => '1.0',
  218. 'Content-Type' => 'text/plain; charset=UTF-8',
  219. 'Content-Transfer-Encoding' => '8bit',
  220. ];
  221. $this->assertSame($expected, $this->message->getHeaders());
  222. $this->message->addHeaders(['X-Something' => 'very nice', 'X-Other' => 'cool']);
  223. $expected = [
  224. 'X-Something' => 'very nice',
  225. 'X-Other' => 'cool',
  226. 'Date' => date(DATE_RFC2822),
  227. 'MIME-Version' => '1.0',
  228. 'Content-Type' => 'text/plain; charset=UTF-8',
  229. 'Content-Transfer-Encoding' => '8bit',
  230. ];
  231. $this->assertSame($expected, $this->message->getHeaders());
  232. $this->message->setFrom('cake@cakephp.org');
  233. $this->assertSame($expected, $this->message->getHeaders());
  234. $expected = [
  235. 'From' => 'cake@cakephp.org',
  236. 'X-Something' => 'very nice',
  237. 'X-Other' => 'cool',
  238. 'Date' => date(DATE_RFC2822),
  239. 'MIME-Version' => '1.0',
  240. 'Content-Type' => 'text/plain; charset=UTF-8',
  241. 'Content-Transfer-Encoding' => '8bit',
  242. ];
  243. $this->assertSame($expected, $this->message->getHeaders(['from' => true]));
  244. $this->message->setFrom('cake@cakephp.org', 'CakePHP');
  245. $expected['From'] = 'CakePHP <cake@cakephp.org>';
  246. $this->assertSame($expected, $this->message->getHeaders(['from' => true]));
  247. $this->message->setTo(['cake@cakephp.org', 'php@cakephp.org' => 'CakePHP']);
  248. $expected = [
  249. 'From' => 'CakePHP <cake@cakephp.org>',
  250. 'To' => 'cake@cakephp.org, CakePHP <php@cakephp.org>',
  251. 'X-Something' => 'very nice',
  252. 'X-Other' => 'cool',
  253. 'Date' => date(DATE_RFC2822),
  254. 'MIME-Version' => '1.0',
  255. 'Content-Type' => 'text/plain; charset=UTF-8',
  256. 'Content-Transfer-Encoding' => '8bit',
  257. ];
  258. $this->assertSame($expected, $this->message->getHeaders(['from' => true, 'to' => true]));
  259. $this->message->setCharset('ISO-2022-JP');
  260. $expected = [
  261. 'From' => 'CakePHP <cake@cakephp.org>',
  262. 'To' => 'cake@cakephp.org, CakePHP <php@cakephp.org>',
  263. 'X-Something' => 'very nice',
  264. 'X-Other' => 'cool',
  265. 'Date' => date(DATE_RFC2822),
  266. 'MIME-Version' => '1.0',
  267. 'Content-Type' => 'text/plain; charset=ISO-2022-JP',
  268. 'Content-Transfer-Encoding' => '7bit',
  269. ];
  270. $this->assertSame($expected, $this->message->getHeaders(['from' => true, 'to' => true]));
  271. $result = $this->message->setHeaders([]);
  272. $this->assertInstanceOf(Message::class, $result);
  273. $this->message->setHeaders(['o:tag' => ['foo']]);
  274. $this->message->addHeaders(['o:tag' => ['bar']]);
  275. $result = $this->message->getHeaders();
  276. $this->assertEquals(['foo', 'bar'], $result['o:tag']);
  277. }
  278. /**
  279. * testHeadersString method
  280. */
  281. public function testHeadersString(): void
  282. {
  283. $this->message->setMessageId(false);
  284. $this->message->setHeaders(['X-Something' => 'nice']);
  285. $expected = [
  286. 'X-Something: nice',
  287. 'Date: ' . date(DATE_RFC2822),
  288. 'MIME-Version: 1.0',
  289. 'Content-Type: text/plain; charset=UTF-8',
  290. 'Content-Transfer-Encoding: 8bit',
  291. ];
  292. $this->assertSame(implode("\r\n", $expected), $this->message->getHeadersString());
  293. }
  294. /**
  295. * testFrom method
  296. */
  297. public function testFrom(): void
  298. {
  299. $this->assertSame([], $this->message->getFrom());
  300. $this->message->setFrom('cake@cakephp.org');
  301. $expected = ['cake@cakephp.org' => 'cake@cakephp.org'];
  302. $this->assertSame($expected, $this->message->getFrom());
  303. $this->message->setFrom(['cake@cakephp.org']);
  304. $this->assertSame($expected, $this->message->getFrom());
  305. $this->message->setFrom('cake@cakephp.org', 'CakePHP');
  306. $expected = ['cake@cakephp.org' => 'CakePHP'];
  307. $this->assertSame($expected, $this->message->getFrom());
  308. $result = $this->message->setFrom(['cake@cakephp.org' => 'CakePHP']);
  309. $this->assertSame($expected, $this->message->getFrom());
  310. $this->assertSame($this->message, $result);
  311. $this->expectException(InvalidArgumentException::class);
  312. $this->message->setFrom(['cake@cakephp.org' => 'CakePHP', 'fail@cakephp.org' => 'From can only be one address']);
  313. }
  314. /**
  315. * Test that from addresses using colons work.
  316. */
  317. public function testFromWithColonsAndQuotes(): void
  318. {
  319. $address = [
  320. 'info@example.com' => '70:20:00 " Forum',
  321. ];
  322. $this->message->setFrom($address);
  323. $this->assertEquals($address, $this->message->getFrom());
  324. $result = $this->message->getHeadersString(['from']);
  325. $this->assertStringContainsString('From: "70:20:00 \" Forum" <info@example.com>', $result);
  326. }
  327. /**
  328. * testSender method
  329. */
  330. public function testSender(): void
  331. {
  332. $this->message->reset();
  333. $this->assertSame([], $this->message->getSender());
  334. $this->message->setSender('cake@cakephp.org', 'Name');
  335. $expected = ['cake@cakephp.org' => 'Name'];
  336. $this->assertSame($expected, $this->message->getSender());
  337. $headers = $this->message->getHeaders(['from' => true, 'sender' => true]);
  338. $this->assertSame('', $headers['From']);
  339. $this->assertSame('Name <cake@cakephp.org>', $headers['Sender']);
  340. $this->message->setFrom('cake@cakephp.org', 'CakePHP');
  341. $headers = $this->message->getHeaders(['from' => true, 'sender' => true]);
  342. $this->assertSame('CakePHP <cake@cakephp.org>', $headers['From']);
  343. $this->assertSame('', $headers['Sender']);
  344. }
  345. /**
  346. * testTo method
  347. */
  348. public function testTo(): void
  349. {
  350. $this->assertSame([], $this->message->getTo());
  351. $result = $this->message->setTo('cake@cakephp.org');
  352. $expected = ['cake@cakephp.org' => 'cake@cakephp.org'];
  353. $this->assertSame($expected, $this->message->getTo());
  354. $this->assertSame($this->message, $result);
  355. $this->message->setTo('cake@cakephp.org', 'CakePHP');
  356. $expected = ['cake@cakephp.org' => 'CakePHP'];
  357. $this->assertSame($expected, $this->message->getTo());
  358. $list = [
  359. 'root@localhost' => 'root',
  360. 'bjørn@hammeröath.com' => 'Bjorn',
  361. 'cake.php@cakephp.org' => 'Cake PHP',
  362. 'cake-php@googlegroups.com' => 'Cake Groups',
  363. 'root@cakephp.org',
  364. ];
  365. $this->message->setTo($list);
  366. $expected = [
  367. 'root@localhost' => 'root',
  368. 'bjørn@hammeröath.com' => 'Bjorn',
  369. 'cake.php@cakephp.org' => 'Cake PHP',
  370. 'cake-php@googlegroups.com' => 'Cake Groups',
  371. 'root@cakephp.org' => 'root@cakephp.org',
  372. ];
  373. $this->assertSame($expected, $this->message->getTo());
  374. $this->message->addTo('jrbasso@cakephp.org');
  375. $this->message->addTo('mark_story@cakephp.org', 'Mark Story');
  376. $this->message->addTo('foobar@ætdcadsl.dk');
  377. $result = $this->message->addTo(['phpnut@cakephp.org' => 'PhpNut', 'jose_zap@cakephp.org']);
  378. $expected = [
  379. 'root@localhost' => 'root',
  380. 'bjørn@hammeröath.com' => 'Bjorn',
  381. 'cake.php@cakephp.org' => 'Cake PHP',
  382. 'cake-php@googlegroups.com' => 'Cake Groups',
  383. 'root@cakephp.org' => 'root@cakephp.org',
  384. 'jrbasso@cakephp.org' => 'jrbasso@cakephp.org',
  385. 'mark_story@cakephp.org' => 'Mark Story',
  386. 'foobar@ætdcadsl.dk' => 'foobar@ætdcadsl.dk',
  387. 'phpnut@cakephp.org' => 'PhpNut',
  388. 'jose_zap@cakephp.org' => 'jose_zap@cakephp.org',
  389. ];
  390. $this->assertSame($expected, $this->message->getTo());
  391. $this->assertSame($this->message, $result);
  392. }
  393. /**
  394. * test to address with _ in domain name
  395. */
  396. public function testToUnderscoreDomain(): void
  397. {
  398. $result = $this->message->setTo('cake@cake_php.org');
  399. $expected = ['cake@cake_php.org' => 'cake@cake_php.org'];
  400. $this->assertSame($expected, $this->message->getTo());
  401. $this->assertSame($this->message, $result);
  402. }
  403. /**
  404. * Data provider function for testBuildInvalidData
  405. *
  406. * @return array
  407. */
  408. public static function invalidEmails(): array
  409. {
  410. return [
  411. [''],
  412. ['string'],
  413. ['<tag>'],
  414. [['ok@cakephp.org', '1.0', '', 'string']],
  415. ];
  416. }
  417. /**
  418. * testBuildInvalidData
  419. *
  420. * @param array|string $value
  421. */
  422. #[DataProvider('invalidEmails')]
  423. public function testInvalidEmail($value): void
  424. {
  425. $this->expectException(InvalidArgumentException::class);
  426. $this->message->setTo($value);
  427. }
  428. /**
  429. * testBuildInvalidData
  430. *
  431. * @param array|string $value
  432. */
  433. #[DataProvider('invalidEmails')]
  434. public function testInvalidEmailAdd($value): void
  435. {
  436. $this->expectException(InvalidArgumentException::class);
  437. $this->message->addTo($value);
  438. }
  439. /**
  440. * test emailPattern method
  441. */
  442. public function testEmailPattern(): void
  443. {
  444. $regex = '/.+@.+\..+/i';
  445. $this->assertSame($regex, $this->message->setEmailPattern($regex)->getEmailPattern());
  446. }
  447. /**
  448. * Tests that it is possible to set email regex configuration to a CakeEmail object
  449. */
  450. public function testConfigEmailPattern(): void
  451. {
  452. $regex = '/.+@.+\..+/i';
  453. $email = new Message(['emailPattern' => $regex]);
  454. $this->assertSame($regex, $email->getEmailPattern());
  455. }
  456. /**
  457. * Tests that it is possible set custom email validation
  458. */
  459. public function testCustomEmailValidation(): void
  460. {
  461. $regex = '/^[\.a-z0-9!#$%&\'*+\/=?^_`{|}~-]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]{2,6}$/i';
  462. $this->message->setEmailPattern($regex)->setTo('pass.@example.com');
  463. $this->assertSame([
  464. 'pass.@example.com' => 'pass.@example.com',
  465. ], $this->message->getTo());
  466. $this->message->addTo('pass..old.docomo@example.com');
  467. $this->assertSame([
  468. 'pass.@example.com' => 'pass.@example.com',
  469. 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
  470. ], $this->message->getTo());
  471. $this->message->reset();
  472. $emails = [
  473. 'pass.@example.com',
  474. 'pass..old.docomo@example.com',
  475. ];
  476. $additionalEmails = [
  477. '.extend.@example.com',
  478. '.docomo@example.com',
  479. ];
  480. $this->message->setEmailPattern($regex)->setTo($emails);
  481. $this->assertSame([
  482. 'pass.@example.com' => 'pass.@example.com',
  483. 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
  484. ], $this->message->getTo());
  485. $this->message->addTo($additionalEmails);
  486. $this->assertSame([
  487. 'pass.@example.com' => 'pass.@example.com',
  488. 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com',
  489. '.extend.@example.com' => '.extend.@example.com',
  490. '.docomo@example.com' => '.docomo@example.com',
  491. ], $this->message->getTo());
  492. }
  493. /**
  494. * Tests that it is possible to unset the email pattern and make use of filter_var() instead.
  495. */
  496. public function testUnsetEmailPattern(): void
  497. {
  498. $this->expectException(InvalidArgumentException::class);
  499. $this->expectExceptionMessage('Invalid email set for `to`. You passed `fail.@example.com`.');
  500. $email = new Message();
  501. $this->assertSame(Message::EMAIL_PATTERN, $email->getEmailPattern());
  502. $email->setEmailPattern(null);
  503. $this->assertNull($email->getEmailPattern());
  504. $email->setTo('pass@example.com');
  505. $email->setTo('fail.@example.com');
  506. }
  507. /**
  508. * Tests that passing an empty string throws an InvalidArgumentException.
  509. */
  510. public function testEmptyTo(): void
  511. {
  512. $this->expectException(InvalidArgumentException::class);
  513. $this->expectExceptionMessage('The email set for `to` is empty.');
  514. $email = new Message();
  515. $email->setTo('');
  516. }
  517. /**
  518. * testFormatAddress method
  519. */
  520. public function testFormatAddress(): void
  521. {
  522. $result = $this->message->fmtAddress(['cake@cakephp.org' => 'cake@cakephp.org']);
  523. $expected = ['cake@cakephp.org'];
  524. $this->assertSame($expected, $result);
  525. $result = $this->message->fmtAddress([
  526. 'cake@cakephp.org' => 'cake@cakephp.org',
  527. 'php@cakephp.org' => 'php@cakephp.org',
  528. ]);
  529. $expected = ['cake@cakephp.org', 'php@cakephp.org'];
  530. $this->assertSame($expected, $result);
  531. $result = $this->message->fmtAddress([
  532. 'cake@cakephp.org' => 'CakePHP',
  533. 'php@cakephp.org' => 'Cake',
  534. ]);
  535. $expected = ['CakePHP <cake@cakephp.org>', 'Cake <php@cakephp.org>'];
  536. $this->assertSame($expected, $result);
  537. $result = $this->message->fmtAddress(['me@example.com' => 'Last, First']);
  538. $expected = ['"Last, First" <me@example.com>'];
  539. $this->assertSame($expected, $result);
  540. $result = $this->message->fmtAddress(['me@example.com' => '"Last" First']);
  541. $expected = ['"\"Last\" First" <me@example.com>'];
  542. $this->assertSame($expected, $result);
  543. $result = $this->message->fmtAddress(['me@example.com' => 'Last First']);
  544. $expected = ['Last First <me@example.com>'];
  545. $this->assertSame($expected, $result);
  546. $result = $this->message->fmtAddress(['cake@cakephp.org' => 'ÄÖÜTest']);
  547. $expected = ['=?UTF-8?B?w4TDlsOcVGVzdA==?= <cake@cakephp.org>'];
  548. $this->assertSame($expected, $result);
  549. $result = $this->message->fmtAddress(['cake@cakephp.org' => '日本語Test']);
  550. $expected = ['=?UTF-8?B?5pel5pys6KqeVGVzdA==?= <cake@cakephp.org>'];
  551. $this->assertSame($expected, $result);
  552. }
  553. /**
  554. * testFormatAddressJapanese
  555. */
  556. public function testFormatAddressJapanese(): void
  557. {
  558. $this->message->setHeaderCharset('ISO-2022-JP');
  559. $result = $this->message->fmtAddress(['cake@cakephp.org' => '日本語Test']);
  560. $expected = ['=?ISO-2022-JP?B?GyRCRnxLXDhsGyhCVGVzdA==?= <cake@cakephp.org>'];
  561. $this->assertSame($expected, $result);
  562. $result = $this->message->fmtAddress(['cake@cakephp.org' => '寿限無寿限無五劫の擦り切れ海砂利水魚の水行末雲来末風来末食う寝る処に住む処やぶら小路の藪柑子パイポパイポパイポのシューリンガンシューリンガンのグーリンダイグーリンダイのポンポコピーのポンポコナーの長久命の長助']);
  563. $expected = ["\"=?ISO-2022-JP?B?GyRCPHc4Qkw1PHc4Qkw1OF45ZSROOyQkakBaJGwzJDo9TXg/ZTV7GyhC?=\r\n" .
  564. " =?ISO-2022-JP?B?GyRCJE4/ZTlUS3YxQE1oS3ZJd01oS3Y/KSQmPzIkaz1oJEs9OyRgGyhC?=\r\n" .
  565. " =?ISO-2022-JP?B?GyRCPWgkZCRWJGk+Lk8pJE5pLjQ7O1IlUSUkJV0lUSUkJV0lUSUkGyhC?=\r\n" .
  566. " =?ISO-2022-JP?B?GyRCJV0kTiU3JWUhPCVqJXMlLCVzJTclZSE8JWolcyUsJXMkTiUwGyhC?=\r\n" .
  567. " =?ISO-2022-JP?B?GyRCITwlaiVzJUAlJCUwITwlaiVzJUAlJCROJV0lcyVdJTMlVCE8GyhC?=\r\n" .
  568. ' =?ISO-2022-JP?B?GyRCJE4lXSVzJV0lMyVKITwkTkQ5NVdMPyRORDk9dRsoQg==?=" <cake@cakephp.org>'];
  569. $this->assertSame($expected, $result);
  570. }
  571. /**
  572. * testAddresses method
  573. */
  574. public function testAddresses(): void
  575. {
  576. $this->message->reset();
  577. $this->message->setFrom('cake@cakephp.org', 'CakePHP');
  578. $this->message->setReplyTo('replyto@cakephp.org', 'ReplyTo CakePHP');
  579. $this->message->setReadReceipt('readreceipt@cakephp.org', 'ReadReceipt CakePHP');
  580. $this->message->setReturnPath('returnpath@cakephp.org', 'ReturnPath CakePHP');
  581. $this->message->setTo('to@cakephp.org', 'To, CakePHP');
  582. $this->message->setCc('cc@cakephp.org', 'Cc CakePHP');
  583. $this->message->setBcc('bcc@cakephp.org', 'Bcc CakePHP');
  584. $this->message->addTo('to2@cakephp.org', 'To2 CakePHP');
  585. $this->message->addCc('cc2@cakephp.org', 'Cc2 CakePHP');
  586. $this->message->addBcc('bcc2@cakephp.org', 'Bcc2 CakePHP');
  587. $this->message->addReplyTo('replyto2@cakephp.org', 'ReplyTo2 CakePHP');
  588. $this->assertSame($this->message->getFrom(), ['cake@cakephp.org' => 'CakePHP']);
  589. $this->assertSame($this->message->getReplyTo(), ['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
  590. $this->assertSame($this->message->getReadReceipt(), ['readreceipt@cakephp.org' => 'ReadReceipt CakePHP']);
  591. $this->assertSame($this->message->getReturnPath(), ['returnpath@cakephp.org' => 'ReturnPath CakePHP']);
  592. $this->assertSame($this->message->getTo(), ['to@cakephp.org' => 'To, CakePHP', 'to2@cakephp.org' => 'To2 CakePHP']);
  593. $this->assertSame($this->message->getCc(), ['cc@cakephp.org' => 'Cc CakePHP', 'cc2@cakephp.org' => 'Cc2 CakePHP']);
  594. $this->assertSame($this->message->getBcc(), ['bcc@cakephp.org' => 'Bcc CakePHP', 'bcc2@cakephp.org' => 'Bcc2 CakePHP']);
  595. $headers = $this->message->getHeaders(array_fill_keys(['from', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'bcc'], true));
  596. $this->assertSame($headers['From'], 'CakePHP <cake@cakephp.org>');
  597. $this->assertSame($headers['Reply-To'], 'ReplyTo CakePHP <replyto@cakephp.org>, ReplyTo2 CakePHP <replyto2@cakephp.org>');
  598. $this->assertSame($headers['Disposition-Notification-To'], 'ReadReceipt CakePHP <readreceipt@cakephp.org>');
  599. $this->assertSame($headers['Return-Path'], 'ReturnPath CakePHP <returnpath@cakephp.org>');
  600. $this->assertSame($headers['To'], '"To, CakePHP" <to@cakephp.org>, To2 CakePHP <to2@cakephp.org>');
  601. $this->assertSame($headers['Cc'], 'Cc CakePHP <cc@cakephp.org>, Cc2 CakePHP <cc2@cakephp.org>');
  602. $this->assertSame($headers['Bcc'], 'Bcc CakePHP <bcc@cakephp.org>, Bcc2 CakePHP <bcc2@cakephp.org>');
  603. $this->message->setReplyTo(['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
  604. $this->assertSame($this->message->getReplyTo(), ['replyto@cakephp.org' => 'ReplyTo CakePHP', 'replyto2@cakephp.org' => 'ReplyTo2 CakePHP']);
  605. $headers = $this->message->getHeaders(array_fill_keys(['replyTo'], true));
  606. $this->assertSame($headers['Reply-To'], 'ReplyTo CakePHP <replyto@cakephp.org>, ReplyTo2 CakePHP <replyto2@cakephp.org>');
  607. }
  608. /**
  609. * test reset addresses method
  610. */
  611. public function testResetAddresses(): void
  612. {
  613. $this->message->reset();
  614. $this->message
  615. ->setFrom('cake@cakephp.org', 'CakePHP')
  616. ->setReplyTo('replyto@cakephp.org', 'ReplyTo CakePHP')
  617. ->setReadReceipt('readreceipt@cakephp.org', 'ReadReceipt CakePHP')
  618. ->setReturnPath('returnpath@cakephp.org', 'ReturnPath CakePHP')
  619. ->setTo('to@cakephp.org', 'To, CakePHP')
  620. ->setCc('cc@cakephp.org', 'Cc CakePHP')
  621. ->setBcc('bcc@cakephp.org', 'Bcc CakePHP');
  622. $this->assertNotEmpty($this->message->getFrom());
  623. $this->assertNotEmpty($this->message->getReplyTo());
  624. $this->assertNotEmpty($this->message->getReadReceipt());
  625. $this->assertNotEmpty($this->message->getReturnPath());
  626. $this->assertNotEmpty($this->message->getTo());
  627. $this->assertNotEmpty($this->message->getCc());
  628. $this->assertNotEmpty($this->message->getBcc());
  629. $this->message
  630. ->setFrom([])
  631. ->setReplyTo([])
  632. ->setReadReceipt([])
  633. ->setReturnPath([])
  634. ->setTo([])
  635. ->setCc([])
  636. ->setBcc([]);
  637. $this->assertEmpty($this->message->getFrom());
  638. $this->assertEmpty($this->message->getReplyTo());
  639. $this->assertEmpty($this->message->getReadReceipt());
  640. $this->assertEmpty($this->message->getReturnPath());
  641. $this->assertEmpty($this->message->getTo());
  642. $this->assertEmpty($this->message->getCc());
  643. $this->assertEmpty($this->message->getBcc());
  644. }
  645. /**
  646. * testMessageId method
  647. */
  648. public function testMessageId(): void
  649. {
  650. $this->message->setMessageId(true);
  651. $result = $this->message->getHeaders();
  652. $this->assertArrayHasKey('Message-ID', $result);
  653. $this->message->setMessageId(false);
  654. $result = $this->message->getHeaders();
  655. $this->assertArrayNotHasKey('Message-ID', $result);
  656. $result = $this->message->setMessageId('<my-email@localhost>');
  657. $this->assertSame($this->message, $result);
  658. $result = $this->message->getHeaders();
  659. $this->assertSame('<my-email@localhost>', $result['Message-ID']);
  660. $result = $this->message->getMessageId();
  661. $this->assertSame('<my-email@localhost>', $result);
  662. }
  663. public function testAutoMessageIdIsIdempotent(): void
  664. {
  665. $headers = $this->message->getHeaders();
  666. $this->assertArrayHasKey('Message-ID', $headers);
  667. $regeneratedHeaders = $this->message->getHeaders();
  668. $this->assertSame($headers['Message-ID'], $regeneratedHeaders['Message-ID']);
  669. }
  670. public function testPriority(): void
  671. {
  672. $this->message->setPriority(4);
  673. $this->assertSame(4, $this->message->getPriority());
  674. $result = $this->message->getHeaders();
  675. $this->assertArrayHasKey('X-Priority', $result);
  676. }
  677. /**
  678. * testMessageIdInvalid method
  679. */
  680. public function testMessageIdInvalid(): void
  681. {
  682. $this->expectException(InvalidArgumentException::class);
  683. $this->message->setMessageId('my-email@localhost');
  684. }
  685. /**
  686. * testDomain method
  687. */
  688. public function testDomain(): void
  689. {
  690. $result = $this->message->getDomain();
  691. $expected = env('HTTP_HOST') ?: php_uname('n');
  692. $this->assertSame($expected, $result);
  693. $this->message->setDomain('example.org');
  694. $result = $this->message->getDomain();
  695. $expected = 'example.org';
  696. $this->assertSame($expected, $result);
  697. }
  698. /**
  699. * testMessageIdWithDomain method
  700. */
  701. public function testMessageIdWithDomain(): void
  702. {
  703. $this->message->setDomain('example.org');
  704. $result = $this->message->getHeaders();
  705. $expected = '@example.org>';
  706. $this->assertTextContains($expected, $result['Message-ID']);
  707. $_SERVER['HTTP_HOST'] = 'example.org';
  708. $result = $this->message->getHeaders();
  709. $this->assertTextContains('example.org', $result['Message-ID']);
  710. $_SERVER['HTTP_HOST'] = 'example.org:81';
  711. $result = $this->message->getHeaders();
  712. $this->assertTextNotContains(':81', $result['Message-ID']);
  713. }
  714. /**
  715. * testSubject method
  716. */
  717. public function testSubject(): void
  718. {
  719. $this->message->setSubject('You have a new message.');
  720. $this->assertSame('You have a new message.', $this->message->getSubject());
  721. $this->message->setSubject('You have a new message, I think.');
  722. $this->assertSame($this->message->getSubject(), 'You have a new message, I think.');
  723. $this->message->setSubject('1');
  724. $this->assertSame('1', $this->message->getSubject());
  725. $input = 'هذه رسالة بعنوان طويل مرسل للمستلم';
  726. $this->message->setSubject($input);
  727. $expected = '=?UTF-8?B?2YfYsNmHINix2LPYp9mE2Kkg2KjYudmG2YjYp9mGINi32YjZitmEINmF2LE=?=' . "\r\n" . ' =?UTF-8?B?2LPZhCDZhNmE2YXYs9iq2YTZhQ==?=';
  728. $this->assertSame($expected, $this->message->getSubject());
  729. $this->assertSame($input, $this->message->getOriginalSubject());
  730. }
  731. /**
  732. * testSubjectJapanese
  733. */
  734. public function testSubjectJapanese(): void
  735. {
  736. mb_internal_encoding('UTF-8');
  737. $this->message->setHeaderCharset('ISO-2022-JP');
  738. $this->message->setSubject('日本語のSubjectにも対応するよ');
  739. $expected = '=?ISO-2022-JP?B?GyRCRnxLXDhsJE4bKEJTdWJqZWN0GyRCJEskYkJQMX4kOSRrJGgbKEI=?=';
  740. $this->assertSame($expected, $this->message->getSubject());
  741. $this->message->setSubject('長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう?');
  742. $expected = "=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
  743. " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
  744. ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=';
  745. $this->assertSame($expected, $this->message->getSubject());
  746. }
  747. /**
  748. * testAttachments method
  749. */
  750. public function testSetAttachments(): void
  751. {
  752. $uploadedFile = new UploadedFile(
  753. __FILE__,
  754. filesize(__FILE__),
  755. UPLOAD_ERR_OK,
  756. 'MessageTest.php',
  757. 'text/x-php'
  758. );
  759. $this->message->setAttachments([
  760. CAKE . 'Mailer' . DS . 'Message.php',
  761. $uploadedFile,
  762. ]);
  763. $expected = [
  764. 'Message.php' => [
  765. 'file' => CAKE . 'Mailer' . DS . 'Message.php',
  766. 'mimetype' => 'text/x-php',
  767. ],
  768. 'MessageTest.php' => [
  769. 'file' => $uploadedFile,
  770. 'mimetype' => 'text/x-php',
  771. ],
  772. ];
  773. $this->assertSame($expected, $this->message->getAttachments());
  774. $this->message->setAttachments([]);
  775. $this->assertSame([], $this->message->getAttachments());
  776. $this->message->setAttachments([
  777. ['file' => __FILE__, 'mimetype' => 'text/plain'],
  778. ]);
  779. $this->message->addAttachments([CORE_PATH . 'config' . DS . 'bootstrap.php']);
  780. $this->message->addAttachments([CORE_PATH . 'config' . DS . 'bootstrap.php']);
  781. $this->message->addAttachments([
  782. 'other.txt' => CORE_PATH . 'config' . DS . 'bootstrap.php',
  783. 'license' => CORE_PATH . 'LICENSE',
  784. ]);
  785. $expected = [
  786. 'MessageTest.php' => ['file' => __FILE__, 'mimetype' => 'text/plain'],
  787. 'bootstrap.php' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
  788. 'other.txt' => ['file' => CORE_PATH . 'config' . DS . 'bootstrap.php', 'mimetype' => 'text/x-php'],
  789. 'license' => ['file' => CORE_PATH . 'LICENSE', 'mimetype' => 'text/plain'],
  790. ];
  791. $this->assertSame($expected, $this->message->getAttachments());
  792. $this->expectException(InvalidArgumentException::class);
  793. $this->message->setAttachments([['nofile' => __FILE__, 'mimetype' => 'text/plain']]);
  794. }
  795. /**
  796. * Test send() with no template and data string attachment and no mimetype
  797. */
  798. public function testSetAttachmentDataNoMimetype(): void
  799. {
  800. $this->message->setAttachments(['cake.icon.gif' => [
  801. 'data' => 'test',
  802. ]]);
  803. $expected = [
  804. 'cake.icon.gif' => [
  805. 'data' => base64_encode('test') . "\r\n",
  806. 'mimetype' => 'application/octet-stream',
  807. ],
  808. ];
  809. $this->assertSame($expected, $this->message->getAttachments());
  810. }
  811. public function testSetAttachmentInvalidFile(): void
  812. {
  813. $this->expectException(InvalidArgumentException::class);
  814. $this->expectExceptionMessage(
  815. 'File must be a filepath or UploadedFileInterface instance. Found `boolean` instead.'
  816. );
  817. $this->message->setAttachments(['cake.icon.gif' => [
  818. 'file' => true,
  819. ]]);
  820. }
  821. /**
  822. * testReset method
  823. */
  824. public function testReset(): void
  825. {
  826. $this->message->setTo('cake@cakephp.org');
  827. $this->message->setEmailPattern('/.+@.+\..+/i');
  828. $this->assertSame(['cake@cakephp.org' => 'cake@cakephp.org'], $this->message->getTo());
  829. $this->message->reset();
  830. $this->assertSame([], $this->message->getTo());
  831. $this->assertSame(Message::EMAIL_PATTERN, $this->message->getEmailPattern());
  832. }
  833. /**
  834. * testReset with charset
  835. */
  836. public function testResetWithCharset(): void
  837. {
  838. $this->message->setCharset('ISO-2022-JP');
  839. $this->message->reset();
  840. $this->assertSame('utf-8', $this->message->getCharset());
  841. $this->assertSame('utf-8', $this->message->getHeaderCharset());
  842. }
  843. /**
  844. * testEmailFormat method
  845. */
  846. public function testEmailFormat(): void
  847. {
  848. $result = $this->message->getEmailFormat();
  849. $this->assertSame('text', $result);
  850. $result = $this->message->setEmailFormat('html');
  851. $this->assertInstanceOf(Message::class, $result);
  852. $result = $this->message->getEmailFormat();
  853. $this->assertSame('html', $result);
  854. $this->expectException(InvalidArgumentException::class);
  855. $this->message->setEmailFormat('invalid');
  856. }
  857. /**
  858. * Tests that it is possible to add charset configuration to a CakeEmail object
  859. */
  860. public function testConfigCharset(): void
  861. {
  862. $email = new Message();
  863. $this->assertEquals(Configure::read('App.encoding'), $email->getCharset());
  864. $this->assertEquals(Configure::read('App.encoding'), $email->getHeaderCharset());
  865. $email = new Message(['charset' => 'iso-2022-jp', 'headerCharset' => 'iso-2022-jp-ms']);
  866. $this->assertSame('iso-2022-jp', $email->getCharset());
  867. $this->assertSame('iso-2022-jp-ms', $email->getHeaderCharset());
  868. $email = new Message(['charset' => 'iso-2022-jp']);
  869. $this->assertSame('iso-2022-jp', $email->getCharset());
  870. $this->assertSame('iso-2022-jp', $email->getHeaderCharset());
  871. $email = new Message(['headerCharset' => 'iso-2022-jp-ms']);
  872. $this->assertEquals(Configure::read('App.encoding'), $email->getCharset());
  873. $this->assertSame('iso-2022-jp-ms', $email->getHeaderCharset());
  874. }
  875. public function testGetBody(): void
  876. {
  877. $message = new Message();
  878. $uploadedFile = new UploadedFile(
  879. __FILE__,
  880. filesize(__FILE__),
  881. UPLOAD_ERR_OK,
  882. 'MessageTest.php',
  883. 'text/x-php'
  884. );
  885. $chunks = base64_encode(file_get_contents(__FILE__));
  886. $result = $message->setAttachments([$uploadedFile])
  887. ->setBodyText('Attached an uploaded file')
  888. ->getBody();
  889. $result = implode("\r\n", $result);
  890. $this->assertStringContainsString($chunks[0], $result);
  891. }
  892. /**
  893. * Tests that the body is encoded using the configured charset (Japanese standard encoding)
  894. */
  895. public function testBodyEncodingIso2022Jp(): void
  896. {
  897. $message = new Message([
  898. 'charset' => 'iso-2022-jp',
  899. 'headerCharset' => 'iso-2022-jp',
  900. 'transport' => 'debug',
  901. ]);
  902. $message->setSubject('あれ?もしかしての前と');
  903. $headers = $message->getHeaders(['subject']);
  904. $expected = '?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?=';
  905. $this->assertStringContainsString($expected, $headers['Subject']);
  906. $message->setBodyHtml('①㈱ ってテーブルを作ってやってたらう');
  907. $result = $message->getHeadersString();
  908. $this->assertTextContains('Content-Type: text/plain; charset=ISO-2022-JP', $result);
  909. $this->assertTextNotContains('Content-Type: text/plain; charset=ISO-2022-JP-MS', $result); // not charset=iso-2022-jp-ms
  910. $result = implode('', $message->getBody());
  911. $this->assertTextNotContains(mb_convert_encoding('①㈱ ってテーブルを作ってやってたらう', 'ISO-2022-JP-MS'), $result);
  912. }
  913. /**
  914. * Tests that the body is encoded using the configured charset (Japanese irregular encoding, but sometime use this)
  915. */
  916. public function testBodyEncodingIso2022JpMs(): void
  917. {
  918. $message = new Message([
  919. 'charset' => 'iso-2022-jp-ms',
  920. 'headerCharset' => 'iso-2022-jp-ms',
  921. 'transport' => 'debug',
  922. ]);
  923. $message->setSubject('あれ?もしかしての前と');
  924. $headers = $message->getHeaders(['subject']);
  925. $expected = '?ISO-2022-JP?B?GyRCJCIkbCEpJGIkNyQrJDckRiROQTAkSBsoQg==?=';
  926. $this->assertStringContainsString($expected, $headers['Subject']);
  927. $result = $message->setBodyText('①㈱ ってテーブルを作ってやってたらう');
  928. $result = $message->getHeadersString();
  929. $this->assertTextContains('Content-Type: text/plain; charset=ISO-2022-JP', $result);
  930. $this->assertTextNotContains('Content-Type: text/plain; charset=iso-2022-jp-ms', $result); // not charset=iso-2022-jp-ms
  931. $result = implode('', $message->getBody());
  932. $this->assertStringContainsString(mb_convert_encoding('①㈱ ってテーブルを作ってやってたらう', 'ISO-2022-JP-MS'), $result);
  933. }
  934. /**
  935. * Tests that the body is encoded using the configured charset
  936. */
  937. public function testEncodingMixed(): void
  938. {
  939. $message = new Message([
  940. 'headerCharset' => 'iso-2022-jp-ms',
  941. 'charset' => 'iso-2022-jp',
  942. ]);
  943. $message->setBodyText('ってテーブルを作ってやってたらう');
  944. $result = $message->getHeadersString();
  945. $this->assertStringContainsString('Content-Type: text/plain; charset=ISO-2022-JP', $result);
  946. $result = implode('', $message->getBody());
  947. $this->assertStringContainsString(mb_convert_encoding('ってテーブルを作ってやってたらう', 'ISO-2022-JP'), $result);
  948. }
  949. /**
  950. * Test CakeMessage::_encode function
  951. */
  952. public function testEncode(): void
  953. {
  954. $this->message->setHeaderCharset('ISO-2022-JP');
  955. $result = $this->message->encode('日本語');
  956. $expected = '=?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=';
  957. $this->assertSame($expected, $result);
  958. $this->message->setHeaderCharset('ISO-2022-JP');
  959. $result = $this->message->encode('長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう?');
  960. $expected = "=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
  961. " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
  962. ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=';
  963. $this->assertSame($expected, $result);
  964. }
  965. /**
  966. * Test CakeMessage::_decode function
  967. */
  968. public function testDecode(): void
  969. {
  970. $this->message->setHeaderCharset('ISO-2022-JP');
  971. $result = $this->message->decode('=?ISO-2022-JP?B?GyRCRnxLXDhsGyhC?=');
  972. $expected = '日本語';
  973. $this->assertSame($expected, $result);
  974. $this->message->setHeaderCharset('ISO-2022-JP');
  975. $result = $this->message->decode("=?ISO-2022-JP?B?GyRCRDkkJEQ5JCREOSQkGyhCU3ViamVjdBskQiROPmw5ZyRPGyhCZm9s?=\r\n" .
  976. " =?ISO-2022-JP?B?ZGluZxskQiQ5JGskTiQsQDUkNyQkJHMkQCQxJEkkJCRDJD8kJCRJGyhC?=\r\n" .
  977. ' =?ISO-2022-JP?B?GyRCJCYkSiRrJHMkQCRtJCYhKRsoQg==?=');
  978. $expected = '長い長い長いSubjectの場合はfoldingするのが正しいんだけどいったいどうなるんだろう?';
  979. $this->assertSame($expected, $result);
  980. }
  981. /**
  982. * Tests charset setter/getter
  983. */
  984. public function testCharset(): void
  985. {
  986. $this->message->setCharset('UTF-8');
  987. $this->assertSame($this->message->getCharset(), 'UTF-8');
  988. $this->message->setCharset('ISO-2022-JP');
  989. $this->assertSame($this->message->getCharset(), 'ISO-2022-JP');
  990. $charset = $this->message->setCharset('Shift_JIS');
  991. $this->assertSame('Shift_JIS', $charset->getCharset());
  992. }
  993. /**
  994. * Tests headerCharset setter/getter
  995. */
  996. public function testHeaderCharset(): void
  997. {
  998. $this->message->setHeaderCharset('UTF-8');
  999. $this->assertSame($this->message->getHeaderCharset(), 'UTF-8');
  1000. $this->message->setHeaderCharset('ISO-2022-JP');
  1001. $this->assertSame($this->message->getHeaderCharset(), 'ISO-2022-JP');
  1002. $charset = $this->message->setHeaderCharset('Shift_JIS');
  1003. $this->assertSame('Shift_JIS', $charset->getHeaderCharset());
  1004. }
  1005. /**
  1006. * Test transferEncoding
  1007. */
  1008. public function testTransferEncoding(): void
  1009. {
  1010. // Test new transfer encoding
  1011. $expected = 'quoted-printable';
  1012. $this->message->setTransferEncoding($expected);
  1013. $this->assertSame($expected, $this->message->getTransferEncoding());
  1014. $this->assertSame($expected, $this->message->getContentTransferEncoding());
  1015. // Test default charset/encoding : utf8/8bit
  1016. $expected = '8bit';
  1017. $this->message->reset();
  1018. $this->assertNull($this->message->getTransferEncoding());
  1019. $this->assertSame($expected, $this->message->getContentTransferEncoding());
  1020. //Test wrong encoding
  1021. $this->expectException(InvalidArgumentException::class);
  1022. $this->message->setTransferEncoding('invalid');
  1023. }
  1024. /**
  1025. * Tests for compatible check.
  1026. * charset property and charset() method.
  1027. * headerCharset property and headerCharset() method.
  1028. */
  1029. public function testCharsetsCompatible(): void
  1030. {
  1031. $checkHeaders = [
  1032. 'from' => true,
  1033. 'to' => true,
  1034. 'cc' => true,
  1035. 'subject' => true,
  1036. ];
  1037. // Header Charset : null (used by default UTF-8)
  1038. // Body Charset : ISO-2022-JP
  1039. $oldStyleEmail = $this->_getEmailByOldStyleCharset('iso-2022-jp', null);
  1040. $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);
  1041. $newStyleEmail = $this->_getEmailByNewStyleCharset('iso-2022-jp', null);
  1042. $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);
  1043. $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
  1044. $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
  1045. $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
  1046. $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);
  1047. // Header Charset : UTF-8
  1048. // Boby Charset : ISO-2022-JP
  1049. $oldStyleEmail = $this->_getEmailByOldStyleCharset('iso-2022-jp', 'utf-8');
  1050. $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);
  1051. $newStyleEmail = $this->_getEmailByNewStyleCharset('iso-2022-jp', 'utf-8');
  1052. $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);
  1053. $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
  1054. $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
  1055. $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
  1056. $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);
  1057. // Header Charset : ISO-2022-JP
  1058. // Boby Charset : UTF-8
  1059. $oldStyleEmail = $this->_getEmailByOldStyleCharset('utf-8', 'iso-2022-jp');
  1060. $oldStyleHeaders = $oldStyleEmail->getHeaders($checkHeaders);
  1061. $newStyleEmail = $this->_getEmailByNewStyleCharset('utf-8', 'iso-2022-jp');
  1062. $newStyleHeaders = $newStyleEmail->getHeaders($checkHeaders);
  1063. $this->assertSame($oldStyleHeaders['From'], $newStyleHeaders['From']);
  1064. $this->assertSame($oldStyleHeaders['To'], $newStyleHeaders['To']);
  1065. $this->assertSame($oldStyleHeaders['Cc'], $newStyleHeaders['Cc']);
  1066. $this->assertSame($oldStyleHeaders['Subject'], $newStyleHeaders['Subject']);
  1067. }
  1068. /**
  1069. * @param mixed $charset
  1070. * @param mixed $headerCharset
  1071. */
  1072. protected function _getEmailByOldStyleCharset($charset, $headerCharset): Message
  1073. {
  1074. $message = new Message(['transport' => 'debug']);
  1075. if ($charset) {
  1076. $message->setCharset($charset);
  1077. }
  1078. if ($headerCharset) {
  1079. $message->setHeaderCharset($headerCharset);
  1080. }
  1081. $message->setFrom('someone@example.com', 'どこかの誰か');
  1082. $message->setTo('someperson@example.jp', 'どこかのどなたか');
  1083. $message->setCc('miku@example.net', 'ミク');
  1084. $message->setSubject('テストメール');
  1085. $message->setBodyText('テストメールの本文');
  1086. return $message;
  1087. }
  1088. /**
  1089. * @param mixed $charset
  1090. * @param mixed $headerCharset
  1091. */
  1092. protected function _getEmailByNewStyleCharset($charset, $headerCharset): Message
  1093. {
  1094. $message = new Message();
  1095. if ($charset) {
  1096. $message->setCharset($charset);
  1097. }
  1098. if ($headerCharset) {
  1099. $message->setHeaderCharset($headerCharset);
  1100. }
  1101. $message->setFrom('someone@example.com', 'どこかの誰か');
  1102. $message->setTo('someperson@example.jp', 'どこかのどなたか');
  1103. $message->setCc('miku@example.net', 'ミク');
  1104. $message->setSubject('テストメール');
  1105. $message->setBodyText('テストメールの本文');
  1106. return $message;
  1107. }
  1108. /**
  1109. * @param string $message
  1110. */
  1111. protected function assertLineLengths($message): void
  1112. {
  1113. $lines = explode("\r\n", $message);
  1114. foreach ($lines as $line) {
  1115. $this->assertTrue(
  1116. strlen($line) <= Message::LINE_LENGTH_MUST,
  1117. 'Line length exceeds the max. limit of Message::LINE_LENGTH_MUST'
  1118. );
  1119. }
  1120. }
  1121. public function testSerialization(): void
  1122. {
  1123. $message = new Message();
  1124. $reflection = new ReflectionClass($message);
  1125. $property = $reflection->getProperty('serializableProperties');
  1126. $property->setAccessible(true);
  1127. $serializableProperties = $property->getValue($message);
  1128. $message
  1129. ->setSubject('I haz Cake')
  1130. ->setEmailFormat(Message::MESSAGE_BOTH)
  1131. ->setBody([
  1132. Message::MESSAGE_TEXT => 'text message',
  1133. Message::MESSAGE_HTML => '<strong>html message</strong>',
  1134. ]);
  1135. $string = serialize($message);
  1136. $this->assertStringContainsString('text message', $string);
  1137. $this->assertIsArray($serializableProperties);
  1138. $this->assertContains('subject', $serializableProperties);
  1139. $this->assertContains('emailFormat', $serializableProperties);
  1140. $this->assertContains('textMessage', $serializableProperties);
  1141. $this->assertContains('htmlMessage', $serializableProperties);
  1142. /** @var \Cake\Mailer\Message $message */
  1143. $message = unserialize($string);
  1144. $this->assertSame('I haz Cake', $message->getSubject());
  1145. $body = $message->getBodyString();
  1146. $this->assertStringContainsString('text message', $body);
  1147. $this->assertStringContainsString('<strong>html message</strong>', $body);
  1148. }
  1149. }