Guest User

Email.php

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