Email.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2019, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
  33. * @license https://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 1.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Email Class
  41. *
  42. * Permits email to be sent using Mail, Sendmail, or SMTP.
  43. *
  44. * @package CodeIgniter
  45. * @subpackage Libraries
  46. * @category Libraries
  47. * @author EllisLab Dev Team
  48. * @link https://codeigniter.com/user_guide/libraries/email.html
  49. */
  50. class CI_Email {
  51. /**
  52. * Used as the User-Agent and X-Mailer headers' value.
  53. *
  54. * @var string
  55. */
  56. public $useragent = 'CodeIgniter';
  57. /**
  58. * Path to the Sendmail binary.
  59. *
  60. * @var string
  61. */
  62. public $mailpath = '/usr/sbin/sendmail'; // Sendmail path
  63. /**
  64. * Which method to use for sending e-mails.
  65. *
  66. * @var string 'mail', 'sendmail' or 'smtp'
  67. */
  68. public $protocol = 'mail'; // mail/sendmail/smtp
  69. /**
  70. * STMP Server host
  71. *
  72. * @var string
  73. */
  74. public $smtp_host = '';
  75. /**
  76. * SMTP Username
  77. *
  78. * @var string
  79. */
  80. public $smtp_user = '';
  81. /**
  82. * SMTP Password
  83. *
  84. * @var string
  85. */
  86. public $smtp_pass = '';
  87. /**
  88. * SMTP Server port
  89. *
  90. * @var int
  91. */
  92. public $smtp_port = 25;
  93. /**
  94. * SMTP connection timeout in seconds
  95. *
  96. * @var int
  97. */
  98. public $smtp_timeout = 5;
  99. /**
  100. * SMTP persistent connection
  101. *
  102. * @var bool
  103. */
  104. public $smtp_keepalive = FALSE;
  105. /**
  106. * SMTP Encryption
  107. *
  108. * @var string empty, 'tls' or 'ssl'
  109. */
  110. public $smtp_crypto = '';
  111. /**
  112. * Whether to apply word-wrapping to the message body.
  113. *
  114. * @var bool
  115. */
  116. public $wordwrap = TRUE;
  117. /**
  118. * Number of characters to wrap at.
  119. *
  120. * @see CI_Email::$wordwrap
  121. * @var int
  122. */
  123. public $wrapchars = 76;
  124. /**
  125. * Message format.
  126. *
  127. * @var string 'text' or 'html'
  128. */
  129. public $mailtype = 'text';
  130. /**
  131. * Character set (default: utf-8)
  132. *
  133. * @var string
  134. */
  135. public $charset = 'UTF-8';
  136. /**
  137. * Alternative message (for HTML messages only)
  138. *
  139. * @var string
  140. */
  141. public $alt_message = '';
  142. /**
  143. * Whether to validate e-mail addresses.
  144. *
  145. * @var bool
  146. */
  147. public $validate = FALSE;
  148. /**
  149. * X-Priority header value.
  150. *
  151. * @var int 1-5
  152. */
  153. public $priority = 3; // Default priority (1 - 5)
  154. /**
  155. * Newline character sequence.
  156. * Use "\r\n" to comply with RFC 822.
  157. *
  158. * @link http://www.ietf.org/rfc/rfc822.txt
  159. * @var string "\r\n" or "\n"
  160. */
  161. public $newline = "\n"; // Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822)
  162. /**
  163. * CRLF character sequence
  164. *
  165. * RFC 2045 specifies that for 'quoted-printable' encoding,
  166. * "\r\n" must be used. However, it appears that some servers
  167. * (even on the receiving end) don't handle it properly and
  168. * switching to "\n", while improper, is the only solution
  169. * that seems to work for all environments.
  170. *
  171. * @link http://www.ietf.org/rfc/rfc822.txt
  172. * @var string
  173. */
  174. public $crlf = "\n";
  175. /**
  176. * Whether to use Delivery Status Notification.
  177. *
  178. * @var bool
  179. */
  180. public $dsn = FALSE;
  181. /**
  182. * Whether to send multipart alternatives.
  183. * Yahoo! doesn't seem to like these.
  184. *
  185. * @var bool
  186. */
  187. public $send_multipart = TRUE;
  188. /**
  189. * Whether to send messages to BCC recipients in batches.
  190. *
  191. * @var bool
  192. */
  193. public $bcc_batch_mode = FALSE;
  194. /**
  195. * BCC Batch max number size.
  196. *
  197. * @see CI_Email::$bcc_batch_mode
  198. * @var int
  199. */
  200. public $bcc_batch_size = 200;
  201. // --------------------------------------------------------------------
  202. /**
  203. * Whether PHP is running in safe mode. Initialized by the class constructor.
  204. *
  205. * @var bool
  206. */
  207. protected $_safe_mode = FALSE;
  208. /**
  209. * Subject header
  210. *
  211. * @var string
  212. */
  213. protected $_subject = '';
  214. /**
  215. * Message body
  216. *
  217. * @var string
  218. */
  219. protected $_body = '';
  220. /**
  221. * Final message body to be sent.
  222. *
  223. * @var string
  224. */
  225. protected $_finalbody = '';
  226. /**
  227. * Final headers to send
  228. *
  229. * @var string
  230. */
  231. protected $_header_str = '';
  232. /**
  233. * SMTP Connection socket placeholder
  234. *
  235. * @var resource
  236. */
  237. protected $_smtp_connect = '';
  238. /**
  239. * Mail encoding
  240. *
  241. * @var string '8bit' or '7bit'
  242. */
  243. protected $_encoding = '8bit';
  244. /**
  245. * Whether to perform SMTP authentication
  246. *
  247. * @var bool
  248. */
  249. protected $_smtp_auth = FALSE;
  250. /**
  251. * Whether to send a Reply-To header
  252. *
  253. * @var bool
  254. */
  255. protected $_replyto_flag = FALSE;
  256. /**
  257. * Debug messages
  258. *
  259. * @see CI_Email::print_debugger()
  260. * @var string
  261. */
  262. protected $_debug_msg = array();
  263. /**
  264. * Recipients
  265. *
  266. * @var string[]
  267. */
  268. protected $_recipients = array();
  269. /**
  270. * CC Recipients
  271. *
  272. * @var string[]
  273. */
  274. protected $_cc_array = array();
  275. /**
  276. * BCC Recipients
  277. *
  278. * @var string[]
  279. */
  280. protected $_bcc_array = array();
  281. /**
  282. * Message headers
  283. *
  284. * @var string[]
  285. */
  286. protected $_headers = array();
  287. /**
  288. * Attachment data
  289. *
  290. * @var array
  291. */
  292. protected $_attachments = array();
  293. /**
  294. * Valid $protocol values
  295. *
  296. * @see CI_Email::$protocol
  297. * @var string[]
  298. */
  299. protected $_protocols = array('mail', 'sendmail', 'smtp');
  300. /**
  301. * Base charsets
  302. *
  303. * Character sets valid for 7-bit encoding,
  304. * excluding language suffix.
  305. *
  306. * @var string[]
  307. */
  308. protected $_base_charsets = array('us-ascii', 'iso-2022-');
  309. /**
  310. * Bit depths
  311. *
  312. * Valid mail encodings
  313. *
  314. * @see CI_Email::$_encoding
  315. * @var string[]
  316. */
  317. protected $_bit_depths = array('7bit', '8bit');
  318. /**
  319. * $priority translations
  320. *
  321. * Actual values to send with the X-Priority header
  322. *
  323. * @var string[]
  324. */
  325. protected $_priorities = array(
  326. 1 => '1 (Highest)',
  327. 2 => '2 (High)',
  328. 3 => '3 (Normal)',
  329. 4 => '4 (Low)',
  330. 5 => '5 (Lowest)'
  331. );
  332. /**
  333. * mbstring.func_overload flag
  334. *
  335. * @var bool
  336. */
  337. protected static $func_overload;
  338. // --------------------------------------------------------------------
  339. /**
  340. * Constructor - Sets Email Preferences
  341. *
  342. * The constructor can be passed an array of config values
  343. *
  344. * @param array $config = array()
  345. * @return void
  346. */
  347. public function __construct(array $config = array())
  348. {
  349. $this->charset = config_item('charset');
  350. $this->initialize($config);
  351. $this->_safe_mode = ( ! is_php('5.4') && ini_get('safe_mode'));
  352. isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
  353. log_message('info', 'Email Class Initialized');
  354. }
  355. // --------------------------------------------------------------------
  356. /**
  357. * Initialize preferences
  358. *
  359. * @param array $config
  360. * @return CI_Email
  361. */
  362. public function initialize(array $config = array())
  363. {
  364. $this->clear();
  365. foreach ($config as $key => $val)
  366. {
  367. if (isset($this->$key))
  368. {
  369. $method = 'set_'.$key;
  370. if (method_exists($this, $method))
  371. {
  372. $this->$method($val);
  373. }
  374. else
  375. {
  376. $this->$key = $val;
  377. }
  378. }
  379. }
  380. $this->charset = strtoupper($this->charset);
  381. $this->_smtp_auth = isset($this->smtp_user[0], $this->smtp_pass[0]);
  382. return $this;
  383. }
  384. // --------------------------------------------------------------------
  385. /**
  386. * Initialize the Email Data
  387. *
  388. * @param bool
  389. * @return CI_Email
  390. */
  391. public function clear($clear_attachments = FALSE)
  392. {
  393. $this->_subject = '';
  394. $this->_body = '';
  395. $this->_finalbody = '';
  396. $this->_header_str = '';
  397. $this->_replyto_flag = FALSE;
  398. $this->_recipients = array();
  399. $this->_cc_array = array();
  400. $this->_bcc_array = array();
  401. $this->_headers = array();
  402. $this->_debug_msg = array();
  403. $this->set_header('Date', $this->_set_date());
  404. if ($clear_attachments !== FALSE)
  405. {
  406. $this->_attachments = array();
  407. }
  408. return $this;
  409. }
  410. // --------------------------------------------------------------------
  411. /**
  412. * Set FROM
  413. *
  414. * @param string $from
  415. * @param string $name
  416. * @param string $return_path = NULL Return-Path
  417. * @return CI_Email
  418. */
  419. public function from($from, $name = '', $return_path = NULL)
  420. {
  421. if (preg_match('/\<(.*)\>/', $from, $match))
  422. {
  423. $from = $match[1];
  424. }
  425. if ($this->validate)
  426. {
  427. $this->validate_email($this->_str_to_array($from));
  428. if ($return_path)
  429. {
  430. $this->validate_email($this->_str_to_array($return_path));
  431. }
  432. }
  433. // prepare the display name
  434. if ($name !== '')
  435. {
  436. // only use Q encoding if there are characters that would require it
  437. if ( ! preg_match('/[\200-\377]/', $name))
  438. {
  439. // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes
  440. $name = '"'.addcslashes($name, "\0..\37\177'\"\\").'"';
  441. }
  442. else
  443. {
  444. $name = $this->_prep_q_encoding($name);
  445. }
  446. }
  447. $this->set_header('From', $name.' <'.$from.'>');
  448. isset($return_path) OR $return_path = $from;
  449. $this->set_header('Return-Path', '<'.$return_path.'>');
  450. return $this;
  451. }
  452. // --------------------------------------------------------------------
  453. /**
  454. * Set Reply-to
  455. *
  456. * @param string
  457. * @param string
  458. * @return CI_Email
  459. */
  460. public function reply_to($replyto, $name = '')
  461. {
  462. if (preg_match('/\<(.*)\>/', $replyto, $match))
  463. {
  464. $replyto = $match[1];
  465. }
  466. if ($this->validate)
  467. {
  468. $this->validate_email($this->_str_to_array($replyto));
  469. }
  470. if ($name !== '')
  471. {
  472. // only use Q encoding if there are characters that would require it
  473. if ( ! preg_match('/[\200-\377]/', $name))
  474. {
  475. // add slashes for non-printing characters, slashes, and double quotes, and surround it in double quotes
  476. $name = '"'.addcslashes($name, "\0..\37\177'\"\\").'"';
  477. }
  478. else
  479. {
  480. $name = $this->_prep_q_encoding($name);
  481. }
  482. }
  483. $this->set_header('Reply-To', $name.' <'.$replyto.'>');
  484. $this->_replyto_flag = TRUE;
  485. return $this;
  486. }
  487. // --------------------------------------------------------------------
  488. /**
  489. * Set Recipients
  490. *
  491. * @param string
  492. * @return CI_Email
  493. */
  494. public function to($to)
  495. {
  496. $to = $this->_str_to_array($to);
  497. $to = $this->clean_email($to);
  498. if ($this->validate)
  499. {
  500. $this->validate_email($to);
  501. }
  502. if ($this->_get_protocol() !== 'mail')
  503. {
  504. $this->set_header('To', implode(', ', $to));
  505. }
  506. $this->_recipients = $to;
  507. return $this;
  508. }
  509. // --------------------------------------------------------------------
  510. /**
  511. * Set CC
  512. *
  513. * @param string
  514. * @return CI_Email
  515. */
  516. public function cc($cc)
  517. {
  518. $cc = $this->clean_email($this->_str_to_array($cc));
  519. if ($this->validate)
  520. {
  521. $this->validate_email($cc);
  522. }
  523. $this->set_header('Cc', implode(', ', $cc));
  524. if ($this->_get_protocol() === 'smtp')
  525. {
  526. $this->_cc_array = $cc;
  527. }
  528. return $this;
  529. }
  530. // --------------------------------------------------------------------
  531. /**
  532. * Set BCC
  533. *
  534. * @param string
  535. * @param string
  536. * @return CI_Email
  537. */
  538. public function bcc($bcc, $limit = '')
  539. {
  540. if ($limit !== '' && is_numeric($limit))
  541. {
  542. $this->bcc_batch_mode = TRUE;
  543. $this->bcc_batch_size = $limit;
  544. }
  545. $bcc = $this->clean_email($this->_str_to_array($bcc));
  546. if ($this->validate)
  547. {
  548. $this->validate_email($bcc);
  549. }
  550. if ($this->_get_protocol() === 'smtp' OR ($this->bcc_batch_mode && count($bcc) > $this->bcc_batch_size))
  551. {
  552. $this->_bcc_array = $bcc;
  553. }
  554. else
  555. {
  556. $this->set_header('Bcc', implode(', ', $bcc));
  557. }
  558. return $this;
  559. }
  560. // --------------------------------------------------------------------
  561. /**
  562. * Set Email Subject
  563. *
  564. * @param string
  565. * @return CI_Email
  566. */
  567. public function subject($subject)
  568. {
  569. $subject = $this->_prep_q_encoding($subject);
  570. $this->set_header('Subject', $subject);
  571. return $this;
  572. }
  573. // --------------------------------------------------------------------
  574. /**
  575. * Set Body
  576. *
  577. * @param string
  578. * @return CI_Email
  579. */
  580. public function message($body)
  581. {
  582. $this->_body = rtrim(str_replace("\r", '', $body));
  583. /* strip slashes only if magic quotes is ON
  584. if we do it with magic quotes OFF, it strips real, user-inputted chars.
  585. NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and
  586. it will probably not exist in future versions at all.
  587. */
  588. if ( ! is_php('5.4') && get_magic_quotes_gpc())
  589. {
  590. $this->_body = stripslashes($this->_body);
  591. }
  592. return $this;
  593. }
  594. // --------------------------------------------------------------------
  595. /**
  596. * Assign file attachments
  597. *
  598. * @param string $file Can be local path, URL or buffered content
  599. * @param string $disposition = 'attachment'
  600. * @param string $newname = NULL
  601. * @param string $mime = ''
  602. * @return CI_Email
  603. */
  604. public function attach($file, $disposition = '', $newname = NULL, $mime = '')
  605. {
  606. if ($mime === '')
  607. {
  608. if (strpos($file, '://') === FALSE && ! file_exists($file))
  609. {
  610. $this->_set_error_message('lang:email_attachment_missing', $file);
  611. return FALSE;
  612. }
  613. if ( ! $fp = @fopen($file, 'rb'))
  614. {
  615. $this->_set_error_message('lang:email_attachment_unreadable', $file);
  616. return FALSE;
  617. }
  618. $file_content = stream_get_contents($fp);
  619. $mime = $this->_mime_types(pathinfo($file, PATHINFO_EXTENSION));
  620. fclose($fp);
  621. }
  622. else
  623. {
  624. $file_content =& $file; // buffered file
  625. }
  626. $this->_attachments[] = array(
  627. 'name' => array($file, $newname),
  628. 'disposition' => empty($disposition) ? 'attachment' : $disposition, // Can also be 'inline' Not sure if it matters
  629. 'type' => $mime,
  630. 'content' => chunk_split(base64_encode($file_content)),
  631. 'multipart' => 'mixed'
  632. );
  633. return $this;
  634. }
  635. // --------------------------------------------------------------------
  636. /**
  637. * Set and return attachment Content-ID
  638. *
  639. * Useful for attached inline pictures
  640. *
  641. * @param string $filename
  642. * @return string
  643. */
  644. public function attachment_cid($filename)
  645. {
  646. for ($i = 0, $c = count($this->_attachments); $i < $c; $i++)
  647. {
  648. if ($this->_attachments[$i]['name'][0] === $filename)
  649. {
  650. $this->_attachments[$i]['multipart'] = 'related';
  651. $this->_attachments[$i]['cid'] = uniqid(basename($this->_attachments[$i]['name'][0]).'@');
  652. return $this->_attachments[$i]['cid'];
  653. }
  654. }
  655. return FALSE;
  656. }
  657. // --------------------------------------------------------------------
  658. /**
  659. * Add a Header Item
  660. *
  661. * @param string
  662. * @param string
  663. * @return CI_Email
  664. */
  665. public function set_header($header, $value)
  666. {
  667. $this->_headers[$header] = str_replace(array("\n", "\r"), '', $value);
  668. return $this;
  669. }
  670. // --------------------------------------------------------------------
  671. /**
  672. * Convert a String to an Array
  673. *
  674. * @param string
  675. * @return array
  676. */
  677. protected function _str_to_array($email)
  678. {
  679. if ( ! is_array($email))
  680. {
  681. return (strpos($email, ',') !== FALSE)
  682. ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY)
  683. : (array) trim($email);
  684. }
  685. return $email;
  686. }
  687. // --------------------------------------------------------------------
  688. /**
  689. * Set Multipart Value
  690. *
  691. * @param string
  692. * @return CI_Email
  693. */
  694. public function set_alt_message($str)
  695. {
  696. $this->alt_message = (string) $str;
  697. return $this;
  698. }
  699. // --------------------------------------------------------------------
  700. /**
  701. * Set Mailtype
  702. *
  703. * @param string
  704. * @return CI_Email
  705. */
  706. public function set_mailtype($type = 'text')
  707. {
  708. $this->mailtype = ($type === 'html') ? 'html' : 'text';
  709. return $this;
  710. }
  711. // --------------------------------------------------------------------
  712. /**
  713. * Set Wordwrap
  714. *
  715. * @param bool
  716. * @return CI_Email
  717. */
  718. public function set_wordwrap($wordwrap = TRUE)
  719. {
  720. $this->wordwrap = (bool) $wordwrap;
  721. return $this;
  722. }
  723. // --------------------------------------------------------------------
  724. /**
  725. * Set Protocol
  726. *
  727. * @param string
  728. * @return CI_Email
  729. */
  730. public function set_protocol($protocol = 'mail')
  731. {
  732. $this->protocol = in_array($protocol, $this->_protocols, TRUE) ? strtolower($protocol) : 'mail';
  733. return $this;
  734. }
  735. // --------------------------------------------------------------------
  736. /**
  737. * Set Priority
  738. *
  739. * @param int
  740. * @return CI_Email
  741. */
  742. public function set_priority($n = 3)
  743. {
  744. $this->priority = preg_match('/^[1-5]$/', $n) ? (int) $n : 3;
  745. return $this;
  746. }
  747. // --------------------------------------------------------------------
  748. /**
  749. * Set Newline Character
  750. *
  751. * @param string
  752. * @return CI_Email
  753. */
  754. public function set_newline($newline = "\n")
  755. {
  756. $this->newline = in_array($newline, array("\n", "\r\n", "\r")) ? $newline : "\n";
  757. return $this;
  758. }
  759. // --------------------------------------------------------------------
  760. /**
  761. * Set CRLF
  762. *
  763. * @param string
  764. * @return CI_Email
  765. */
  766. public function set_crlf($crlf = "\n")
  767. {
  768. $this->crlf = ($crlf !== "\n" && $crlf !== "\r\n" && $crlf !== "\r") ? "\n" : $crlf;
  769. return $this;
  770. }
  771. // --------------------------------------------------------------------
  772. /**
  773. * Get the Message ID
  774. *
  775. * @return string
  776. */
  777. protected function _get_message_id()
  778. {
  779. $from = str_replace(array('>', '<'), '', $this->_headers['Return-Path']);
  780. return '<'.uniqid('').strstr($from, '@').'>';
  781. }
  782. // --------------------------------------------------------------------
  783. /**
  784. * Get Mail Protocol
  785. *
  786. * @return mixed
  787. */
  788. protected function _get_protocol()
  789. {
  790. $this->protocol = strtolower($this->protocol);
  791. in_array($this->protocol, $this->_protocols, TRUE) OR $this->protocol = 'mail';
  792. return $this->protocol;
  793. }
  794. // --------------------------------------------------------------------
  795. /**
  796. * Get Mail Encoding
  797. *
  798. * @return string
  799. */
  800. protected function _get_encoding()
  801. {
  802. in_array($this->_encoding, $this->_bit_depths) OR $this->_encoding = '8bit';
  803. foreach ($this->_base_charsets as $charset)
  804. {
  805. if (strpos($this->charset, $charset) === 0)
  806. {
  807. $this->_encoding = '7bit';
  808. }
  809. }
  810. return $this->_encoding;
  811. }
  812. // --------------------------------------------------------------------
  813. /**
  814. * Get content type (text/html/attachment)
  815. *
  816. * @return string
  817. */
  818. protected function _get_content_type()
  819. {
  820. if ($this->mailtype === 'html')
  821. {
  822. return empty($this->_attachments) ? 'html' : 'html-attach';
  823. }
  824. elseif ($this->mailtype === 'text' && ! empty($this->_attachments))
  825. {
  826. return 'plain-attach';
  827. }
  828. return 'plain';
  829. }
  830. // --------------------------------------------------------------------
  831. /**
  832. * Set RFC 822 Date
  833. *
  834. * @return string
  835. */
  836. protected function _set_date()
  837. {
  838. $timezone = date('Z');
  839. $operator = ($timezone[0] === '-') ? '-' : '+';
  840. $timezone = abs($timezone);
  841. $timezone = floor($timezone/3600) * 100 + ($timezone % 3600) / 60;
  842. return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone);
  843. }
  844. // --------------------------------------------------------------------
  845. /**
  846. * Mime message
  847. *
  848. * @return string
  849. */
  850. protected function _get_mime_message()
  851. {
  852. return 'This is a multi-part message in MIME format.'.$this->newline.'Your email application may not support this format.';
  853. }
  854. // --------------------------------------------------------------------
  855. /**
  856. * Validate Email Address
  857. *
  858. * @param string
  859. * @return bool
  860. */
  861. public function validate_email($email)
  862. {
  863. if ( ! is_array($email))
  864. {
  865. $this->_set_error_message('lang:email_must_be_array');
  866. return FALSE;
  867. }
  868. foreach ($email as $val)
  869. {
  870. if ( ! $this->valid_email($val))
  871. {
  872. $this->_set_error_message('lang:email_invalid_address', $val);
  873. return FALSE;
  874. }
  875. }
  876. return TRUE;
  877. }
  878. // --------------------------------------------------------------------
  879. /**
  880. * Email Validation
  881. *
  882. * @param string
  883. * @return bool
  884. */
  885. public function valid_email($email)
  886. {
  887. if (function_exists('idn_to_ascii') && strpos($email, '@'))
  888. {
  889. list($account, $domain) = explode('@', $email, 2);
  890. $domain = defined('INTL_IDNA_VARIANT_UTS46')
  891. ? idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46)
  892. : idn_to_ascii($domain);
  893. if ($domain !== FALSE)
  894. {
  895. $email = $account.'@'.$domain;
  896. }
  897. }
  898. return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
  899. }
  900. // --------------------------------------------------------------------
  901. /**
  902. * Clean Extended Email Address: Joe Smith <joe@smith.com>
  903. *
  904. * @param string
  905. * @return string
  906. */
  907. public function clean_email($email)
  908. {
  909. if ( ! is_array($email))
  910. {
  911. return preg_match('/\<(.*)\>/', $email, $match) ? $match[1] : $email;
  912. }
  913. $clean_email = array();
  914. foreach ($email as $addy)
  915. {
  916. $clean_email[] = preg_match('/\<(.*)\>/', $addy, $match) ? $match[1] : $addy;
  917. }
  918. return $clean_email;
  919. }
  920. // --------------------------------------------------------------------
  921. /**
  922. * Build alternative plain text message
  923. *
  924. * Provides the raw message for use in plain-text headers of
  925. * HTML-formatted emails.
  926. * If the user hasn't specified his own alternative message
  927. * it creates one by stripping the HTML
  928. *
  929. * @return string
  930. */
  931. protected function _get_alt_message()
  932. {
  933. if ( ! empty($this->alt_message))
  934. {
  935. return ($this->wordwrap)
  936. ? $this->word_wrap($this->alt_message, 76)
  937. : $this->alt_message;
  938. }
  939. $body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body;
  940. $body = str_replace("\t", '', preg_replace('#<!--(.*)--\>#', '', trim(strip_tags($body))));
  941. for ($i = 20; $i >= 3; $i--)
  942. {
  943. $body = str_replace(str_repeat("\n", $i), "\n\n", $body);
  944. }
  945. // Reduce multiple spaces
  946. $body = preg_replace('| +|', ' ', $body);
  947. return ($this->wordwrap)
  948. ? $this->word_wrap($body, 76)
  949. : $body;
  950. }
  951. // --------------------------------------------------------------------
  952. /**
  953. * Word Wrap
  954. *
  955. * @param string
  956. * @param int line-length limit
  957. * @return string
  958. */
  959. public function word_wrap($str, $charlim = NULL)
  960. {
  961. // Set the character limit, if not already present
  962. if (empty($charlim))
  963. {
  964. $charlim = empty($this->wrapchars) ? 76 : $this->wrapchars;
  965. }
  966. // Standardize newlines
  967. if (strpos($str, "\r") !== FALSE)
  968. {
  969. $str = str_replace(array("\r\n", "\r"), "\n", $str);
  970. }
  971. // Reduce multiple spaces at end of line
  972. $str = preg_replace('| +\n|', "\n", $str);
  973. // If the current word is surrounded by {unwrap} tags we'll
  974. // strip the entire chunk and replace it with a marker.
  975. $unwrap = array();
  976. if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches))
  977. {
  978. for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
  979. {
  980. $unwrap[] = $matches[1][$i];
  981. $str = str_replace($matches[0][$i], '{{unwrapped'.$i.'}}', $str);
  982. }
  983. }
  984. // Use PHP's native function to do the initial wordwrap.
  985. // We set the cut flag to FALSE so that any individual words that are
  986. // too long get left alone. In the next step we'll deal with them.
  987. $str = wordwrap($str, $charlim, "\n", FALSE);
  988. // Split the string into individual lines of text and cycle through them
  989. $output = '';
  990. foreach (explode("\n", $str) as $line)
  991. {
  992. // Is the line within the allowed character count?
  993. // If so we'll join it to the output and continue
  994. if (self::strlen($line) <= $charlim)
  995. {
  996. $output .= $line.$this->newline;
  997. continue;
  998. }
  999. $temp = '';
  1000. do
  1001. {
  1002. // If the over-length word is a URL we won't wrap it
  1003. if (preg_match('!\[url.+\]|://|www\.!', $line))
  1004. {
  1005. break;
  1006. }
  1007. // Trim the word down
  1008. $temp .= self::substr($line, 0, $charlim - 1);
  1009. $line = self::substr($line, $charlim - 1);
  1010. }
  1011. while (self::strlen($line) > $charlim);
  1012. // If $temp contains data it means we had to split up an over-length
  1013. // word into smaller chunks so we'll add it back to our current line
  1014. if ($temp !== '')
  1015. {
  1016. $output .= $temp.$this->newline;
  1017. }
  1018. $output .= $line.$this->newline;
  1019. }
  1020. // Put our markers back
  1021. if (count($unwrap) > 0)
  1022. {
  1023. foreach ($unwrap as $key => $val)
  1024. {
  1025. $output = str_replace('{{unwrapped'.$key.'}}', $val, $output);
  1026. }
  1027. }
  1028. return $output;
  1029. }
  1030. // --------------------------------------------------------------------
  1031. /**
  1032. * Build final headers
  1033. *
  1034. * @return void
  1035. */
  1036. protected function _build_headers()
  1037. {
  1038. $this->set_header('User-Agent', $this->useragent);
  1039. $this->set_header('X-Sender', $this->clean_email($this->_headers['From']));
  1040. $this->set_header('X-Mailer', $this->useragent);
  1041. $this->set_header('X-Priority', $this->_priorities[$this->priority]);
  1042. $this->set_header('Message-ID', $this->_get_message_id());
  1043. $this->set_header('Mime-Version', '1.0');
  1044. }
  1045. // --------------------------------------------------------------------
  1046. /**
  1047. * Write Headers as a string
  1048. *
  1049. * @return void
  1050. */
  1051. protected function _write_headers()
  1052. {
  1053. if ($this->protocol === 'mail')
  1054. {
  1055. if (isset($this->_headers['Subject']))
  1056. {
  1057. $this->_subject = $this->_headers['Subject'];
  1058. unset($this->_headers['Subject']);
  1059. }
  1060. }
  1061. reset($this->_headers);
  1062. $this->_header_str = '';
  1063. foreach ($this->_headers as $key => $val)
  1064. {
  1065. $val = trim($val);
  1066. if ($val !== '')
  1067. {
  1068. $this->_header_str .= $key.': '.$val.$this->newline;
  1069. }
  1070. }
  1071. if ($this->_get_protocol() === 'mail')
  1072. {
  1073. $this->_header_str = rtrim($this->_header_str);
  1074. }
  1075. }
  1076. // --------------------------------------------------------------------
  1077. /**
  1078. * Build Final Body and attachments
  1079. *
  1080. * @return bool
  1081. */
  1082. protected function _build_message()
  1083. {
  1084. if ($this->wordwrap === TRUE && $this->mailtype !== 'html')
  1085. {
  1086. $this->_body = $this->word_wrap($this->_body);
  1087. }
  1088. $this->_write_headers();
  1089. $hdr = ($this->_get_protocol() === 'mail') ? $this->newline : '';
  1090. $body = '';
  1091. switch ($this->_get_content_type())
  1092. {
  1093. case 'plain':
  1094. $hdr .= 'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1095. .'Content-Transfer-Encoding: '.$this->_get_encoding();
  1096. if ($this->_get_protocol() === 'mail')
  1097. {
  1098. $this->_header_str .= $hdr;
  1099. $this->_finalbody = $this->_body;
  1100. }
  1101. else
  1102. {
  1103. $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_body;
  1104. }
  1105. return;
  1106. case 'html':
  1107. if ($this->send_multipart === FALSE)
  1108. {
  1109. $hdr .= 'Content-Type: text/html; charset='.$this->charset.$this->newline
  1110. .'Content-Transfer-Encoding: quoted-printable';
  1111. }
  1112. else
  1113. {
  1114. $boundary = uniqid('B_ALT_');
  1115. $hdr .= 'Content-Type: multipart/alternative; boundary="'.$boundary.'"';
  1116. $body .= $this->_get_mime_message().$this->newline.$this->newline
  1117. .'--'.$boundary.$this->newline
  1118. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1119. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline
  1120. .$this->_get_alt_message().$this->newline.$this->newline
  1121. .'--'.$boundary.$this->newline
  1122. .'Content-Type: text/html; charset='.$this->charset.$this->newline
  1123. .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline;
  1124. }
  1125. $this->_finalbody = $body.$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline;
  1126. if ($this->_get_protocol() === 'mail')
  1127. {
  1128. $this->_header_str .= $hdr;
  1129. }
  1130. else
  1131. {
  1132. $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_finalbody;
  1133. }
  1134. if ($this->send_multipart !== FALSE)
  1135. {
  1136. $this->_finalbody .= '--'.$boundary.'--';
  1137. }
  1138. return;
  1139. case 'plain-attach':
  1140. $boundary = uniqid('B_ATC_');
  1141. $hdr .= 'Content-Type: multipart/mixed; boundary="'.$boundary.'"';
  1142. if ($this->_get_protocol() === 'mail')
  1143. {
  1144. $this->_header_str .= $hdr;
  1145. }
  1146. $body .= $this->_get_mime_message().$this->newline
  1147. .$this->newline
  1148. .'--'.$boundary.$this->newline
  1149. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1150. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline
  1151. .$this->newline
  1152. .$this->_body.$this->newline.$this->newline;
  1153. $this->_append_attachments($body, $boundary);
  1154. break;
  1155. case 'html-attach':
  1156. $alt_boundary = uniqid('B_ALT_');
  1157. $last_boundary = NULL;
  1158. if ($this->_attachments_have_multipart('mixed'))
  1159. {
  1160. $atc_boundary = uniqid('B_ATC_');
  1161. $hdr .= 'Content-Type: multipart/mixed; boundary="'.$atc_boundary.'"';
  1162. $last_boundary = $atc_boundary;
  1163. }
  1164. if ($this->_attachments_have_multipart('related'))
  1165. {
  1166. $rel_boundary = uniqid('B_REL_');
  1167. $rel_boundary_header = 'Content-Type: multipart/related; boundary="'.$rel_boundary.'"';
  1168. if (isset($last_boundary))
  1169. {
  1170. $body .= '--'.$last_boundary.$this->newline.$rel_boundary_header;
  1171. }
  1172. else
  1173. {
  1174. $hdr .= $rel_boundary_header;
  1175. }
  1176. $last_boundary = $rel_boundary;
  1177. }
  1178. if ($this->_get_protocol() === 'mail')
  1179. {
  1180. $this->_header_str .= $hdr;
  1181. }
  1182. self::strlen($body) && $body .= $this->newline.$this->newline;
  1183. $body .= $this->_get_mime_message().$this->newline.$this->newline
  1184. .'--'.$last_boundary.$this->newline
  1185. .'Content-Type: multipart/alternative; boundary="'.$alt_boundary.'"'.$this->newline.$this->newline
  1186. .'--'.$alt_boundary.$this->newline
  1187. .'Content-Type: text/plain; charset='.$this->charset.$this->newline
  1188. .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline
  1189. .$this->_get_alt_message().$this->newline.$this->newline
  1190. .'--'.$alt_boundary.$this->newline
  1191. .'Content-Type: text/html; charset='.$this->charset.$this->newline
  1192. .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline
  1193. .$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline
  1194. .'--'.$alt_boundary.'--'.$this->newline.$this->newline;
  1195. if ( ! empty($rel_boundary))
  1196. {
  1197. $body .= $this->newline.$this->newline;
  1198. $this->_append_attachments($body, $rel_boundary, 'related');
  1199. }
  1200. // multipart/mixed attachments
  1201. if ( ! empty($atc_boundary))
  1202. {
  1203. $body .= $this->newline.$this->newline;
  1204. $this->_append_attachments($body, $atc_boundary, 'mixed');
  1205. }
  1206. break;
  1207. }
  1208. $this->_finalbody = ($this->_get_protocol() === 'mail')
  1209. ? $body
  1210. : $hdr.$this->newline.$this->newline.$body;
  1211. return TRUE;
  1212. }
  1213. // --------------------------------------------------------------------
  1214. protected function _attachments_have_multipart($type)
  1215. {
  1216. foreach ($this->_attachments as &$attachment)
  1217. {
  1218. if ($attachment['multipart'] === $type)
  1219. {
  1220. return TRUE;
  1221. }
  1222. }
  1223. return FALSE;
  1224. }
  1225. // --------------------------------------------------------------------
  1226. /**
  1227. * Prepares attachment string
  1228. *
  1229. * @param string $body Message body to append to
  1230. * @param string $boundary Multipart boundary
  1231. * @param string $multipart When provided, only attachments of this type will be processed
  1232. * @return string
  1233. */
  1234. protected function _append_attachments(&$body, $boundary, $multipart = null)
  1235. {
  1236. for ($i = 0, $c = count($this->_attachments); $i < $c; $i++)
  1237. {
  1238. if (isset($multipart) && $this->_attachments[$i]['multipart'] !== $multipart)
  1239. {
  1240. continue;
  1241. }
  1242. $name = isset($this->_attachments[$i]['name'][1])
  1243. ? $this->_attachments[$i]['name'][1]
  1244. : basename($this->_attachments[$i]['name'][0]);
  1245. $body .= '--'.$boundary.$this->newline
  1246. .'Content-Type: '.$this->_attachments[$i]['type'].'; name="'.$name.'"'.$this->newline
  1247. .'Content-Disposition: '.$this->_attachments[$i]['disposition'].';'.$this->newline
  1248. .'Content-Transfer-Encoding: base64'.$this->newline
  1249. .(empty($this->_attachments[$i]['cid']) ? '' : 'Content-ID: <'.$this->_attachments[$i]['cid'].'>'.$this->newline)
  1250. .$this->newline
  1251. .$this->_attachments[$i]['content'].$this->newline;
  1252. }
  1253. // $name won't be set if no attachments were appended,
  1254. // and therefore a boundary wouldn't be necessary
  1255. empty($name) OR $body .= '--'.$boundary.'--';
  1256. }
  1257. // --------------------------------------------------------------------
  1258. /**
  1259. * Prep Quoted Printable
  1260. *
  1261. * Prepares string for Quoted-Printable Content-Transfer-Encoding
  1262. * Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt
  1263. *
  1264. * @param string
  1265. * @return string
  1266. */
  1267. protected function _prep_quoted_printable($str)
  1268. {
  1269. // ASCII code numbers for "safe" characters that can always be
  1270. // used literally, without encoding, as described in RFC 2049.
  1271. // http://www.ietf.org/rfc/rfc2049.txt
  1272. static $ascii_safe_chars = array(
  1273. // ' ( ) + , - . / : = ?
  1274. 39, 40, 41, 43, 44, 45, 46, 47, 58, 61, 63,
  1275. // numbers
  1276. 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
  1277. // upper-case letters
  1278. 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
  1279. // lower-case letters
  1280. 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122
  1281. );
  1282. // We are intentionally wrapping so mail servers will encode characters
  1283. // properly and MUAs will behave, so {unwrap} must go!
  1284. $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str);
  1285. // RFC 2045 specifies CRLF as "\r\n".
  1286. // However, many developers choose to override that and violate
  1287. // the RFC rules due to (apparently) a bug in MS Exchange,
  1288. // which only works with "\n".
  1289. if ($this->crlf === "\r\n")
  1290. {
  1291. return quoted_printable_encode($str);
  1292. }
  1293. // Reduce multiple spaces & remove nulls
  1294. $str = preg_replace(array('| +|', '/\x00+/'), array(' ', ''), $str);
  1295. // Standardize newlines
  1296. if (strpos($str, "\r") !== FALSE)
  1297. {
  1298. $str = str_replace(array("\r\n", "\r"), "\n", $str);
  1299. }
  1300. $escape = '=';
  1301. $output = '';
  1302. foreach (explode("\n", $str) as $line)
  1303. {
  1304. $length = self::strlen($line);
  1305. $temp = '';
  1306. // Loop through each character in the line to add soft-wrap
  1307. // characters at the end of a line " =\r\n" and add the newly
  1308. // processed line(s) to the output (see comment on $crlf class property)
  1309. for ($i = 0; $i < $length; $i++)
  1310. {
  1311. // Grab the next character
  1312. $char = $line[$i];
  1313. $ascii = ord($char);
  1314. // Convert spaces and tabs but only if it's the end of the line
  1315. if ($ascii === 32 OR $ascii === 9)
  1316. {
  1317. if ($i === ($length - 1))
  1318. {
  1319. $char = $escape.sprintf('%02s', dechex($ascii));
  1320. }
  1321. }
  1322. // DO NOT move this below the $ascii_safe_chars line!
  1323. //
  1324. // = (equals) signs are allowed by RFC2049, but must be encoded
  1325. // as they are the encoding delimiter!
  1326. elseif ($ascii === 61)
  1327. {
  1328. $char = $escape.strtoupper(sprintf('%02s', dechex($ascii))); // =3D
  1329. }
  1330. elseif ( ! in_array($ascii, $ascii_safe_chars, TRUE))
  1331. {
  1332. $char = $escape.strtoupper(sprintf('%02s', dechex($ascii)));
  1333. }
  1334. // If we're at the character limit, add the line to the output,
  1335. // reset our temp variable, and keep on chuggin'
  1336. if ((self::strlen($temp) + self::strlen($char)) >= 76)
  1337. {
  1338. $output .= $temp.$escape.$this->crlf;
  1339. $temp = '';
  1340. }
  1341. // Add the character to our temporary line
  1342. $temp .= $char;
  1343. }
  1344. // Add our completed line to the output
  1345. $output .= $temp.$this->crlf;
  1346. }
  1347. // get rid of extra CRLF tacked onto the end
  1348. return self::substr($output, 0, self::strlen($this->crlf) * -1);
  1349. }
  1350. // --------------------------------------------------------------------
  1351. /**
  1352. * Prep Q Encoding
  1353. *
  1354. * Performs "Q Encoding" on a string for use in email headers.
  1355. * It's related but not identical to quoted-printable, so it has its
  1356. * own method.
  1357. *
  1358. * @param string
  1359. * @return string
  1360. */
  1361. protected function _prep_q_encoding($str)
  1362. {
  1363. $str = str_replace(array("\r", "\n"), '', $str);
  1364. if ($this->charset === 'UTF-8')
  1365. {
  1366. // Note: We used to have mb_encode_mimeheader() as the first choice
  1367. // here, but it turned out to be buggy and unreliable. DO NOT
  1368. // re-add it! -- Narf
  1369. if (ICONV_ENABLED === TRUE)
  1370. {
  1371. $output = @iconv_mime_encode('', $str,
  1372. array(
  1373. 'scheme' => 'Q',
  1374. 'line-length' => 76,
  1375. 'input-charset' => $this->charset,
  1376. 'output-charset' => $this->charset,
  1377. 'line-break-chars' => $this->crlf
  1378. )
  1379. );
  1380. // There are reports that iconv_mime_encode() might fail and return FALSE
  1381. if ($output !== FALSE)
  1382. {
  1383. // iconv_mime_encode() will always put a header field name.
  1384. // We've passed it an empty one, but it still prepends our
  1385. // encoded string with ': ', so we need to strip it.
  1386. return self::substr($output, 2);
  1387. }
  1388. $chars = iconv_strlen($str, 'UTF-8');
  1389. }
  1390. elseif (MB_ENABLED === TRUE)
  1391. {
  1392. $chars = mb_strlen($str, 'UTF-8');
  1393. }
  1394. }
  1395. // We might already have this set for UTF-8
  1396. isset($chars) OR $chars = self::strlen($str);
  1397. $output = '=?'.$this->charset.'?Q?';
  1398. for ($i = 0, $length = self::strlen($output); $i < $chars; $i++)
  1399. {
  1400. $chr = ($this->charset === 'UTF-8' && ICONV_ENABLED === TRUE)
  1401. ? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2))
  1402. : '='.strtoupper(bin2hex($str[$i]));
  1403. // RFC 2045 sets a limit of 76 characters per line.
  1404. // We'll append ?= to the end of each line though.
  1405. if ($length + ($l = self::strlen($chr)) > 74)
  1406. {
  1407. $output .= '?='.$this->crlf // EOL
  1408. .' =?'.$this->charset.'?Q?'.$chr; // New line
  1409. $length = 6 + self::strlen($this->charset) + $l; // Reset the length for the new line
  1410. }
  1411. else
  1412. {
  1413. $output .= $chr;
  1414. $length += $l;
  1415. }
  1416. }
  1417. // End the header
  1418. return $output.'?=';
  1419. }
  1420. // --------------------------------------------------------------------
  1421. /**
  1422. * Send Email
  1423. *
  1424. * @param bool $auto_clear = TRUE
  1425. * @return bool
  1426. */
  1427. public function send($auto_clear = TRUE)
  1428. {
  1429. if ( ! isset($this->_headers['From']))
  1430. {
  1431. $this->_set_error_message('lang:email_no_from');
  1432. return FALSE;
  1433. }
  1434. if ($this->_replyto_flag === FALSE)
  1435. {
  1436. $this->reply_to($this->_headers['From']);
  1437. }
  1438. if ( ! isset($this->_recipients) && ! isset($this->_headers['To'])
  1439. && ! isset($this->_bcc_array) && ! isset($this->_headers['Bcc'])
  1440. && ! isset($this->_headers['Cc']))
  1441. {
  1442. $this->_set_error_message('lang:email_no_recipients');
  1443. return FALSE;
  1444. }
  1445. $this->_build_headers();
  1446. if ($this->bcc_batch_mode && count($this->_bcc_array) > $this->bcc_batch_size)
  1447. {
  1448. $result = $this->batch_bcc_send();
  1449. if ($result && $auto_clear)
  1450. {
  1451. $this->clear();
  1452. }
  1453. return $result;
  1454. }
  1455. if ($this->_build_message() === FALSE)
  1456. {
  1457. return FALSE;
  1458. }
  1459. $result = $this->_spool_email();
  1460. if ($result && $auto_clear)
  1461. {
  1462. $this->clear();
  1463. }
  1464. return $result;
  1465. }
  1466. // --------------------------------------------------------------------
  1467. /**
  1468. * Batch Bcc Send. Sends groups of BCCs in batches
  1469. *
  1470. * @return void
  1471. */
  1472. public function batch_bcc_send()
  1473. {
  1474. $float = $this->bcc_batch_size - 1;
  1475. $set = '';
  1476. $chunk = array();
  1477. for ($i = 0, $c = count($this->_bcc_array); $i < $c; $i++)
  1478. {
  1479. if (isset($this->_bcc_array[$i]))
  1480. {
  1481. $set .= ', '.$this->_bcc_array[$i];
  1482. }
  1483. if ($i === $float)
  1484. {
  1485. $chunk[] = self::substr($set, 1);
  1486. $float += $this->bcc_batch_size;
  1487. $set = '';
  1488. }
  1489. if ($i === $c-1)
  1490. {
  1491. $chunk[] = self::substr($set, 1);
  1492. }
  1493. }
  1494. for ($i = 0, $c = count($chunk); $i < $c; $i++)
  1495. {
  1496. unset($this->_headers['Bcc']);
  1497. $bcc = $this->clean_email($this->_str_to_array($chunk[$i]));
  1498. if ($this->protocol !== 'smtp')
  1499. {
  1500. $this->set_header('Bcc', implode(', ', $bcc));
  1501. }
  1502. else
  1503. {
  1504. $this->_bcc_array = $bcc;
  1505. }
  1506. if ($this->_build_message() === FALSE)
  1507. {
  1508. return FALSE;
  1509. }
  1510. $this->_spool_email();
  1511. }
  1512. }
  1513. // --------------------------------------------------------------------
  1514. /**
  1515. * Unwrap special elements
  1516. *
  1517. * @return void
  1518. */
  1519. protected function _unwrap_specials()
  1520. {
  1521. $this->_finalbody = preg_replace_callback('/\{unwrap\}(.*?)\{\/unwrap\}/si', array($this, '_remove_nl_callback'), $this->_finalbody);
  1522. }
  1523. // --------------------------------------------------------------------
  1524. /**
  1525. * Strip line-breaks via callback
  1526. *
  1527. * @param string $matches
  1528. * @return string
  1529. */
  1530. protected function _remove_nl_callback($matches)
  1531. {
  1532. if (strpos($matches[1], "\r") !== FALSE OR strpos($matches[1], "\n") !== FALSE)
  1533. {
  1534. $matches[1] = str_replace(array("\r\n", "\r", "\n"), '', $matches[1]);
  1535. }
  1536. return $matches[1];
  1537. }
  1538. // --------------------------------------------------------------------
  1539. /**
  1540. * Spool mail to the mail server
  1541. *
  1542. * @return bool
  1543. */
  1544. protected function _spool_email()
  1545. {
  1546. $this->_unwrap_specials();
  1547. $protocol = $this->_get_protocol();
  1548. $method = '_send_with_'.$protocol;
  1549. if ( ! $this->$method())
  1550. {
  1551. $this->_set_error_message('lang:email_send_failure_'.($protocol === 'mail' ? 'phpmail' : $protocol));
  1552. return FALSE;
  1553. }
  1554. $this->_set_error_message('lang:email_sent', $protocol);
  1555. return TRUE;
  1556. }
  1557. // --------------------------------------------------------------------
  1558. /**
  1559. * Validate email for shell
  1560. *
  1561. * Applies stricter, shell-safe validation to email addresses.
  1562. * Introduced to prevent RCE via sendmail's -f option.
  1563. *
  1564. * @see https://github.com/bcit-ci/CodeIgniter/issues/4963
  1565. * @see https://gist.github.com/Zenexer/40d02da5e07f151adeaeeaa11af9ab36
  1566. * @license https://creativecommons.org/publicdomain/zero/1.0/ CC0 1.0, Public Domain
  1567. *
  1568. * Credits for the base concept go to Paul Buonopane <paul@namepros.com>
  1569. *
  1570. * @param string $email
  1571. * @return bool
  1572. */
  1573. protected function _validate_email_for_shell(&$email)
  1574. {
  1575. if (function_exists('idn_to_ascii') && strpos($email, '@'))
  1576. {
  1577. list($account, $domain) = explode('@', $email, 2);
  1578. $domain = defined('INTL_IDNA_VARIANT_UTS46')
  1579. ? idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46)
  1580. : idn_to_ascii($domain);
  1581. if ($domain !== FALSE)
  1582. {
  1583. $email = $account.'@'.$domain;
  1584. }
  1585. }
  1586. return (filter_var($email, FILTER_VALIDATE_EMAIL) === $email && preg_match('#\A[a-z0-9._+-]+@[a-z0-9.-]{1,253}\z#i', $email));
  1587. }
  1588. // --------------------------------------------------------------------
  1589. /**
  1590. * Send using mail()
  1591. *
  1592. * @return bool
  1593. */
  1594. protected function _send_with_mail()
  1595. {
  1596. if (is_array($this->_recipients))
  1597. {
  1598. $this->_recipients = implode(', ', $this->_recipients);
  1599. }
  1600. // _validate_email_for_shell() below accepts by reference,
  1601. // so this needs to be assigned to a variable
  1602. $from = $this->clean_email($this->_headers['Return-Path']);
  1603. if ($this->_safe_mode === TRUE || ! $this->_validate_email_for_shell($from))
  1604. {
  1605. return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str);
  1606. }
  1607. else
  1608. {
  1609. // most documentation of sendmail using the "-f" flag lacks a space after it, however
  1610. // we've encountered servers that seem to require it to be in place.
  1611. return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, '-f '.$from);
  1612. }
  1613. }
  1614. // --------------------------------------------------------------------
  1615. /**
  1616. * Send using Sendmail
  1617. *
  1618. * @return bool
  1619. */
  1620. protected function _send_with_sendmail()
  1621. {
  1622. // _validate_email_for_shell() below accepts by reference,
  1623. // so this needs to be assigned to a variable
  1624. $from = $this->clean_email($this->_headers['From']);
  1625. if ($this->_validate_email_for_shell($from))
  1626. {
  1627. $from = '-f '.$from;
  1628. }
  1629. else
  1630. {
  1631. $from = '';
  1632. }
  1633. // is popen() enabled?
  1634. if ( ! function_usable('popen') OR FALSE === ($fp = @popen($this->mailpath.' -oi '.$from.' -t', 'w')))
  1635. {
  1636. // server probably has popen disabled, so nothing we can do to get a verbose error.
  1637. return FALSE;
  1638. }
  1639. fputs($fp, $this->_header_str);
  1640. fputs($fp, $this->_finalbody);
  1641. $status = pclose($fp);
  1642. if ($status !== 0)
  1643. {
  1644. $this->_set_error_message('lang:email_exit_status', $status);
  1645. $this->_set_error_message('lang:email_no_socket');
  1646. return FALSE;
  1647. }
  1648. return TRUE;
  1649. }
  1650. // --------------------------------------------------------------------
  1651. /**
  1652. * Send using SMTP
  1653. *
  1654. * @return bool
  1655. */
  1656. protected function _send_with_smtp()
  1657. {
  1658. if ($this->smtp_host === '')
  1659. {
  1660. $this->_set_error_message('lang:email_no_hostname');
  1661. return FALSE;
  1662. }
  1663. if ( ! $this->_smtp_connect() OR ! $this->_smtp_authenticate())
  1664. {
  1665. return FALSE;
  1666. }
  1667. if ( ! $this->_send_command('from', $this->clean_email($this->_headers['From'])))
  1668. {
  1669. $this->_smtp_end();
  1670. return FALSE;
  1671. }
  1672. foreach ($this->_recipients as $val)
  1673. {
  1674. if ( ! $this->_send_command('to', $val))
  1675. {
  1676. $this->_smtp_end();
  1677. return FALSE;
  1678. }
  1679. }
  1680. if (count($this->_cc_array) > 0)
  1681. {
  1682. foreach ($this->_cc_array as $val)
  1683. {
  1684. if ($val !== '' && ! $this->_send_command('to', $val))
  1685. {
  1686. $this->_smtp_end();
  1687. return FALSE;
  1688. }
  1689. }
  1690. }
  1691. if (count($this->_bcc_array) > 0)
  1692. {
  1693. foreach ($this->_bcc_array as $val)
  1694. {
  1695. if ($val !== '' && ! $this->_send_command('to', $val))
  1696. {
  1697. $this->_smtp_end();
  1698. return FALSE;
  1699. }
  1700. }
  1701. }
  1702. if ( ! $this->_send_command('data'))
  1703. {
  1704. $this->_smtp_end();
  1705. return FALSE;
  1706. }
  1707. // perform dot transformation on any lines that begin with a dot
  1708. $this->_send_data($this->_header_str.preg_replace('/^\./m', '..$1', $this->_finalbody));
  1709. $this->_send_data('.');
  1710. $reply = $this->_get_smtp_data();
  1711. $this->_set_error_message($reply);
  1712. $this->_smtp_end();
  1713. if (strpos($reply, '250') !== 0)
  1714. {
  1715. $this->_set_error_message('lang:email_smtp_error', $reply);
  1716. return FALSE;
  1717. }
  1718. return TRUE;
  1719. }
  1720. // --------------------------------------------------------------------
  1721. /**
  1722. * SMTP End
  1723. *
  1724. * Shortcut to send RSET or QUIT depending on keep-alive
  1725. *
  1726. * @return void
  1727. */
  1728. protected function _smtp_end()
  1729. {
  1730. ($this->smtp_keepalive)
  1731. ? $this->_send_command('reset')
  1732. : $this->_send_command('quit');
  1733. }
  1734. // --------------------------------------------------------------------
  1735. /**
  1736. * SMTP Connect
  1737. *
  1738. * @return string
  1739. */
  1740. protected function _smtp_connect()
  1741. {
  1742. if (is_resource($this->_smtp_connect))
  1743. {
  1744. return TRUE;
  1745. }
  1746. $ssl = ($this->smtp_crypto === 'ssl') ? 'ssl://' : '';
  1747. $this->_smtp_connect = fsockopen($ssl.$this->smtp_host,
  1748. $this->smtp_port,
  1749. $errno,
  1750. $errstr,
  1751. $this->smtp_timeout);
  1752. if ( ! is_resource($this->_smtp_connect))
  1753. {
  1754. $this->_set_error_message('lang:email_smtp_error', $errno.' '.$errstr);
  1755. return FALSE;
  1756. }
  1757. stream_set_timeout($this->_smtp_connect, $this->smtp_timeout);
  1758. $this->_set_error_message($this->_get_smtp_data());
  1759. if ($this->smtp_crypto === 'tls')
  1760. {
  1761. $this->_send_command('hello');
  1762. $this->_send_command('starttls');
  1763. /**
  1764. * STREAM_CRYPTO_METHOD_TLS_CLIENT is quite the mess ...
  1765. *
  1766. * - On PHP <5.6 it doesn't even mean TLS, but SSL 2.0, and there's no option to use actual TLS
  1767. * - On PHP 5.6.0-5.6.6, >=7.2 it means negotiation with any of TLS 1.0, 1.1, 1.2
  1768. * - On PHP 5.6.7-7.1.* it means only TLS 1.0
  1769. *
  1770. * We want the negotiation, so we'll force it below ...
  1771. */
  1772. $method = is_php('5.6')
  1773. ? STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
  1774. : STREAM_CRYPTO_METHOD_TLS_CLIENT;
  1775. $crypto = stream_socket_enable_crypto($this->_smtp_connect, TRUE, $method);
  1776. if ($crypto !== TRUE)
  1777. {
  1778. $this->_set_error_message('lang:email_smtp_error', $this->_get_smtp_data());
  1779. return FALSE;
  1780. }
  1781. }
  1782. return $this->_send_command('hello');
  1783. }
  1784. // --------------------------------------------------------------------
  1785. /**
  1786. * Send SMTP command
  1787. *
  1788. * @param string
  1789. * @param string
  1790. * @return bool
  1791. */
  1792. protected function _send_command($cmd, $data = '')
  1793. {
  1794. switch ($cmd)
  1795. {
  1796. case 'hello' :
  1797. if ($this->_smtp_auth OR $this->_get_encoding() === '8bit')
  1798. {
  1799. $this->_send_data('EHLO '.$this->_get_hostname());
  1800. }
  1801. else
  1802. {
  1803. $this->_send_data('HELO '.$this->_get_hostname());
  1804. }
  1805. $resp = 250;
  1806. break;
  1807. case 'starttls' :
  1808. $this->_send_data('STARTTLS');
  1809. $resp = 220;
  1810. break;
  1811. case 'from' :
  1812. $this->_send_data('MAIL FROM:<'.$data.'>');
  1813. $resp = 250;
  1814. break;
  1815. case 'to' :
  1816. if ($this->dsn)
  1817. {
  1818. $this->_send_data('RCPT TO:<'.$data.'> NOTIFY=SUCCESS,DELAY,FAILURE ORCPT=rfc822;'.$data);
  1819. }
  1820. else
  1821. {
  1822. $this->_send_data('RCPT TO:<'.$data.'>');
  1823. }
  1824. $resp = 250;
  1825. break;
  1826. case 'data' :
  1827. $this->_send_data('DATA');
  1828. $resp = 354;
  1829. break;
  1830. case 'reset':
  1831. $this->_send_data('RSET');
  1832. $resp = 250;
  1833. break;
  1834. case 'quit' :
  1835. $this->_send_data('QUIT');
  1836. $resp = 221;
  1837. break;
  1838. }
  1839. $reply = $this->_get_smtp_data();
  1840. $this->_debug_msg[] = '<pre>'.$cmd.': '.$reply.'</pre>';
  1841. if ((int) self::substr($reply, 0, 3) !== $resp)
  1842. {
  1843. $this->_set_error_message('lang:email_smtp_error', $reply);
  1844. return FALSE;
  1845. }
  1846. if ($cmd === 'quit')
  1847. {
  1848. fclose($this->_smtp_connect);
  1849. }
  1850. return TRUE;
  1851. }
  1852. // --------------------------------------------------------------------
  1853. /**
  1854. * SMTP Authenticate
  1855. *
  1856. * @return bool
  1857. */
  1858. protected function _smtp_authenticate()
  1859. {
  1860. if ( ! $this->_smtp_auth)
  1861. {
  1862. return TRUE;
  1863. }
  1864. if ($this->smtp_user === '' && $this->smtp_pass === '')
  1865. {
  1866. $this->_set_error_message('lang:email_no_smtp_unpw');
  1867. return FALSE;
  1868. }
  1869. $this->_send_data('AUTH LOGIN');
  1870. $reply = $this->_get_smtp_data();
  1871. if (strpos($reply, '503') === 0) // Already authenticated
  1872. {
  1873. return TRUE;
  1874. }
  1875. elseif (strpos($reply, '334') !== 0)
  1876. {
  1877. $this->_set_error_message('lang:email_failed_smtp_login', $reply);
  1878. return FALSE;
  1879. }
  1880. $this->_send_data(base64_encode($this->smtp_user));
  1881. $reply = $this->_get_smtp_data();
  1882. if (strpos($reply, '334') !== 0)
  1883. {
  1884. $this->_set_error_message('lang:email_smtp_auth_un', $reply);
  1885. return FALSE;
  1886. }
  1887. $this->_send_data(base64_encode($this->smtp_pass));
  1888. $reply = $this->_get_smtp_data();
  1889. if (strpos($reply, '235') !== 0)
  1890. {
  1891. $this->_set_error_message('lang:email_smtp_auth_pw', $reply);
  1892. return FALSE;
  1893. }
  1894. if ($this->smtp_keepalive)
  1895. {
  1896. $this->_smtp_auth = FALSE;
  1897. }
  1898. return TRUE;
  1899. }
  1900. // --------------------------------------------------------------------
  1901. /**
  1902. * Send SMTP data
  1903. *
  1904. * @param string $data
  1905. * @return bool
  1906. */
  1907. protected function _send_data($data)
  1908. {
  1909. $data .= $this->newline;
  1910. for ($written = $timestamp = 0, $length = self::strlen($data); $written < $length; $written += $result)
  1911. {
  1912. if (($result = fwrite($this->_smtp_connect, self::substr($data, $written))) === FALSE)
  1913. {
  1914. break;
  1915. }
  1916. // See https://bugs.php.net/bug.php?id=39598 and http://php.net/manual/en/function.fwrite.php#96951
  1917. elseif ($result === 0)
  1918. {
  1919. if ($timestamp === 0)
  1920. {
  1921. $timestamp = time();
  1922. }
  1923. elseif ($timestamp < (time() - $this->smtp_timeout))
  1924. {
  1925. $result = FALSE;
  1926. break;
  1927. }
  1928. usleep(250000);
  1929. continue;
  1930. }
  1931. $timestamp = 0;
  1932. }
  1933. if ($result === FALSE)
  1934. {
  1935. $this->_set_error_message('lang:email_smtp_data_failure', $data);
  1936. return FALSE;
  1937. }
  1938. return TRUE;
  1939. }
  1940. // --------------------------------------------------------------------
  1941. /**
  1942. * Get SMTP data
  1943. *
  1944. * @return string
  1945. */
  1946. protected function _get_smtp_data()
  1947. {
  1948. $data = '';
  1949. while ($str = fgets($this->_smtp_connect, 512))
  1950. {
  1951. $data .= $str;
  1952. if ($str[3] === ' ')
  1953. {
  1954. break;
  1955. }
  1956. }
  1957. return $data;
  1958. }
  1959. // --------------------------------------------------------------------
  1960. /**
  1961. * Get Hostname
  1962. *
  1963. * There are only two legal types of hostname - either a fully
  1964. * qualified domain name (eg: "mail.example.com") or an IP literal
  1965. * (eg: "[1.2.3.4]").
  1966. *
  1967. * @link https://tools.ietf.org/html/rfc5321#section-2.3.5
  1968. * @link http://cbl.abuseat.org/namingproblems.html
  1969. * @return string
  1970. */
  1971. protected function _get_hostname()
  1972. {
  1973. if (isset($_SERVER['SERVER_NAME']))
  1974. {
  1975. return $_SERVER['SERVER_NAME'];
  1976. }
  1977. return isset($_SERVER['SERVER_ADDR']) ? '['.$_SERVER['SERVER_ADDR'].']' : '[127.0.0.1]';
  1978. }
  1979. // --------------------------------------------------------------------
  1980. /**
  1981. * Get Debug Message
  1982. *
  1983. * @param array $include List of raw data chunks to include in the output
  1984. * Valid options are: 'headers', 'subject', 'body'
  1985. * @return string
  1986. */
  1987. public function print_debugger($include = array('headers', 'subject', 'body'))
  1988. {
  1989. $msg = '';
  1990. if (count($this->_debug_msg) > 0)
  1991. {
  1992. foreach ($this->_debug_msg as $val)
  1993. {
  1994. $msg .= $val;
  1995. }
  1996. }
  1997. // Determine which parts of our raw data needs to be printed
  1998. $raw_data = '';
  1999. is_array($include) OR $include = array($include);
  2000. if (in_array('headers', $include, TRUE))
  2001. {
  2002. $raw_data = htmlspecialchars($this->_header_str)."\n";
  2003. }
  2004. if (in_array('subject', $include, TRUE))
  2005. {
  2006. $raw_data .= htmlspecialchars($this->_subject)."\n";
  2007. }
  2008. if (in_array('body', $include, TRUE))
  2009. {
  2010. $raw_data .= htmlspecialchars($this->_finalbody);
  2011. }
  2012. return $msg.($raw_data === '' ? '' : '<pre>'.$raw_data.'</pre>');
  2013. }
  2014. // --------------------------------------------------------------------
  2015. /**
  2016. * Set Message
  2017. *
  2018. * @param string $msg
  2019. * @param string $val = ''
  2020. * @return void
  2021. */
  2022. protected function _set_error_message($msg, $val = '')
  2023. {
  2024. $CI =& get_instance();
  2025. $CI->lang->load('email');
  2026. if (sscanf($msg, 'lang:%s', $line) !== 1 OR FALSE === ($line = $CI->lang->line($line)))
  2027. {
  2028. $this->_debug_msg[] = str_replace('%s', $val, $msg).'<br />';
  2029. }
  2030. else
  2031. {
  2032. $this->_debug_msg[] = str_replace('%s', $val, $line).'<br />';
  2033. }
  2034. }
  2035. // --------------------------------------------------------------------
  2036. /**
  2037. * Mime Types
  2038. *
  2039. * @param string
  2040. * @return string
  2041. */
  2042. protected function _mime_types($ext = '')
  2043. {
  2044. $ext = strtolower($ext);
  2045. $mimes =& get_mimes();
  2046. if (isset($mimes[$ext]))
  2047. {
  2048. return is_array($mimes[$ext])
  2049. ? current($mimes[$ext])
  2050. : $mimes[$ext];
  2051. }
  2052. return 'application/x-unknown-content-type';
  2053. }
  2054. // --------------------------------------------------------------------
  2055. /**
  2056. * Destructor
  2057. *
  2058. * @return void
  2059. */
  2060. public function __destruct()
  2061. {
  2062. is_resource($this->_smtp_connect) && $this->_send_command('quit');
  2063. }
  2064. // --------------------------------------------------------------------
  2065. /**
  2066. * Byte-safe strlen()
  2067. *
  2068. * @param string $str
  2069. * @return int
  2070. */
  2071. protected static function strlen($str)
  2072. {
  2073. return (self::$func_overload)
  2074. ? mb_strlen($str, '8bit')
  2075. : strlen($str);
  2076. }
  2077. // --------------------------------------------------------------------
  2078. /**
  2079. * Byte-safe substr()
  2080. *
  2081. * @param string $str
  2082. * @param int $start
  2083. * @param int $length
  2084. * @return string
  2085. */
  2086. protected static function substr($str, $start, $length = NULL)
  2087. {
  2088. if (self::$func_overload)
  2089. {
  2090. // mb_substr($str, $start, null, '8bit') returns an empty
  2091. // string on PHP 5.3
  2092. isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
  2093. return mb_substr($str, $start, $length, '8bit');
  2094. }
  2095. return isset($length)
  2096. ? substr($str, $start, $length)
  2097. : substr($str, $start);
  2098. }
  2099. }