Source for file Message.class.php

Documentation is available at Message.class.php

  1. <?php
  2.  
  3. /**
  4.  * Message.class.php
  5.  *
  6.  * This file contains functions needed to handle mime messages.
  7.  *
  8.  * @copyright 2003-2020 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: Message.class.php 14845 2020-01-07 08:09:34Z pdontthink $
  11.  * @package squirrelmail
  12.  * @subpackage mime
  13.  * @since 1.3.2
  14.  */
  15.  
  16. /**
  17.  * The object that contains a message.
  18.  *
  19.  * message is the object that contains messages. It is a recursive object in
  20.  * that through the $entities variable, it can contain more objects of type
  21.  * message. See documentation in mime.txt for a better description of how this
  22.  * works.
  23.  * @package squirrelmail
  24.  * @subpackage mime
  25.  * @since 1.3.0
  26.  */
  27. class Message {
  28.     /**
  29.      * rfc822header object
  30.      * @var object 
  31.      */
  32.     var $rfc822_header = '';
  33.     /**
  34.      * MessageHeader object
  35.      * @var object 
  36.      */
  37.     var $mime_header = '';
  38.     /**
  39.      * @var mixed 
  40.      */
  41.     var $flags = '';
  42.     /**
  43.      * Media type
  44.      * @var string 
  45.      */
  46.     var $type0='';
  47.     /**
  48.      * Media subtype
  49.      * @var string 
  50.      */
  51.     var $type1='';
  52.     /**
  53.      * Nested mime parts
  54.      * @var array 
  55.      */
  56.     var $entities = array();
  57.     /**
  58.      * Message part id
  59.      * @var string 
  60.      */
  61.     var $entity_id = '';
  62.     /**
  63.      * Parent message part id
  64.      * @var string 
  65.      */
  66.     var $parent_ent;
  67.     /**
  68.      * @var mixed 
  69.      */
  70.     var $entity;
  71.     /**
  72.      * @var mixed 
  73.      */
  74.     var $parent = '';
  75.     /**
  76.      * @var string 
  77.      */
  78.     var $decoded_body='';
  79.     /**
  80.      * Message \seen status
  81.      * @var boolean 
  82.      */
  83.     var $is_seen = 0;
  84.     /**
  85.      * Message \answered status
  86.      * @var boolean 
  87.      */
  88.     var $is_answered = 0;
  89.     /**
  90.      * Message forward status
  91.      * @var boolean 
  92.      */
  93.     var $is_forwarded = 0;
  94.     /**
  95.      * Message \deleted status
  96.      * @var boolean 
  97.      */
  98.     var $is_deleted = 0;
  99.     /**
  100.      * Message \flagged status
  101.      * @var boolean 
  102.      */
  103.     var $is_flagged = 0;
  104.     /**
  105.      * Message mdn status
  106.      * @var boolean 
  107.      */
  108.     var $is_mdnsent = 0;
  109.     /**
  110.      * Message text body
  111.      * @var string 
  112.      */
  113.     var $body_part = '';
  114.     /**
  115.      * Message part offset
  116.      * for fetching body parts out of raw messages
  117.      * @var integer 
  118.      */
  119.     var $offset = 0;
  120.     /**
  121.      * Message part length
  122.      * for fetching body parts out of raw messages
  123.      * @var integer 
  124.      */
  125.     var $length = 0;
  126.     /**
  127.      * Local attachment filename location where the tempory attachment is
  128.      * stored. For use in delivery class.
  129.      * @var string 
  130.      */
  131.     var $att_local_name = '';
  132.  
  133.     /**
  134.      * @param string $ent entity id
  135.      */
  136.     function setEnt($ent) {
  137.         $this->entity_id= $ent;
  138.     }
  139.  
  140.     /**
  141.      * Add nested message part
  142.      * @param object $msg 
  143.      */
  144.     function addEntity ($msg) {
  145.         $this->entities[] = $msg;
  146.     }
  147.  
  148.     /**
  149.      * Get file name used for mime part
  150.      * @return string file name
  151.      * @since 1.3.2
  152.      */
  153.     function getFilename() {
  154.          $filename = '';
  155.          $header = $this->header;
  156.          if (is_object($header->disposition)) {
  157.               $filename = $header->disposition->getProperty('filename');
  158.               if (trim($filename) == '') {
  159.                   $name = decodeHeader($header->disposition->getProperty('name'));
  160.                   if (!trim($name)) {
  161.                       $name = $header->getParameter('name');
  162.                       if(!trim($name)) {
  163.                           if (!trim( $header->id )) {
  164.                               $filename = 'untitled-[' . $this->entity_id . ']' . '.' . strtolower($header->type1);
  165.                           } else {
  166.                               $filename = 'cid: ' . $header->id . '.' . strtolower($header->type1);
  167.                           }
  168.                       } else {
  169.                           $filename = $name;
  170.                       }
  171.                   } else {
  172.                       $filename = $name;
  173.                   }
  174.               }
  175.          } else {
  176.               $filename = $header->getParameter('filename');
  177.               if (!trim($filename)) {
  178.                   $filename = $header->getParameter('name');
  179.                   if (!trim($filename)) {
  180.                       if (!trim( $header->id )) {
  181.                           $filename = 'untitled-[' . $this->entity_id . ']' . '.' . strtolower($header->type1);
  182.                       } else {
  183.                           $filename = 'cid: ' . $header->id . '.' . strtolower($header->type1);
  184.                       }
  185.                   }
  186.               }
  187.          }
  188.          return $filename;
  189.     }
  190.  
  191.     /**
  192.      * Add header object to message object.
  193.      * WARNING: Unfinished code. Don't expect it to work in older sm versions.
  194.      * @param mixed $read array or string with message headers
  195.      * @todo FIXME: rfc822header->parseHeader() does not return rfc822header object
  196.      */
  197.     function addRFC822Header($read) {
  198.         $header = new Rfc822Header();
  199.         $this->rfc822_header = $header->parseHeader($read);
  200.     }
  201.  
  202.     /**
  203.      * @param string $ent 
  204.      * @return mixed (object or string?)
  205.      */
  206.     function getEntity($ent) {
  207.         $cur_ent = $this->entity_id;
  208.         $msg = $this;
  209.         if (($cur_ent == '') || ($cur_ent == '0')) {
  210.             $cur_ent_a = array();
  211.         } else {
  212.             $cur_ent_a = explode('.', $this->entity_id);
  213.         }
  214.         $ent_a = explode('.', $ent);
  215.  
  216.         for ($i = 0,$entCount = count($ent_a) - 1; $i < $entCount; ++$i) {
  217.             if (isset($cur_ent_a[$i]) && ($cur_ent_a[$i] != $ent_a[$i])) {
  218.                 $msg = $msg->parent;
  219.                 $cur_ent_a = explode('.', $msg->entity_id);
  220.                 --$i;
  221.             } else if (!isset($cur_ent_a[$i])) {
  222.                 if (isset($msg->entities[($ent_a[$i]-1)])) {
  223.                     $msg = $msg->entities[($ent_a[$i]-1)];
  224.                 } else {
  225.                     $msg = $msg->entities[0];
  226.                 }
  227.             }
  228.             if (($msg->type0 == 'message') && ($msg->type1 == 'rfc822')) {
  229.                 /*this is a header for a message/rfc822 entity */
  230.                 $msg = $msg->entities[0];
  231.             }
  232.         }
  233.  
  234.         if (($msg->type0 == 'message') && ($msg->type1 == 'rfc822')) {
  235.             /*this is a header for a message/rfc822 entity */
  236.             $msg = $msg->entities[0];
  237.         }
  238.  
  239.         if (isset($msg->entities[($ent_a[$entCount])-1])) {
  240.             if (is_object($msg->entities[($ent_a[$entCount])-1])) {
  241.                 $msg = $msg->entities[($ent_a[$entCount]-1)];
  242.             }
  243.         }
  244.  
  245.         return $msg;
  246.     }
  247.  
  248.     /**
  249.      * Set message body
  250.      * @param string $s message body
  251.      */
  252.     function setBody($s) {
  253.         $this->body_part = $s;
  254.     }
  255.  
  256.     /**
  257.      * Clean message object
  258.      */
  259.     function clean_up() {
  260.         $msg = $this;
  261.         $msg->body_part = '';
  262.  
  263.         foreach ($msg->entities as $m) {
  264.             $m->clean_up();
  265.         }
  266.     }
  267.  
  268.     /**
  269.      * @return string 
  270.      */
  271.     function getMailbox() {
  272.         $msg = $this;
  273.         while (is_object($msg->parent)) {
  274.             $msg = $msg->parent;
  275.         }
  276.         return $msg->mailbox;
  277.     }
  278.  
  279.     /*
  280.      * Bodystructure parser, a recursive function for generating the
  281.      * entity-tree with all the mime-parts.
  282.      *
  283.      * It follows RFC2060 and stores all the described fields in the
  284.      * message object.
  285.      *
  286.      * Question/Bugs:
  287.      *
  288.      * Ask for me (Marc Groot Koerkamp, [email protected])
  289.      * @param string $read
  290.      * @param integer $i
  291.      * @param mixed $sub_msg
  292.      * @return object Message object
  293.      * @todo define argument and return types
  294.      */
  295.     static function parseStructure($read, &$i, $sub_msg = '') {
  296.         $msg = Message::parseBodyStructure($read, $i, $sub_msg);
  297.         if($msg) $msg->setEntIds($msg,false,0);
  298.         return $msg;
  299.     }
  300.  
  301.     /**
  302.      * @param object $msg 
  303.      * @param mixed $init 
  304.      * @param integer $i 
  305.      * @todo document me
  306.      * @since 1.4.0
  307.      */
  308.     function setEntIds(&$msg,$init=false,$i=0) {
  309.         $iCnt = count($msg->entities);
  310.         if ($init !==false) {
  311.             $iEntSub = $i+1;
  312.             if ($msg->parent->type0 == 'message' &&
  313.                 $msg->parent->type1 == 'rfc822' &&
  314.                 $msg->type0 == 'multipart') {
  315.                 $iEntSub = '0';
  316.             }
  317.             if ($init) {
  318.                 $msg->entity_id = "$init.$iEntSub";
  319.             } else {
  320.                 $msg->entity_id = $iEntSub;
  321.             }
  322.         } else if ($iCnt) {
  323.             $msg->entity_id='0';
  324.         } else {
  325.             $msg->entity_id='1';
  326.         }
  327.         for ($i=0;$i<$iCnt;++$i) {
  328.             $msg->entities[$i]->parent =& $msg;
  329.             if (strrchr($msg->entity_id, '.') != '.0') {
  330.                 $msg->entities[$i]->setEntIds($msg->entities[$i],$msg->entity_id,$i);
  331.             } else {
  332.                 $msg->entities[$i]->setEntIds($msg->entities[$i],$msg->parent->entity_id,$i);
  333.             }
  334.         }
  335.     }
  336.  
  337.     /**
  338.      * @param string $read 
  339.      * @param integer $i 
  340.      * @param mixed $sub_msg 
  341.      * @return object Message object
  342.      * @todo document me
  343.      * @since 1.4.0 (code was part of parseStructure() in 1.3.x)
  344.      */
  345.     static function parseBodyStructure($read, &$i, $sub_msg = '') {
  346.         $arg_no = 0;
  347.         $arg_a  = array();
  348.         if ($sub_msg) {
  349.             $message = $sub_msg;
  350.         } else {
  351.             $message = new Message();
  352.         }
  353.  
  354.         for ($cnt = strlen($read); $i < $cnt; ++$i) {
  355.             $char = strtoupper($read{$i});
  356.             switch ($char) {
  357.                 case '(':
  358.                     switch($arg_no) {
  359.                         case 0:
  360.                             if (!isset($msg)) {
  361.                                 $msg = new Message();
  362.                                 $hdr = new MessageHeader();
  363.                                 $hdr->type0 = 'text';
  364.                                 $hdr->type1 = 'plain';
  365.                                 $hdr->encoding = '7bit';
  366.                                 $msg->header = $hdr;
  367.                             } else {
  368.                                 $msg->header->type0 = 'multipart';
  369.                                 $msg->type0 = 'multipart';
  370.                                 while ($read{$i} == '(') {
  371.                                     $msg->addEntity($msg->parseBodyStructure($read, $i, $msg));
  372.                                 }
  373.                             }
  374.                             break;
  375.                         case 1:
  376.                             /* multipart properties */
  377.                             ++$i;
  378.                             $arg_a[] = $msg->parseProperties($read, $i);
  379.                             ++$arg_no;
  380.                             break;
  381.                         case 2:
  382.                             if (isset($msg->type0) && ($msg->type0 == 'multipart')) {
  383.                                 ++$i;
  384.                                 $arg_a[] = $msg->parseDisposition($read, $i);
  385.                             } else { /* properties */
  386.                                 $arg_a[] = $msg->parseProperties($read, $i);
  387.                             }
  388.                             ++$arg_no;
  389.                             break;
  390.                         case 3:
  391.                             if (isset($msg->type0) && ($msg->type0 == 'multipart')) {
  392.                                 ++$i;
  393.                                 $arg_a[]= $msg->parseLanguage($read, $i);
  394.                             }
  395.                         case 7:
  396.                             if (($arg_a[0] == 'message') && ($arg_a[1] == 'rfc822')) {
  397.                                 $msg->header->type0 = $arg_a[0];
  398.                                 $msg->header->type1 = $arg_a[1];
  399.                                 $msg->type0 = $arg_a[0];
  400.                                 $msg->type1 = $arg_a[1];
  401.                                 $rfc822_hdr = new Rfc822Header();
  402.                                 $msg->rfc822_header = $msg->parseEnvelope($read, $i, $rfc822_hdr);
  403.                                 while (($i < $cnt) && ($read{$i} != '(')) {
  404.                                     ++$i;
  405.                                 }
  406.                                 $msg->addEntity($msg->parseBodyStructure($read, $i,$msg));
  407.                             }
  408.                             break;
  409.                         case 8:
  410.                             ++$i;
  411.                             $arg_a[] = $msg->parseDisposition($read, $i);
  412.                             ++$arg_no;
  413.                             break;
  414.                         case 9:
  415.                             ++$i;
  416.                             if (($arg_a[0] == 'text') || (($arg_a[0] == 'message') && ($arg_a[1] == 'rfc822'))) {
  417.                                 $arg_a[] = $msg->parseDisposition($read, $i);
  418.                             } else {
  419.                                 $arg_a[] = $msg->parseLanguage($read, $i);
  420.                             }
  421.                             ++$arg_no;
  422.                             break;
  423.                        case 10:
  424.                            if (($arg_a[0] == 'text') || (($arg_a[0] == 'message') && ($arg_a[1] == 'rfc822'))) {
  425.                                ++$i;
  426.                                $arg_a[] = $msg->parseLanguage($read, $i);
  427.                            } else {
  428.                                $i = $msg->parseParenthesis($read, $i);
  429.                                $arg_a[] = ''; /* not yet described in rfc2060 */
  430.                            }
  431.                            ++$arg_no;
  432.                            break;
  433.                        default:
  434.                            /* unknown argument, skip this part */
  435.                            $i = $msg->parseParenthesis($read, $i);
  436.                            $arg_a[] = '';
  437.                            ++$arg_no;
  438.                            break;
  439.                    } /* switch */
  440.                    break;
  441.                 case '"':
  442.                     /* inside an entity -> start processing */
  443.                     $arg_s = $msg->parseQuote($read, $i);
  444.                     ++$arg_no;
  445.                     if ($arg_no < 3) {
  446.                         $arg_s = strtolower($arg_s); /* type0 and type1 */
  447.                     }
  448.                     $arg_a[] = $arg_s;
  449.                     break;
  450.                 case 'n':
  451.                 case 'N':
  452.                     /* probably NIL argument */
  453.                     $tmpnil = strtoupper(substr($read, $i, 4));
  454.                     if ($tmpnil == 'NIL ' || $tmpnil == 'NIL)') {
  455.                         $arg_a[] = '';
  456.                         ++$arg_no;
  457.                         $i += 2;
  458.                     }
  459.                     break;
  460.                 case '{':
  461.                     /* process the literal value */
  462.                     $arg_a[] = $msg->parseLiteral($read, $i);
  463.                     ++$arg_no;
  464.                     break;
  465.                 case '0':
  466.                 case is_numeric($read{$i}):
  467.                     /* process integers */
  468.                     if ($read{$i} == ' ') { break; }
  469.                     ++$arg_no;
  470.                     if (preg_match('/^([0-9]+).*/',substr($read,$i), $regs)) {
  471.                         $i += strlen($regs[1])-1;
  472.                         $arg_a[] = $regs[1];
  473.                     } else {
  474.                         $arg_a[] = 0;
  475.                     }
  476.                     break;
  477.                 case ')':
  478.                     $multipart = (isset($msg->type0) && ($msg->type0 == 'multipart'));
  479.                     if (!$multipart) {
  480.                         $shifted_args = (($arg_a[0] == 'text') || (($arg_a[0] == 'message') && ($arg_a[1] == 'rfc822')));
  481.                         $hdr->type0 = $arg_a[0];
  482.                         $hdr->type1 = $arg_a[1];
  483.  
  484.                         $msg->type0 = $arg_a[0];
  485.                         $msg->type1 = $arg_a[1];
  486.                         $arr = $arg_a[2];
  487.                         if (is_array($arr)) {
  488.                             $hdr->parameters = $arg_a[2];
  489.                         }
  490.                         $hdr->id = str_replace('<', '', str_replace('>', '', $arg_a[3]));
  491.                         $hdr->description = $arg_a[4];
  492.                         $hdr->encoding = strtolower($arg_a[5]);
  493.                         $hdr->entity_id = $msg->entity_id;
  494.                         $hdr->size = $arg_a[6];
  495.                         if ($shifted_args) {
  496.                             $hdr->lines = $arg_a[7];
  497.                             $s = 1;
  498.                         } else {
  499.                             $s = 0;
  500.                         }
  501.                         $hdr->md5 = (isset($arg_a[7+$s]) ? $arg_a[7+$s] : $hdr->md5);
  502.                         $hdr->disposition = (isset($arg_a[8+$s]) ? $arg_a[8+$s] : $hdr->disposition);
  503.                         $hdr->language = (isset($arg_a[9+$s]) ? $arg_a[9+$s] : $hdr->language);
  504.                         $msg->header = $hdr;
  505.                     } else {
  506.                         $hdr->type0 = 'multipart';
  507.                         $hdr->type1 = $arg_a[0];
  508.                         $msg->type0 = 'multipart';
  509.                         $msg->type1 = $arg_a[0];
  510.                         $hdr->parameters = (isset($arg_a[1]) ? $arg_a[1] : $hdr->parameters);
  511.                         $hdr->disposition = (isset($arg_a[2]) ? $arg_a[2] : $hdr->disposition);
  512.                         $hdr->language = (isset($arg_a[3]) ? $arg_a[3] : $hdr->language);
  513.                         $msg->header = $hdr;
  514.                     }
  515.                     return $msg;
  516.                 default: break;
  517.             } /* switch */
  518.         } /* for */
  519.     } /* parsestructure */
  520.  
  521.     /**
  522.      * @param string $read 
  523.      * @param integer $i 
  524.      * @return array 
  525.      */
  526.     function parseProperties($read, &$i) {
  527.         $properties = array();
  528.         $prop_name = '';
  529.  
  530.         for (; $read{$i} != ')'; ++$i) {
  531.             $arg_s = '';
  532.             if ($read{$i} == '"') {
  533.                 $arg_s = $this->parseQuote($read, $i);
  534.             } else if ($read{$i} == '{') {
  535.                 $arg_s = $this->parseLiteral($read, $i);
  536.             }
  537.  
  538.             if ($arg_s != '') {
  539.                 if ($prop_name == '') {
  540.                     $prop_name = strtolower($arg_s);
  541.                     $properties[$prop_name] = '';
  542.                 } else if ($prop_name != '') {
  543.                     $properties[$prop_name] = $arg_s;
  544.                     $prop_name = '';
  545.                 }
  546.             }
  547.         }
  548.         return $this->handleRfc2231($properties);
  549.     }
  550.  
  551.     /**
  552.      * Joins RFC-2231 continuations, converts encoding to RFC-2047 style
  553.      * @param array $properties 
  554.      * @return array 
  555.      */
  556.     function handleRfc2231($properties) {
  557.  
  558.         /* STAGE 1: look for multi-line parameters, convert to the single line
  559.            form, and normalize values */
  560.  
  561.         $cont = array();
  562.         foreach($properties as $key=>$value) {
  563.             /* Look for parameters followed by "*", a number, and an optional "*"
  564.                at the end. */
  565.             if (preg_match('/^(.*\*)(\d+)(|\*)$/', $key, $matches)) {
  566.                 unset($properties[$key]);
  567.                 $prop_name = $matches[1];
  568.                 if (!isset($cont[$prop_name])) $cont[$prop_name] = array();
  569.  
  570.                 /* An asterisk at the end of parameter name indicates that there
  571.                    may be an encoding information present, and the parameter
  572.                    value is percent-hex encoded. If parameter is not encoded, we
  573.                    encode it to simplify further processing.
  574.                 */
  575.                 if ($matches[3] == '') $value = rawurlencode($value);
  576.                 /* Use the number from parameter name as segment index */
  577.                 $cont[$prop_name][$matches[2]] = $value;
  578.             }
  579.         }
  580.         foreach($cont as $key=>$values) {
  581.             /* Sort segments of multi-line parameters by index number. */
  582.             ksort($values);
  583.             /* Join segments. We can do it safely, because:
  584.                - All segments are encoded.
  585.                - Per RFC-2231, chapter 4.1 notes.
  586.             */
  587.             $value = implode('', $values);
  588.             /* Add language and character set field delimiters if not present,
  589.                as required per RFC-2231, chapter 4.1, note #5. */
  590.             if (strpos($value, "'") === false) $value = "''".$value;
  591.             $properties[$key] = $value;
  592.         }
  593.  
  594.         /* STAGE 2: Convert single line RFC-2231 encoded parameters, and
  595.            previously converted multi-line parameters, to RFC-2047 encoding */
  596.  
  597.         foreach($properties as $key=>$value) {
  598.             if ($idx = strpos($key, '*')) {
  599.                 unset($properties[$key]);
  600.                 /* Extract the charset & language. */
  601.                 $charset = substr($value,0,strpos($value,"'"));
  602.                 $value = substr($value,strlen($charset)+1);
  603.                 $language = substr($value,0,strpos($value,"'"));
  604.                 $value = substr($value,strlen($language)+1);
  605.                 /* No character set defaults to US-ASCII */
  606.                 if (!$charset) $charset = 'US-ASCII';
  607.                 if ($language) $language = '*'.$language;
  608.                 /* Convert to RFC-2047 base64 encoded string. */
  609.                 $properties[substr($key, 0, $idx)] = '=?'.$charset.$language.'?B?'.base64_encode(rawurldecode($value)).'?=';
  610.             }
  611.         }
  612.         return $properties;
  613.     }
  614.  
  615.     /**
  616.      * @param string $read 
  617.      * @param integer $i 
  618.      * @param object $hdr MessageHeader object
  619.      * @return object MessageHeader object
  620.      */
  621.     function parseEnvelope($read, &$i, $hdr) {
  622.         $arg_no = 0;
  623.         $arg_a = array();
  624.         ++$i;
  625.         for ($cnt = strlen($read); ($i < $cnt) && ($read{$i} != ')'); ++$i) {
  626.             $char = strtoupper($read{$i});
  627.             switch ($char) {
  628.                 case '"':
  629.                     $arg_a[] = $this->parseQuote($read, $i);
  630.                     ++$arg_no;
  631.                     break;
  632.                 case '{':
  633.                     $arg_a[] = $this->parseLiteral($read, $i);
  634.             /* temp bugfix (SM 1.5 will have a working clean version)
  635.                too much work to implement that version right now */
  636. //            --$i;
  637.                     ++$arg_no;
  638.                     break;
  639.                 case 'N':
  640.                     /* probably NIL argument */
  641.                     if (strtoupper(substr($read, $i, 3)) == 'NIL') {
  642.                         $arg_a[] = '';
  643.                         ++$arg_no;
  644.                         $i += 2;
  645.                     }
  646.                     break;
  647.                 case '(':
  648.                     /* Address structure (with group support)
  649.                      * Note: Group support is useless on SMTP connections
  650.                      *       because the protocol doesn't support it
  651.                      */
  652.                     $addr_a = array();
  653.                     $group = '';
  654.                     $a=0;
  655.                     for (; $i < $cnt && $read{$i} != ')'; ++$i) {
  656.                         if ($read{$i} == '(') {
  657.                             $addr = $this->parseAddress($read, $i);
  658.                             if (($addr->host == '') && ($addr->mailbox != '')) {
  659.                                 /* start of group */
  660.                                 $group = $addr->mailbox;
  661.                                 $group_addr = $addr;
  662.                                 $j = $a;
  663.                             } else if ($group && ($addr->host == '') && ($addr->mailbox == '')) {
  664.                                /* end group */
  665.                                 if ($a == ($j+1)) { /* no group members */
  666.                                     $group_addr->group = $group;
  667.                                     $group_addr->mailbox = '';
  668.                                     $group_addr->personal = "$group: Undisclosed recipients;";
  669.                                     $addr_a[] = $group_addr;
  670.                                     $group ='';
  671.                                 }
  672.                             } else {
  673.                                 $addr->group = $group;
  674.                                 $addr_a[] = $addr;
  675.                             }
  676.                             ++$a;
  677.                         }
  678.                     }
  679.                     $arg_a[] = $addr_a;
  680.                     break;
  681.                 default: break;
  682.             }
  683.         }
  684.  
  685.         if (count($arg_a) > 9) {
  686.             $d = strtr($arg_a[0], array('  ' => ' '));
  687.             $d_parts = explode(' ', $d);
  688.             if (!$arg_a[1]) $arg_a[1] = _("(no subject)");
  689.  
  690.             $hdr->date = getTimeStamp($d_parts); /* argument 1: date */
  691.             $hdr->date_unparsed = strtr($d,'<>','  '); /* original date */
  692.             $hdr->subject = $arg_a[1];     /* argument 2: subject */
  693.             $hdr->from = is_array($arg_a[2]) ? $arg_a[2][0] : '';     /* argument 3: from        */
  694.             $hdr->sender = is_array($arg_a[3]) ? $arg_a[3][0] : '';   /* argument 4: sender      */
  695.             $hdr->reply_to = is_array($arg_a[4]) ? $arg_a[4][0] : '';  /* argument 5: reply-to    */
  696.             $hdr->to = $arg_a[5];          /* argument 6: to          */
  697.             $hdr->cc = $arg_a[6];          /* argument 7: cc          */
  698.             $hdr->bcc = $arg_a[7];         /* argument 8: bcc         */
  699.             $hdr->in_reply_to = $arg_a[8];   /* argument 9: in-reply-to */
  700.             $hdr->message_id = $arg_a[9];  /* argument 10: message-id */
  701.         }
  702.         return $hdr;
  703.     }
  704.  
  705.     /**
  706.      * @param string $read 
  707.      * @param integer $i 
  708.      * @return string 
  709.      * @todo document me
  710.      */
  711.     function parseLiteral($read, &$i) {
  712.         $lit_cnt = '';
  713.         ++$i;
  714.         $iPos = strpos($read,'}',$i);
  715.         if ($iPos) {
  716.             $lit_cnt = substr($read, $i, $iPos - $i);
  717.             $i += strlen($lit_cnt) + 3; /* skip } + \r + \n */
  718.             /* Now read the literal */
  719.             $s = ($lit_cnt ? substr($read,$i,$lit_cnt): '');
  720.             $i += $lit_cnt;
  721.             /* temp bugfix (SM 1.5 will have a working clean version)
  722.                too much work to implement that version right now */
  723.             --$i;
  724.         } else { /* should never happen */
  725.             $i += 3; /* } + \r + \n */
  726.             $s = '';
  727.         }
  728.         return $s;
  729.     }
  730.  
  731.     /**
  732.      * function parseQuote
  733.      *
  734.      * This extract the string value from a quoted string. After the end-quote
  735.      * character is found it returns the string. The offset $i when calling
  736.      * this function points to the first double quote. At the end it points to
  737.      * The ending quote. This function takes care of escaped double quotes.
  738.      * "some \"string\""
  739.      * ^               ^
  740.      * initial $i      end position $i
  741.      *
  742.      * @param string $read 
  743.      * @param integer $i offset in $read
  744.      * @return string string inbetween the double quotes
  745.      * @author Marc Groot Koerkamp
  746.      */
  747.     function parseQuote($read, &$i) {
  748.         $s = '';
  749.         $iPos = ++$i;
  750.         $iPosStart = $iPos;
  751.         while (true) {
  752.             $iPos = strpos($read,'"',$iPos);
  753.             if (!$iPos) break;
  754.             if ($iPos && $read{$iPos -1} != '\\') {
  755.                 $s = substr($read,$i,($iPos-$i));
  756.                 $i = $iPos;
  757.                 break;
  758.             } else if ($iPos > 1 && $read{$iPos -1} == '\\' && $read{$iPos-2} == '\\') {
  759.                 // This is an unique situation where the fast detection of the string
  760.                 // fails. If the quote string ends with \\ then we need to iterate
  761.                 // through the entire string to make sure we detect the unexcaped
  762.                 // double quotes correctly.
  763.                 $s = '';
  764.                 $bEscaped = false;
  765.                 $k = 0;
  766.                  for ($j=$iPosStart,$iCnt=strlen($read);$j<$iCnt;++$j) {
  767.                     $cChar = $read{$j};
  768.                     switch ($cChar) {
  769.                         case '\\':
  770.                            $bEscaped = !$bEscaped;
  771.                             $s .= $cChar;
  772.                             break;
  773.                          case '"':
  774.                             if ($bEscaped) {
  775.                                 $s .= $cChar;
  776.                                 $bEscaped = false;
  777.                             } else {
  778.                                 $i = $j;
  779.                                 break 3;
  780.                             }
  781.                             break;
  782.                          default:
  783.                             if ($bEscaped) {
  784.                                $bEscaped = false;
  785.                             }
  786.                             $s .= $cChar;
  787.                             break;
  788.                     }
  789.                 }
  790.             }
  791.             ++$iPos;
  792.             if ($iPos > strlen($read)) {
  793.                 break;
  794.             }
  795.         }
  796.         return $s;
  797.     }
  798.  
  799.     /**
  800.      * @param string $read 
  801.      * @param integer $i 
  802.      * @return object AddressStructure object
  803.      */
  804.     function parseAddress($read, &$i) {
  805.         $arg_a = array();
  806.         for (; $read{$i} != ')'; ++$i) {
  807.             $char = strtoupper($read{$i});
  808.             switch ($char) {
  809.                 case '"': $arg_a[] = $this->parseQuote($read, $i); break;
  810.                 case '{': $arg_a[] = $this->parseLiteral($read, $i); break;
  811.                 case 'n':
  812.                 case 'N':
  813.                     if (strtoupper(substr($read, $i, 3)) == 'NIL') {
  814.                         $arg_a[] = '';
  815.                         $i += 2;
  816.                     }
  817.                     break;
  818.                 default: break;
  819.             }
  820.         }
  821.  
  822.         if (count($arg_a) == 4) {
  823.             $adr = new AddressStructure();
  824.             $adr->personal = $arg_a[0];
  825.             $adr->adl = $arg_a[1];
  826.             $adr->mailbox = $arg_a[2];
  827.             $adr->host = $arg_a[3];
  828.         } else {
  829.             $adr = '';
  830.         }
  831.         return $adr;
  832.     }
  833.  
  834.     /**
  835.      * @param string $read 
  836.      * @param integer $i 
  837.      * @param object Disposition object or empty string
  838.      */
  839.     function parseDisposition($read, &$i) {
  840.         $arg_a = array();
  841.         for (; $read{$i} != ')'; ++$i) {
  842.             switch ($read{$i}) {
  843.                 case '"': $arg_a[] = $this->parseQuote($read, $i); break;
  844.                 case '{': $arg_a[] = $this->parseLiteral($read, $i); break;
  845.                 case '(': $arg_a[] = $this->parseProperties($read, $i); break;
  846.                 default: break;
  847.             }
  848.         }
  849.  
  850.         if (isset($arg_a[0])) {
  851.             $disp = new Disposition($arg_a[0]);
  852.             if (isset($arg_a[1])) {
  853.                 $disp->properties = $arg_a[1];
  854.             }
  855.         }
  856.         return (is_object($disp) ? $disp : '');
  857.     }
  858.  
  859.     /**
  860.      * @param string $read 
  861.      * @param integer $i 
  862.      * @return object Language object or empty string
  863.      */
  864.     function parseLanguage($read, &$i) {
  865.         /* no idea how to process this one without examples */
  866.         $arg_a = array();
  867.  
  868.         for (; $read{$i} != ')'; ++$i) {
  869.             switch ($read{$i}) {
  870.                 case '"': $arg_a[] = $this->parseQuote($read, $i); break;
  871.                 case '{': $arg_a[] = $this->parseLiteral($read, $i); break;
  872.                 case '(': $arg_a[] = $this->parseProperties($read, $i); break;
  873.                 default: break;
  874.             }
  875.         }
  876.  
  877.         if (isset($arg_a[0])) {
  878.             $lang = new Language($arg_a[0]);
  879.             if (isset($arg_a[1])) {
  880.                 $lang->properties = $arg_a[1];
  881.             }
  882.         }
  883.         return (is_object($lang) ? $lang : '');
  884.     }
  885.  
  886.     /**
  887.      * Parse message text enclosed in parenthesis
  888.      * @param string $read 
  889.      * @param integer $i 
  890.      * @return integer 
  891.      */
  892.     function parseParenthesis($read, $i) {
  893.         for ($i++; $read{$i} != ')'; ++$i) {
  894.             switch ($read{$i}) {
  895.                 case '"': $this->parseQuote($read, $i); break;
  896.                 case '{': $this->parseLiteral($read, $i); break;
  897.                 case '(': $this->parseProperties($read, $i); break;
  898.                 default: break;
  899.             }
  900.         }
  901.         return $i;
  902.     }
  903.  
  904.     /**
  905.      * Function to fill the message structure in case the
  906.      * bodystructure is not available
  907.      * NOT FINISHED YET
  908.      * @param string $read 
  909.      * @param string $type0 message part type
  910.      * @param string $type1 message part subtype
  911.      * @return string (only when type0 is not message or multipart)
  912.      */
  913.     function parseMessage($read, $type0, $type1) {
  914.         switch ($type0) {
  915.             case 'message':
  916.                 $rfc822_header = true;
  917.                 $mime_header = false;
  918.                 break;
  919.             case 'multipart':
  920.                 $rfc822_header = false;
  921.                 $mime_header = true;
  922.                 break;
  923.             default: return $read;
  924.         }
  925.  
  926.         for ($i = 1; $i < $count; ++$i) {
  927.             $line = trim($body[$i]);
  928.             if (($mime_header || $rfc822_header) &&
  929.                 (preg_match("/^.*boundary=\"?(.+(?=\")|.+).*/i", $line, $reg))) {
  930.                 $bnd = $reg[1];
  931.                 $bndreg = $bnd;
  932.                 $bndreg = str_replace("\\", "\\\\", $bndreg);
  933.                 $bndreg = str_replace("?", "\\?", $bndreg);
  934.                 $bndreg = str_replace("+", "\\+", $bndreg);
  935.                 $bndreg = str_replace(".", "\\.", $bndreg);
  936.                 $bndreg = str_replace("/", "\\/", $bndreg);
  937.                 $bndreg = str_replace("-", "\\-", $bndreg);
  938.                 $bndreg = str_replace("(", "\\(", $bndreg);
  939.                 $bndreg = str_replace(")", "\\)", $bndreg);
  940.             } else if ($rfc822_header && $line == '') {
  941.                 $rfc822_header = false;
  942.                 if ($msg->type0 == 'multipart') {
  943.                     $mime_header = true;
  944.                 }
  945.             }
  946.  
  947.             if ((($line{0} == '-') || $rfc822_header)  && isset($boundaries[0])) {
  948.                 $cnt = count($boundaries)-1;
  949.                 $bnd = $boundaries[$cnt]['bnd'];
  950.                 $bndreg = $boundaries[$cnt]['bndreg'];
  951.  
  952.                 $regstr = '/^--'."($bndreg)".".*".'/';
  953.                 if (preg_match($regstr, $line, $reg)) {
  954.                     $bndlen = strlen($reg[1]);
  955.                     $bndend = false;
  956.                     if (strlen($line) > ($bndlen + 3)) {
  957.                         if (($line{$bndlen+2} == '-') && ($line{$bndlen+3} == '-')) {
  958.                             $bndend = true;
  959.                         }
  960.                     }
  961.                     if ($bndend) {
  962.                         /* calc offset and return $msg */
  963.                         //$entStr = CalcEntity("$entStr", -1);
  964.                         array_pop($boundaries);
  965.                         $mime_header = true;
  966.                         $bnd_end = true;
  967.                     } else {
  968.                         $mime_header = true;
  969.                          $bnd_end = false;
  970.                         //$entStr = CalcEntity("$entStr", 0);
  971.                         ++$content_indx;
  972.                     }
  973.                 } else {
  974.                     if ($header) { }
  975.                 }
  976.             }
  977.         }
  978.     }
  979.  
  980.     /**
  981.      * @param array $entity 
  982.      * @param array $alt_order 
  983.      * @param boolean $strict 
  984.      * @return array 
  985.      */
  986.     function findDisplayEntity($entity = array(), $alt_order = array('text/plain', 'text/html'), $strict=false) {
  987.         $found = false;
  988.         if ($this->type0 == 'multipart') {
  989.             if($this->type1 == 'alternative') {
  990.                 $msg = $this->findAlternativeEntity($alt_order);
  991.                 if ( ! is_null($msg) ) {
  992.                     if (count($msg->entities) == 0) {
  993.                         $entity[] = $msg->entity_id;
  994.                     } else {
  995.                         $entity = $msg->findDisplayEntity($entity, $alt_order, $strict);
  996.                     }
  997.                     $found = true;
  998.                 }
  999.             } else if ($this->type1 == 'related') { /* RFC 2387 */
  1000.                 $msgs = $this->findRelatedEntity();
  1001.                 foreach ($msgs as $msg) {
  1002.                     if (count($msg->entities) == 0) {
  1003.                         $entity[] = $msg->entity_id;
  1004.                     } else {
  1005.                         $entity = $msg->findDisplayEntity($entity, $alt_order, $strict);
  1006.                     }
  1007.                 }
  1008.                 if (count($msgs) > 0) {
  1009.                     $found = true;
  1010.                 }
  1011.             } else { /* Treat as multipart/mixed */
  1012.                 foreach ($this->entities as $ent) {
  1013.                     if(!(is_object($ent->header->disposition) && strtolower($ent->header->disposition->name) == 'attachment') &&
  1014.                             (!isset($ent->header->parameters['filename'])) &&
  1015.                             (!isset($ent->header->parameters['name'])) &&
  1016.                             (($ent->type0 != 'message') && ($ent->type1 != 'rfc822'))) {
  1017.                         $entity = $ent->findDisplayEntity($entity, $alt_order, $strict);
  1018.                         $found = true;
  1019.                     }
  1020.                 }
  1021.             }
  1022.         } else { /* If not multipart, then just compare with each entry from $alt_order */
  1023.             $type = $this->type0.'/'.$this->type1;
  1024. //        $alt_order[] = "message/rfc822";
  1025.             foreach ($alt_order as $alt) {
  1026.                 if( ($alt == $type) && isset($this->entity_id) ) {
  1027.                     if ((count($this->entities) == 0) &&
  1028.                             (!isset($this->header->parameters['filename'])) &&
  1029.                             (!isset($this->header->parameters['name'])) &&
  1030.                             isset($this->header->disposition) && is_object($this->header->disposition) &&
  1031.                             !(is_object($this->header->disposition) && strtolower($this->header->disposition->name) == 'attachment')) {
  1032.                         $entity[] = $this->entity_id;
  1033.                         $found = true;
  1034.                     }
  1035.                 }
  1036.             }
  1037.         }
  1038.         if(!$found) {
  1039.             foreach ($this->entities as $ent) {
  1040.                 if(!(is_object($ent->header->disposition) && strtolower($ent->header->disposition->name) == 'attachment') &&
  1041.                    (($ent->type0 != 'message') && ($ent->type1 != 'rfc822'))) {
  1042.                     $entity = $ent->findDisplayEntity($entity, $alt_order, $strict);
  1043.                     $found = true;
  1044.                 }
  1045.             }
  1046.         }
  1047.         if(!$strict && !$found) {
  1048.             if (($this->type0 == 'text') &&
  1049.                 in_array($this->type1, array('plain', 'html', 'message')) &&
  1050.                 isset($this->entity_id)) {
  1051.                 if (count($this->entities) == 0) {
  1052.                     if (!is_object($this->header->disposition) || strtolower($this->header->disposition->name) != 'attachment') {
  1053.                         $entity[] = $this->entity_id;
  1054.                     }
  1055.                 }
  1056.             }
  1057.         }
  1058.         return $entity;
  1059.     }
  1060.  
  1061.     /**
  1062.      * @param array $alt_order 
  1063.      * @return entity 
  1064.      */
  1065.     function findAlternativeEntity($alt_order) {
  1066.         /* If we are dealing with alternative parts then we  */
  1067.         /* choose the best viewable message supported by SM. */
  1068.         $best_view = 0;
  1069.         $entity = null;
  1070.         foreach($this->entities as $ent) {
  1071.             $type = $ent->header->type0 . '/' . $ent->header->type1;
  1072.             if ($type == 'multipart/related') {
  1073.                 $type = $ent->header->getParameter('type');
  1074.                 // Mozilla bug. Mozilla does not provide the parameter type.
  1075.                 if (!$type) $type = 'text/html';
  1076.             }
  1077.             $altCount = count($alt_order);
  1078.             for ($j = $best_view; $j < $altCount; ++$j) {
  1079.                 if (($alt_order[$j] == $type) && ($j >= $best_view)) {
  1080.                     $best_view = $j;
  1081.                     $entity = $ent;
  1082.                 }
  1083.             }
  1084.         }
  1085.         return $entity;
  1086.     }
  1087.  
  1088.     /**
  1089.      * @return array 
  1090.      */
  1091.     function findRelatedEntity() {
  1092.         $msgs = array();
  1093.         $related_type = $this->header->getParameter('type');
  1094.         // Mozilla bug. Mozilla does not provide the parameter type.
  1095.         if (!$related_type) $related_type = 'text/html';
  1096.         $entCount = count($this->entities);
  1097.         for ($i = 0; $i < $entCount; ++$i) {
  1098.             $type = $this->entities[$i]->header->type0.'/'.$this->entities[$i]->header->type1;
  1099.             if ($related_type == $type) {
  1100.                 $msgs[] = $this->entities[$i];
  1101.             }
  1102.         }
  1103.         return $msgs;
  1104.     }
  1105.  
  1106.     /**
  1107.      * @param array $exclude_id 
  1108.      * @param array $result 
  1109.      * @return array 
  1110.      */
  1111.     function getAttachments($exclude_id=array(), $result = array()) {
  1112. /*
  1113.         if (($this->type0 == 'message') &&
  1114.         ($this->type1 == 'rfc822') &&
  1115.         ($this->entity_id) ) {
  1116.             $this = $this->entities[0];
  1117.         }
  1118. */
  1119.         if (count($this->entities)) {
  1120.             foreach ($this->entities as $entity) {
  1121.                 $exclude = false;
  1122.                 foreach ($exclude_id as $excl) {
  1123.                     if ($entity->entity_id === $excl) {
  1124.                         $exclude = true;
  1125.                     }
  1126.                 }
  1127.  
  1128.                 if (!$exclude) {
  1129.                     if ($entity->type0 == 'multipart') {
  1130.                         $result = $entity->getAttachments($exclude_id, $result);
  1131.                     } else if ($entity->type0 != 'multipart') {
  1132.                         $result[] = $entity;
  1133.                     }
  1134.                 }
  1135.             }
  1136.         } else {
  1137.             $exclude = false;
  1138.             foreach ($exclude_id as $excl) {
  1139.                 $exclude = $exclude || ($this->entity_id == $excl);
  1140.             }
  1141.  
  1142.             if (!$exclude) {
  1143.                 $result[] = $this;
  1144.             }
  1145.         }
  1146.         return $result;
  1147.     }
  1148.  
  1149.     /**
  1150.      * Add attachment to message object
  1151.      * @param string $type attachment type
  1152.      * @param string $name attachment name
  1153.      * @param string $location path to attachment
  1154.      */
  1155.     function initAttachment($type, $name, $location) {
  1156.         $attachment = new Message();
  1157.         $mime_header = new MessageHeader();
  1158.         $mime_header->setParameter('name', $name);
  1159.         // FIXME: duplicate code. see ContentType class
  1160.         $pos = strpos($type, '/');
  1161.         if ($pos > 0) {
  1162.             $mime_header->type0 = substr($type, 0, $pos);
  1163.             $mime_header->type1 = substr($type, $pos+1);
  1164.         } else {
  1165.             $mime_header->type0 = $type;
  1166.         }
  1167.         $attachment->att_local_name = $location;
  1168.         $disposition = new Disposition('attachment');
  1169.         $disposition->properties['filename'] = $name;
  1170.         $mime_header->disposition = $disposition;
  1171.         $attachment->mime_header = $mime_header;
  1172.         $this->entities[]=$attachment;
  1173.     }
  1174.  
  1175.     /**
  1176.      * Delete all attachments from this object from disk.
  1177.      * @since 1.5.1
  1178.      */
  1179.     function purgeAttachments() {
  1180.         if ($this->att_local_name) {
  1181.             global $username, $attachment_dir;
  1182.             $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
  1183.             if ( file_exists($hashed_attachment_dir . '/' . $this->att_local_name) ) {
  1184.                 unlink($hashed_attachment_dir . '/' . $this->att_local_name);
  1185.             }
  1186.         }
  1187.         // recursively delete attachments from entities contained in this object
  1188.         for ($i=0, $entCount=count($this->entities);$i< $entCount; ++$i) {
  1189.             $this->entities[$i]->purgeAttachments();
  1190.         }
  1191.     }
  1192. }

Documentation generated on Mon, 13 Jan 2020 04:23:04 +0100 by phpDocumentor 1.4.3