MessageTest.php 52 KB

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