b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_ext/mime_parser/mime_parser.php - 2573 lines - 82145 bytes - Summary - Text - Print

   1  <?php
   2  /*
   3   * mime_parser.php
   4   *
   5   * @(#) $Id: mime_parser.php 1180 2012-04-05 00:48:28Z sam2kb $
   6   *
   7   */
   8  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
   9  
  10  define('MIME_PARSER_START',        1);
  11  define('MIME_PARSER_HEADER',       2);
  12  define('MIME_PARSER_HEADER_VALUE', 3);
  13  define('MIME_PARSER_BODY',         4);
  14  define('MIME_PARSER_BODY_START',   5);
  15  define('MIME_PARSER_BODY_DATA',    6);
  16  define('MIME_PARSER_BODY_DONE',    7);
  17  define('MIME_PARSER_END',          8);
  18  
  19  define('MIME_MESSAGE_START',            1);
  20  define('MIME_MESSAGE_GET_HEADER_NAME',  2);
  21  define('MIME_MESSAGE_GET_HEADER_VALUE', 3);
  22  define('MIME_MESSAGE_GET_BODY',         4);
  23  define('MIME_MESSAGE_GET_BODY_PART',    5);
  24  
  25  define('MIME_ADDRESS_START',            1);
  26  define('MIME_ADDRESS_FIRST',            2);
  27  
  28  /*
  29  {metadocument}<?xml version="1.0" encoding="ISO-8859-1" ?>
  30  <class>
  31  
  32      <package>net.manuellemos.mimeparser</package>
  33  
  34      <version>@(#) $Id: mime_parser.php 1180 2012-04-05 00:48:28Z sam2kb $</version>
  35      <copyright>Copyright  (C) Manuel Lemos 2006 - 2008</copyright>
  36      <title>MIME parser</title>
  37      <author>Manuel Lemos</author>
  38      <authoraddress>mlemos-at-acm.org</authoraddress>
  39  
  40      <documentation>
  41          <idiom>en</idiom>
  42          <purpose>Parse MIME encapsulated e-mail message data compliant with
  43              the RFC 2822 or aggregated in mbox format.</purpose>
  44          <usage>Use the function <functionlink>Decode</functionlink> function
  45              to retrieve the structure of the messages to be parsed. Adjust its
  46              parameters to tell how to return the decoded body data.
  47              Use the <tt>SaveBody</tt> parameter to make the body parts be saved
  48              to files when the message is larger than the available memory. Use
  49              the <tt>SkipBody</tt> parameter to just retrieve the message
  50              structure without returning the body data.<paragraphbreak />
  51              If the message data is an archive that may contain multiple messages
  52              aggregated in the mbox format, set the variable
  53              <variablelink>mbox</variablelink> to <booleanvalue>1</booleanvalue>.</usage>
  54      </documentation>
  55  
  56  {/metadocument}
  57  */
  58  
  59  class mime_parser_class
  60  {
  61  /*
  62  {metadocument}
  63      <variable>
  64          <name>error</name>
  65          <type>STRING</type>
  66          <value></value>
  67          <documentation>
  68              <purpose>Store the message that is returned when an error
  69                  occurs.</purpose>
  70              <usage>Check this variable to understand what happened when a call to
  71                  any of the class functions has failed.<paragraphbreak />
  72                  This class uses cumulative error handling. This means that if one
  73                  class functions that may fail is called and this variable was
  74                  already set to an error message due to a failure in a previous call
  75                  to the same or other function, the function will also fail and does
  76                  not do anything.<paragraphbreak />
  77                  This allows programs using this class to safely call several
  78                  functions that may fail and only check the failure condition after
  79                  the last function call.<paragraphbreak />
  80                  Just set this variable to an empty string to clear the error
  81                  condition.</usage>
  82          </documentation>
  83      </variable>
  84  {/metadocument}
  85  */
  86      var $error='';
  87  
  88  /*
  89  {metadocument}
  90      <variable>
  91          <name>error_position</name>
  92          <type>INTEGER</type>
  93          <value>-1</value>
  94          <documentation>
  95              <purpose>Point to the position of the message data or file that
  96                  refers to the last error that occurred.</purpose>
  97              <usage>Check this variable to determine the relevant position of the
  98                  message when a parsing error occurs.</usage>
  99          </documentation>
 100      </variable>
 101  {/metadocument}
 102  */
 103      var $error_position = -1;
 104  
 105  /*
 106  {metadocument}
 107      <variable>
 108          <name>mbox</name>
 109          <type>BOOLEAN</type>
 110          <value>0</value>
 111          <documentation>
 112              <purpose>Specify whether the message data to parse is a single RFC
 113                  2822 message or it is an archive that contain multiple messages in
 114                  the mbox format.</purpose>
 115              <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
 116                  it is intended to parse an mbox message archive.<br />
 117                  mbox archives may contain multiple messages. Each message starts
 118                  with the header <tt>From</tt>. Since all valid RFC 2822 headers
 119                  must with a colon, the class will fail to parse a mbox archive if
 120                  this variable is set to <booleanvalue>0</booleanvalue>.</usage>
 121          </documentation>
 122      </variable>
 123  {/metadocument}
 124  */
 125      var $mbox = 0;
 126  
 127  /*
 128  {metadocument}
 129      <variable>
 130          <name>decode_headers</name>
 131          <type>BOOLEAN</type>
 132          <value>1</value>
 133          <documentation>
 134              <purpose>Specify whether the message headers should be decoded.</purpose>
 135              <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
 136                  necessary to decode message headers that may have non-ASCII
 137                  characters and use other character set encodings.</usage>
 138          </documentation>
 139      </variable>
 140  {/metadocument}
 141  */
 142      var $decode_headers = 1;
 143  
 144  /*
 145  {metadocument}
 146      <variable>
 147          <name>decode_bodies</name>
 148          <type>BOOLEAN</type>
 149          <value>1</value>
 150          <documentation>
 151              <purpose>Specify whether the message body parts should be decoded.</purpose>
 152              <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
 153                  necessary to parse the message bodies and extract its part
 154                  structure.</usage>
 155          </documentation>
 156      </variable>
 157  {/metadocument}
 158  */
 159      var $decode_bodies = 1;
 160  
 161  /*
 162  {metadocument}
 163      <variable>
 164          <name>extract_addresses</name>
 165          <type>BOOLEAN</type>
 166          <value>1</value>
 167          <documentation>
 168              <purpose>Specify whether the message headers that usually contain
 169                  e-mail addresses should be parsed and the addresses should be
 170                  extracted by the <functionlink>Decode</functionlink> function.</purpose>
 171              <usage>Set this variable to <booleanvalue>1</booleanvalue> if it is
 172                  necessary to extract the e-mail addresses contained in certain
 173                  message headers.<paragraphbreak />
 174                  The headers to be parsed are defined by the
 175                  <variablelink>address_headers</variablelink> variable.<paragraphbreak />
 176                  The parsed addresses are returned by the
 177                  <tt>ExtractedAddresses</tt> entry of the <argumentlink>
 178                      <function>Decode</function>
 179                      <argument>decoded</argument>
 180                  </argumentlink> argument of the
 181                  <functionlink>Decode</functionlink> function.</usage>
 182          </documentation>
 183      </variable>
 184  {/metadocument}
 185  */
 186      var $extract_addresses = 1;
 187  
 188  /*
 189  {metadocument}
 190      <variable>
 191          <name>address_headers</name>
 192          <type>HASH</type>
 193          <value></value>
 194          <documentation>
 195              <purpose>Specify which headers contain addresses that should be
 196                  parsed and extracted.</purpose>
 197              <usage>Change this variable if you need to extract e-mail addresses
 198                  from a different list of message headers.<paragraphbreak />
 199                  It must be set to an associative array with keys set to the names
 200                  of the headers to be parsed including the colon. The array values
 201                  must be set to a boolean flag to tell whether the headers with the
 202                  respective name should be parsed. The header names must be in lower
 203                  case.<paragraphbreak />
 204                  By default the class addresses from the headers:
 205                  <stringvalue>from:</stringvalue>, <stringvalue>to:</stringvalue>,
 206                  <stringvalue>cc:</stringvalue>, <stringvalue>bcc:</stringvalue>,
 207                  <stringvalue>return-path:</stringvalue>,
 208                  <stringvalue>reply-to:</stringvalue> and
 209                  <stringvalue>disposition-notification-to:</stringvalue>.</usage>
 210          </documentation>
 211      </variable>
 212  {/metadocument}
 213  */
 214      var $address_headers = array(
 215          'from:' => 1,
 216          'to:' => 1,
 217          'cc:' => 1,
 218          'bcc:' => 1,
 219          'return-path:'=>1,
 220          'reply-to:'=>1,
 221          'disposition-notification-to:'=>1
 222      );
 223  
 224  /*
 225  {metadocument}
 226      <variable>
 227          <name>message_buffer_length</name>
 228          <type>INTEGER</type>
 229          <value>8000</value>
 230          <documentation>
 231              <purpose>Maximum length of the chunks of message data that the class
 232                  parse at one time.</purpose>
 233              <usage>Adjust this value according to the available memory.</usage>
 234          </documentation>
 235      </variable>
 236  {/metadocument}
 237  */
 238      var $message_buffer_length = 8000;
 239  
 240  /*
 241  {metadocument}
 242      <variable>
 243          <name>ignore_syntax_errors</name>
 244          <type>BOOLEAN</type>
 245          <value>1</value>
 246          <documentation>
 247              <purpose>Specify whether the class should ignore syntax errors in
 248                  malformed messages.</purpose>
 249              <usage>Set this variable to <booleanvalue>0</booleanvalue> if it is
 250                  necessary to verify whether message data may be corrupted due to
 251                  to eventual bugs in the program that generated the
 252                  message.<paragraphbreak />
 253                  Currently the class only ignores some types of syntax errors.
 254                  Other syntax errors may still cause the
 255                  <functionlink>Decode</functionlink> to fail.</usage>
 256          </documentation>
 257      </variable>
 258  {/metadocument}
 259  */
 260      var $ignore_syntax_errors=1;
 261  
 262  /*
 263  {metadocument}
 264      <variable>
 265          <name>warnings</name>
 266          <type>HASH</type>
 267          <value></value>
 268          <documentation>
 269              <purpose>Return a list of positions of the original message that
 270                  contain syntax errors.</purpose>
 271              <usage>Check this variable to retrieve eventual message syntax
 272                  errors that were ignored when the
 273                  <variablelink>ignore_syntax_errors</variablelink> is set to
 274                  <booleanvalue>1</booleanvalue>.<paragraphbreak />
 275                  The indexes of this array are the positions of the errors. The
 276                  array values are the corresponding syntax error messages.</usage>
 277          </documentation>
 278      </variable>
 279  {/metadocument}
 280  */
 281      var $warnings=array();
 282  
 283  /*
 284  {metadocument}
 285      <variable>
 286          <name>track_lines</name>
 287          <type>BOOLEAN</type>
 288          <value>0</value>
 289          <documentation>
 290              <purpose>Tell the class to keep track the position of each message
 291                  line.</purpose>
 292              <usage>Set this variable to <integervalue>1</integervalue> if you
 293                  need to determine the line and column number associated to a given
 294                  position of the parsed message.</usage>
 295          </documentation>
 296      </variable>
 297  {/metadocument}
 298  */
 299      var $track_lines = 0;
 300  
 301  /*
 302  {metadocument}
 303      <variable>
 304          <name>use_part_file_names</name>
 305          <type>BOOLEAN</type>
 306          <value>0</value>
 307          <documentation>
 308              <purpose>Tell the class to try to use the original message
 309                  attachment file names when saving body parts to files.</purpose>
 310              <usage>Set this variable to <integervalue>1</integervalue> if you
 311                  would like to have attachments be saved to file with the names
 312                  that were used when the message was sent.</usage>
 313          </documentation>
 314      </variable>
 315  {/metadocument}
 316  */
 317      var $use_part_file_names = 0;
 318  
 319      /* Private variables */
 320      var $state = MIME_PARSER_START;
 321      var $buffer = '';
 322      var $buffer_position = 0;
 323      var $offset = 0;
 324      var $parts = array();
 325      var $part_position = 0;
 326      var $headers = array();
 327      var $body_parser;
 328      var $body_parser_state = MIME_PARSER_BODY_DONE;
 329      var $body_buffer = '';
 330      var $body_buffer_position = 0;
 331      var $body_offset = 0;
 332      var $current_header = '';
 333      var $file;
 334      var $body_file;
 335      var $position = 0;
 336      var $body_part_number = 1;
 337      var $next_token = '';
 338      var $lines = array();
 339      var $line_offset = 0;
 340      var $last_line = 1;
 341      var $last_carriage_return = 0;
 342      var $header_name_characters = '!"#$%&\'()*+,-./0123456789;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}';
 343      var $message_position = 0;
 344  
 345      /* Private functions */
 346  
 347      Function SetError($error)
 348      {
 349          $this->error = $error;
 350          return(0);
 351      }
 352  
 353      Function SetErrorWithContact($error)
 354      {
 355          return($this->SetError($error.'. Please contact the author Manuel Lemos <mlemos@acm.org> and send a copy of this message to let him add support for this kind of messages'));
 356      }
 357  
 358      Function SetPositionedError($error, $position)
 359      {
 360          $this->error_position = $position;
 361          return($this->SetError($error));
 362      }
 363  
 364      Function SetPositionedWarning($error, $position)
 365      {
 366          if(!$this->ignore_syntax_errors)
 367              return($this->SetPositionedError($error, $position));
 368          $this->warnings[$position]=$error;
 369          return(1);
 370      }
 371  
 372      Function SetPHPError($error, &$php_error_message)
 373      {
 374          if(IsSet($php_error_message)
 375          && strlen($php_error_message))
 376              $error .= ': '.$php_error_message;
 377          return($this->SetError($error));
 378      }
 379  
 380      Function ResetParserState()
 381      {
 382          $this->error='';
 383          $this->error_position = -1;
 384          $this->state = MIME_PARSER_START;
 385          $this->buffer = '';
 386          $this->buffer_position = 0;
 387          $this->offset = 0;
 388          $this->parts = array();
 389          $this->part_position = 0;
 390          $this->headers = array();
 391          $this->body_parser_state = MIME_PARSER_BODY_DONE;
 392          $this->body_buffer = '';
 393          $this->body_buffer_position = 0;
 394          $this->body_offset = 0;
 395          $this->current_header = '';
 396          $this->position = 0;
 397          $this->body_part_number = 1;
 398          $this->next_token = '';
 399          $this->lines = ($this->track_lines ? array(0 => 0) : array());
 400          $this->line_offset = 0;
 401          $this->last_line = 0;
 402          $this->last_carriage_return = 0;
 403      }
 404  
 405      Function Tokenize($string,$separator="")
 406      {
 407          if(!strcmp($separator,""))
 408          {
 409              $separator=$string;
 410              $string=$this->next_token;
 411          }
 412          for($character=0;$character<strlen($separator);$character++)
 413          {
 414              if(GetType($position=strpos($string,$separator[$character]))=='integer')
 415                  $found=(IsSet($found) ? min($found,$position) : $position);
 416          }
 417          if(IsSet($found))
 418          {
 419              $this->next_token=substr($string,$found+1);
 420              return(substr($string,0,$found));
 421          }
 422          else
 423          {
 424              $this->next_token='';
 425              return($string);
 426          }
 427      }
 428  
 429      Function ParseStructuredHeader($value, &$type, &$parameters, &$character_sets, &$languages)
 430      {
 431          $type = strtolower(trim($this->Tokenize($value, ';')));
 432          $p = trim($this->Tokenize(''));
 433          $parameters = $character_sets = $languages = array();
 434          while(strlen($p))
 435          {
 436              $parameter = trim(strtolower($this->Tokenize($p, '=')));
 437              $remaining = trim($this->Tokenize(''));
 438              if(strlen($remaining)
 439              && !strcmp($remaining[0], '"')
 440              && (GetType($quote = strpos($remaining, '"', 1)) == 'integer'))
 441              {
 442                  $value = substr($remaining, 1, $quote - 1);
 443                  $p = trim(substr($remaining, $quote + 1));
 444                  if(strlen($p) > 0
 445                  && !strcmp($p[0], ';'))
 446                      $p = substr($p, 1);
 447              }
 448              else
 449              {
 450                  $value = trim($this->Tokenize($remaining, ';'));
 451                  $p = trim($this->Tokenize(''));
 452              }
 453              if(($l=strlen($parameter))
 454              && !strcmp($parameter[$l - 1],'*'))
 455              {
 456                  $parameter=$this->Tokenize($parameter, '*');
 457                  if(IsSet($parameters[$parameter])
 458                  && IsSet($character_sets[$parameter]))
 459                      $value = $parameters[$parameter] . UrlDecode($value);
 460                  else
 461                  {
 462                      $character_sets[$parameter] = strtolower($this->Tokenize($value, '\''));
 463                      $languages[$parameter] = $this->Tokenize('\'');
 464                      $value = UrlDecode($this->Tokenize(''));
 465                  }
 466              }
 467              $parameters[$parameter] = $value;
 468          }
 469      }
 470  
 471      Function FindStringLineBreak($string, $position, &$break, &$line_break)
 472      {
 473          if(GetType($line_break=strpos($string, $break="\r", $position))=='integer')
 474          {
 475              if(GetType($new_line_break=strpos($string, "\n", $position))=='integer')
 476              {
 477                  if($new_line_break < $line_break)
 478                  {
 479                      $break = "\n";
 480                      $line_break = $new_line_break;
 481                      return(1);
 482                  }
 483              }
 484              if($line_break>$position
 485              && $string[$line_break-1]=="\r")
 486              {
 487                  $line_break--;
 488                  $break="\r\n";
 489              }
 490              return(1);
 491          }
 492          return(GetType($line_break=strpos($string, $break="\n", $position))=='integer');
 493      }
 494  
 495      Function FindLineBreak($position, &$break, &$line_break)
 496      {
 497          if(GetType($line_break=strpos($this->buffer, $break="\r", $position))=='integer')
 498          {
 499              if(GetType($new_line_break=strpos($this->buffer, "\n", $position))=='integer')
 500              {
 501                  if($new_line_break < $line_break)
 502                  {
 503                      $break = "\n";
 504                      $line_break = $new_line_break;
 505                      return(1);
 506                  }
 507              }
 508              if(($n = $line_break + 1) < strlen($this->buffer)
 509              && $this->buffer[$n]=="\n")
 510                  $break="\r\n";
 511              return(1);
 512          }
 513          return(GetType($line_break=strpos($this->buffer, $break="\n", $position))=='integer');
 514      }
 515  
 516      Function FindBodyLineBreak($position, &$break, &$line_break)
 517      {
 518          if(GetType($line_break=strpos($this->body_buffer, $break="\r", $position))=='integer')
 519          {
 520              if(GetType($new_line_break=strpos($this->body_buffer, "\n", $position))=='integer')
 521              {
 522                  if($new_line_break < $line_break)
 523                  {
 524                      $break = "\n";
 525                      $line_break = $new_line_break;
 526                      return(1);
 527                  }
 528              }
 529              if(($n = $line_break + 1) < strlen($this->body_buffer)
 530              && $this->body_buffer[$n]=="\n")
 531                  $break="\r\n";
 532              return(1);
 533          }
 534          return(GetType($line_break=strpos($this->body_buffer, $break="\n", $position))=='integer');
 535      }
 536  
 537      Function ParseHeaderString($body, &$position, &$headers)
 538      {
 539          $l = strlen($body);
 540          $headers = array();
 541          for(;$position < $l;)
 542          {
 543              if($this->FindStringLineBreak($body, $position, $break, $line_break))
 544              {
 545                  $line = substr($body, $position, $line_break - $position);
 546                  $position = $line_break + strlen($break);
 547              }
 548              else
 549              {
 550                  $line = substr($body, $position);
 551                  $position = $l;
 552              }
 553              if(strlen($line)==0)
 554                  break;
 555              $h = strtolower(strtok($line,':'));
 556              $headers[$h] = trim(strtok(''));
 557          }
 558      }
 559  
 560      Function ParsePart($end, &$part, &$need_more_data)
 561      {
 562          $need_more_data = 0;
 563          switch($this->state)
 564          {
 565              case MIME_PARSER_START:
 566                  $part=array(
 567                      'Type'=>'MessageStart',
 568                      'Position'=>$this->offset + $this->buffer_position
 569                  );
 570                  $this->state = MIME_PARSER_HEADER;
 571                  break;
 572              case MIME_PARSER_HEADER:
 573                  if($this->FindLineBreak($this->buffer_position, $break, $line_break))
 574                  {
 575                      $next = $line_break + strlen($break);
 576                      if(!strcmp($break,"\r")
 577                      && strlen($this->buffer) == $next
 578                      && !$end)
 579                      {
 580                          $need_more_data = 1;
 581                          break;
 582                      }
 583                      if($line_break==$this->buffer_position)
 584                      {
 585                          $part=array(
 586                              'Type'=>'BodyStart',
 587                              'Position'=>$this->offset + $this->buffer_position
 588                          );
 589                          $this->buffer_position = $next;
 590                          $this->state = MIME_PARSER_BODY;
 591                          break;
 592                      }
 593                  }
 594                  if(($break = strspn($this->buffer, $this->header_name_characters, $this->buffer_position) + $this->buffer_position) < strlen($this->buffer))
 595                  {
 596                      switch($this->buffer[$break])
 597                      {
 598                          case ':':
 599                              $next = $break + 1;
 600                              break;
 601                          case ' ':
 602                              if(substr($this->buffer, $this->buffer_position, $break - $this->buffer_position)==='From')
 603                              {
 604                                  $next = $break + 1;
 605                                  break;
 606                              }
 607                          default:
 608                              if(!$this->SetPositionedWarning('message headers do not end with empty line', $this->buffer_position))
 609                                  return(0);
 610                              $part=array(
 611                                  'Type'=>'BodyStart',
 612                                  'Position'=>$this->offset + $this->buffer_position
 613                              );
 614                              $this->state = MIME_PARSER_BODY;
 615                              break 2;
 616                      }
 617                  }
 618                  else
 619                  {
 620                      $need_more_data = !$end;
 621                      if($end)
 622                      {
 623                          $part=array(
 624                              'Type'=>'BodyStart',
 625                              'Position'=>$this->offset + $this->buffer_position
 626                          );
 627                          $this->state = MIME_PARSER_BODY;
 628                      }
 629                      break;
 630                  }
 631  
 632                  $part=array(
 633                      'Type'=>'HeaderName',
 634                      'Name'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
 635                      'Position'=>$this->offset + $this->buffer_position
 636                  );
 637                  $this->buffer_position = $next;
 638                  $this->state = MIME_PARSER_HEADER_VALUE;
 639                  break;
 640              case MIME_PARSER_HEADER_VALUE:
 641                  $position = $this->buffer_position;
 642                  $value = '';
 643                  for(;;)
 644                  {
 645                      if($this->FindLineBreak($position, $break, $line_break))
 646                      {
 647                          $next = $line_break + strlen($break);
 648                          $line = substr($this->buffer, $position, $line_break - $position);
 649                          if(strlen($this->buffer) == $next)
 650                          {
 651                              if(!$end)
 652                              {
 653                                  $need_more_data = 1;
 654                                  break 2;
 655                              }
 656                              $value .= $line;
 657                              $part=array(
 658                                  'Type'=>'HeaderValue',
 659                                  'Value'=>$value,
 660                                  'Position'=>$this->offset + $this->buffer_position
 661                              );
 662                              $this->buffer_position = $next;
 663                              $this->state = MIME_PARSER_END;
 664                              break ;
 665                          }
 666                          else
 667                          {
 668                              $character = $this->buffer[$next];
 669                              if(!strcmp($character, ' ')
 670                              || !strcmp($character, "\t"))
 671                              {
 672                                  $value .= $line;
 673                                  $position = $next + 1;
 674                              }
 675                              else
 676                              {
 677                                  $value .= $line;
 678                                  $part=array(
 679                                      'Type'=>'HeaderValue',
 680                                      'Value'=>$value,
 681                                      'Position'=>$this->offset + $this->buffer_position
 682                                  );
 683                                  $this->buffer_position = $next;
 684                                  $this->state = MIME_PARSER_HEADER;
 685                                  break 2;
 686                              }
 687                          }
 688                      }
 689                      else
 690                      {
 691                          if(!$end)
 692                          {
 693                              $need_more_data = 1;
 694                              break;
 695                          }
 696                          else
 697                          {
 698                              $value .= substr($this->buffer, $position);
 699                              $part=array(
 700                                  'Type'=>'HeaderValue',
 701                                  'Value'=>$value,
 702                                  'Position'=>$this->offset + $this->buffer_position
 703                              );
 704                              $this->buffer_position = strlen($this->buffer);
 705                              $this->state = MIME_PARSER_END;
 706                              break;
 707                          }
 708                      }
 709                  }
 710                  break;
 711              case MIME_PARSER_BODY:
 712                  if($this->mbox)
 713                  {
 714                      $add = 0;
 715                      $append='';
 716                      if($this->FindLineBreak($this->buffer_position, $break, $line_break))
 717                      {
 718                          $next = $line_break + strlen($break);
 719                          $following = $next + strlen($break);
 720                          if($following >= strlen($this->buffer)
 721                          || GetType($line=strpos($this->buffer, $break, $following))!='integer')
 722                          {
 723                              if(!$end)
 724                              {
 725                                  $need_more_data = 1;
 726                                  break;
 727                              }
 728                          }
 729                          $start = substr($this->buffer, $next, strlen($break.'From '));
 730                          if(!strcmp($break.'From ', $start))
 731                          {
 732                              if($line_break == $this->buffer_position)
 733                              {
 734                                  $part=array(
 735                                      'Type'=>'MessageEnd',
 736                                      'Position'=>$this->offset + $this->buffer_position
 737                                  );
 738                                  $this->buffer_position = $following;
 739                                  $this->state = MIME_PARSER_START;
 740                                  break;
 741                              }
 742                              else
 743                                  $add = strlen($break);
 744                              $next = $line_break;
 745                          }
 746                          elseif(($indent = strspn($this->buffer, '>', $next)) > 0)
 747                          {
 748                              $start = substr($this->buffer, $next + $indent, strlen('From '));
 749                              if(!strcmp('From ', $start))
 750                              {
 751                                  $part=array(
 752                                      'Type'=>'BodyData',
 753                                      'Data'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position),
 754                                      'Position'=>$this->offset + $this->buffer_position
 755                                  );
 756                                  $this->buffer_position = $next + 1;
 757                                  break;
 758                              }
 759                          }
 760                      }
 761                      else
 762                      {
 763                          if(!$end)
 764                          {
 765                              $need_more_data = 1;
 766                              break;
 767                          }
 768                          $next = strlen($this->buffer);
 769                          $append="\r\n";
 770                      }
 771                      if($next > $this->buffer_position)
 772                      {
 773                          $part=array(
 774                              'Type'=>'BodyData',
 775                              'Data'=>substr($this->buffer, $this->buffer_position, $next + $add - $this->buffer_position).$append,
 776                              'Position'=>$this->offset + $this->buffer_position
 777                          );
 778                      }
 779                      elseif($end)
 780                      {
 781                          $part=array(
 782                              'Type'=>'MessageEnd',
 783                              'Position'=>$this->offset + $this->buffer_position
 784                          );
 785                          $this->state = MIME_PARSER_END;
 786                      }
 787                      $this->buffer_position = $next;
 788                  }
 789                  else
 790                  {
 791                      if(strlen($this->buffer)-$this->buffer_position)
 792                      {
 793                          $data=substr($this->buffer, $this->buffer_position, strlen($this->buffer) - $this->buffer_position);
 794                          $end_line = (!strcmp(substr($data,-1),"\n") || !strcmp(substr($data,-1),"\r"));
 795                          if($end
 796                          && !$end_line)
 797                          {
 798                              $data.="\n";
 799                              $end_line = 1;
 800                          }
 801                          $offset = $this->offset + $this->buffer_position;
 802                          $this->buffer_position = strlen($this->buffer);
 803                          $need_more_data = !$end;
 804                          if(!$end_line)
 805                          {
 806                              if(GetType($line_break=strrpos($data, "\n"))=='integer'
 807                              || GetType($line_break=strrpos($data, "\r"))=='integer')
 808                              {
 809                                  $line_break++;
 810                                  $this->buffer_position -= strlen($data) - $line_break;
 811                                  $data = substr($data, 0, $line_break);
 812                              }
 813                          }
 814                          $part=array(
 815                              'Type'=>'BodyData',
 816                              'Data'=>$data,
 817                              'Position'=>$offset
 818                          );
 819                      }
 820                      else
 821                      {
 822                          if($end)
 823                          {
 824                              $part=array(
 825                                  'Type'=>'MessageEnd',
 826                                  'Position'=>$this->offset + $this->buffer_position
 827                              );
 828                              $this->state = MIME_PARSER_END;
 829                          }
 830                          else
 831                              $need_more_data = 1;
 832                      }
 833                  }
 834                  break;
 835              default:
 836                  return($this->SetPositionedError($this->state.' is not a valid parser state', $this->buffer_position));
 837          }
 838          return(1);
 839      }
 840  
 841      Function QueueBodyParts()
 842      {
 843          for(;;)
 844          {
 845              if(!$this->body_parser->GetPart($part,$end))
 846                  return($this->SetError($this->body_parser->error));
 847              if($end)
 848                  return(1);
 849              if(!IsSet($part['Part']))
 850                  $part['Part']=$this->headers['Boundary'];
 851              $this->parts[]=$part;
 852          }
 853      }
 854  
 855      Function ParseParameters($value, &$first, &$parameters, $return)
 856      {
 857          $first = strtolower(trim(strtok($value, ';')));
 858          $values = trim(strtok(''));
 859          $parameters = array();
 860          $return_value = '';
 861          while(strlen($values))
 862          {
 863              $parameter = trim(strtolower(strtok($values, '=')));
 864              $value = trim(strtok(';'));
 865              $l = strlen($value);
 866              if($l > 1
 867              && !strcmp($value[0], '"')
 868              && !strcmp($value[$l - 1], '"'))
 869                  $value = substr($value, 1, $l - 2);
 870              $parameters[$parameter] = $value;
 871              if(!strcmp($parameter, $return))
 872                  $return_value = $value;
 873              $values = trim(strtok(''));
 874          }
 875          return($return_value);
 876      }
 877  
 878      Function ParseBody($data, $end, $offset)
 879      {
 880          $success = $this->body_parser->Parse($data, $end);
 881          $tw = count($this->body_parser->warnings);
 882          for(Reset($this->body_parser->warnings), $w = 0; $w < $tw; ++$w, Next($this->body_parser->warnings))
 883          {
 884              $position = Key($this->body_parser->warnings);
 885              $this->SetPositionedWarning($this->body_parser->warnings[$position], $offset + $position + $this->body_parser->message_position);
 886          }
 887          if(!$success)
 888          {
 889              if(($this->error_position = $this->body_parser->error_position) != -1)
 890                   $this->body_parser->error_position += $offset + $this->body_parser->message_position;
 891              return($this->SetError($this->body_parser->error));
 892          }
 893          return(1);
 894      }
 895  
 896      Function DecodePart($part)
 897      {
 898          switch($part['Type'])
 899          {
 900              case 'MessageStart':
 901                  $this->headers=array();
 902                  break;
 903              case 'HeaderName':
 904                  if($this->decode_bodies)
 905                      $this->current_header = strtolower($part['Name']);
 906                  break;
 907              case 'HeaderValue':
 908                  if($this->decode_headers)
 909                  {
 910                      $value = $part['Value'];
 911                      $error = '';
 912                      for($decoded_header = array(), $position = 0; $position<strlen($value); )
 913                      {
 914                          if(GetType($encoded=strpos($value,'=?', $position))!='integer')
 915                          {
 916                              if($position<strlen($value))
 917                              {
 918                                  if(count($decoded_header))
 919                                      $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position);
 920                                  else
 921                                  {
 922                                      $decoded_header[]=array(
 923                                          'Value'=>substr($value, $position),
 924                                          'Encoding'=>'ASCII'
 925                                      );
 926                                  }
 927                              }
 928                              break;
 929                          }
 930                          $set = $encoded + 2;
 931                          if(GetType($method=strpos($value,'?', $set))!='integer')
 932                          {
 933                              $error = 'invalid header encoding syntax '.$part['Value'];
 934                              $error_position = $part['Position'] + $set;
 935                              break;
 936                          }
 937                          $encoding=strtoupper(substr($value, $set, $method - $set));
 938                          $method += 1;
 939                          if(GetType($data=strpos($value,'?', $method))!='integer')
 940                          {
 941                              $error = 'invalid header encoding syntax '.$part['Value'];
 942                              $error_position = $part['Position'] + $set;
 943                              break;
 944                          }
 945                          $start = $data + 1;
 946                          if(GetType($end=strpos($value,'?=', $start))!='integer')
 947                          {
 948                              $error = 'invalid header encoding syntax '.$part['Value'];
 949                              $error_position = $part['Position'] + $start;
 950                              break;
 951                          }
 952                          if($encoded > $position)
 953                          {
 954                              if(count($decoded_header))
 955                                  $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position, $encoded - $position);
 956                              else
 957                              {
 958                                  $decoded_header[]=array(
 959                                      'Value'=>substr($value, $position, $encoded - $position),
 960                                      'Encoding'=>'ASCII'
 961                                  );
 962                              }
 963                          }
 964                          switch(strtolower(substr($value, $method, $data - $method)))
 965                          {
 966                              case 'q':
 967                                  if($end>$start)
 968                                  {
 969                                      for($decoded = '', $position = $start; $position < $end ; )
 970                                      {
 971                                          switch($value[$position])
 972                                          {
 973                                              case '=':
 974                                                  $h = HexDec($hex = strtolower(substr($value, $position+1, 2)));
 975                                                  if($end - $position < 3
 976                                                  || strcmp(sprintf('%02x', $h), $hex))
 977                                                  {
 978                                                      $warning = 'the header specified an invalid encoded character';
 979                                                      $warning_position = $part['Position'] + $position + 1;
 980                                                      if($this->ignore_syntax_errors)
 981                                                      {
 982                                                          $this->SetPositionedWarning($warning, $warning_position);
 983                                                          $decoded .= '=';
 984                                                          $position ++;
 985                                                      }
 986                                                      else
 987                                                      {
 988                                                          $error = $warning;
 989                                                          $error_position = $warning_position;
 990                                                          break 4;
 991                                                      }
 992                                                  }
 993                                                  else
 994                                                  {
 995                                                      $decoded .= Chr($h);
 996                                                      $position += 3;
 997                                                  }
 998                                                  break;
 999                                              case '_':
1000                                                  $decoded .= ' ';
1001                                                  $position++;
1002                                                  break;
1003                                              default:
1004                                                  $decoded .= $value[$position];
1005                                                  $position++;
1006                                                  break;
1007                                          }
1008                                      }
1009                                      if(count($decoded_header)
1010                                      && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII')
1011                                      || !strcmp($decoded_header[$last]['Encoding'], $encoding)))
1012                                      {
1013                                          $decoded_header[$last]['Value'].= $decoded;
1014                                          $decoded_header[$last]['Encoding']= $encoding;
1015                                      }
1016                                      else
1017                                      {
1018                                          $decoded_header[]=array(
1019                                              'Value'=>$decoded,
1020                                              'Encoding'=>$encoding
1021                                          );
1022                                      }
1023                                  }
1024                                  break;
1025                              case 'b':
1026                                  $decoded=base64_decode(substr($value, $start, $end - $start));
1027                                  if($end <= $start
1028                                  || GetType($decoded) != 'string'
1029                                  || strlen($decoded) == 0)
1030                                  {
1031                                      $warning = 'the header specified an invalid base64 encoded text';
1032                                      $warning_position = $part['Position'] + $start;
1033                                      if($this->ignore_syntax_errors)
1034                                          $this->SetPositionedWarning($warning, $warning_position);
1035                                      else
1036                                      {
1037                                          $error = $warning;
1038                                          $error_position = $warning_position;
1039                                          break 2;
1040                                      }
1041                                  }
1042                                  if(count($decoded_header)
1043                                  && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII')
1044                                  || !strcmp($decoded_header[$last]['Encoding'], $encoding)))
1045                                  {
1046                                      $decoded_header[$last]['Value'].= $decoded;
1047                                      $decoded_header[$last]['Encoding']= $encoding;
1048                                  }
1049                                  else
1050                                  {
1051                                      $decoded_header[]=array(
1052                                          'Value'=>$decoded,
1053                                          'Encoding'=>$encoding
1054                                      );
1055                                  }
1056                                  break;
1057                              default:
1058                                  $error = 'the header specified an unsupported encoding method';
1059                                  $error_position = $part['Position'] + $method;
1060                                  break 2;
1061                          }
1062                          $position = $end + 2;
1063                          $position += strspn($value, " \t", $position);
1064                      }
1065                      if(strlen($error)==0
1066                      && count($decoded_header))
1067                          $part['Decoded']=$decoded_header;
1068                  }
1069                  if($this->decode_bodies
1070                  || $this->decode_headers)
1071                  {
1072                      switch($this->current_header)
1073                      {
1074                          case 'content-type:':
1075                              $boundary = $this->ParseParameters($part['Value'], $type, $parameters, 'boundary');
1076                              $this->headers['Type'] = $type;
1077                              if($this->decode_headers)
1078                              {
1079                                  $part['MainValue'] = $type;
1080                                  $part['Parameters'] = $parameters;
1081                              }
1082                              if(!strcmp(strtok($type, '/'), 'multipart'))
1083                              {
1084                                  $this->headers['Multipart'] = 1;
1085                                  if(strlen($boundary))
1086                                      $this->headers['Boundary'] = $boundary;
1087                                  else
1088                                      return($this->SetPositionedError('multipart content-type header does not specify the boundary parameter', $part['Position']));
1089                              }
1090                              break;
1091                          case 'content-transfer-encoding:':
1092                              switch($this->headers['Encoding']=strtolower(trim($part['Value'])))
1093                              {
1094                                  case 'quoted-printable':
1095                                      $this->headers['QuotedPrintable'] = 1;
1096                                      break;
1097                                  case '7 bit':
1098                                  case '8 bit':
1099                                      if(!$this->SetPositionedWarning('"'.$this->headers['Encoding'].'" is an incorrect content transfer encoding type', $part['Position']))
1100                                          return(0);
1101                                  case '7bit':
1102                                  case '8bit':
1103                                  case 'binary':
1104                                      break;
1105                                  case 'base64':
1106                                      $this->headers['Base64']=1;
1107                                      break;
1108                                  default:
1109                                      if(!$this->SetPositionedWarning('decoding '.$this->headers['Encoding'].' encoded bodies is not yet supported', $part['Position']))
1110                                          return(0);
1111                              }
1112                              break;
1113                      }
1114                  }
1115                  break;
1116              case 'BodyStart':
1117                  if($this->decode_bodies
1118                  && IsSet($this->headers['Multipart']))
1119                  {
1120                      $this->body_parser_state = MIME_PARSER_BODY_START;
1121                      $this->body_buffer = '';
1122                      $this->body_buffer_position = 0;
1123                  }
1124                  break;
1125              case 'MessageEnd':
1126                  if($this->decode_bodies
1127                  && IsSet($this->headers['Multipart'])
1128                  && $this->body_parser_state != MIME_PARSER_BODY_DONE)
1129                  {
1130                      if($this->body_parser_state != MIME_PARSER_BODY_DATA)
1131                          return($this->SetPositionedError('incomplete message body part', $part['Position']));
1132                      if(!$this->SetPositionedWarning('truncated message body part', $part['Position']))
1133                          return(0);
1134                  }
1135                  break;
1136              case 'BodyData':
1137                  if($this->decode_bodies)
1138                  {
1139                      if(strlen($this->body_buffer)==0)
1140                      {
1141                          $this->body_buffer = $part['Data'];
1142                          $this->body_offset = $part['Position'];
1143                      }
1144                      else
1145                          $this->body_buffer .= $part['Data'];
1146                      if(IsSet($this->headers['Multipart']))
1147                      {
1148                          $boundary = '--'.$this->headers['Boundary'];
1149                          switch($this->body_parser_state)
1150                          {
1151                              case MIME_PARSER_BODY_START:
1152                                  for($position = $this->body_buffer_position; ;)
1153                                  {
1154                                      if(!$this->FindBodyLineBreak($position, $break, $line_break))
1155                                          return(1);
1156                                      $next = $line_break + strlen($break);
1157                                      if(!strcmp(rtrim(substr($this->body_buffer, $position, $line_break - $position)), $boundary))
1158                                      {
1159                                          $part=array(
1160                                              'Type'=>'StartPart',
1161                                              'Part'=>$this->headers['Boundary'],
1162                                              'Position'=>$this->body_offset + $next
1163                                          );
1164                                          $this->parts[]=$part;
1165                                          UnSet($this->body_parser);
1166                                          $this->body_parser = new mime_parser_class;
1167                                          $this->body_parser->decode_bodies = 1;
1168                                          $this->body_parser->decode_headers = $this->decode_headers;
1169                                          $this->body_parser->mbox = 0;
1170                                          $this->body_parser_state = MIME_PARSER_BODY_DATA;
1171                                          $this->body_buffer = substr($this->body_buffer, $next);
1172                                          $this->body_offset += $next;
1173                                          $this->body_buffer_position = 0;
1174                                          break;
1175                                      }
1176                                      else
1177                                          $position = $next;
1178                                  }
1179                              case MIME_PARSER_BODY_DATA:
1180                                  for($position = $this->body_buffer_position; ;)
1181                                  {
1182                                      if(!$this->FindBodyLineBreak($position, $break, $line_break))
1183                                      {
1184                                          if($position > 0)
1185                                          {
1186                                              if(!$this->ParseBody(substr($this->body_buffer, 0, $position), 0, $this->body_offset))
1187                                                  return(0);
1188                                              if(!$this->QueueBodyParts())
1189                                                  return(0);
1190                                          }
1191                                          $this->body_buffer = substr($this->body_buffer, $position);
1192                                          $this->body_buffer_position = 0;
1193                                          $this->body_offset += $position;
1194                                          return(1);
1195                                      }
1196                                      $next = $line_break + strlen($break);
1197                                      $line = rtrim(substr($this->body_buffer, $position, $line_break - $position));
1198                                      if(!strcmp($line, $boundary.'--'))
1199                                      {
1200                                          if(!$this->ParseBody(substr($this->body_buffer, 0, $position), 1, $this->body_offset))
1201                                              return(0);
1202                                          if(!$this->QueueBodyParts())
1203                                              return(0);
1204                                          $part=array(
1205                                              'Type'=>'EndPart',
1206                                              'Part'=>$this->headers['Boundary'],
1207                                              'Position'=>$this->body_offset + $position
1208                                          );
1209                                          $this->body_buffer = substr($this->body_buffer, $next);
1210                                          $this->body_buffer_position = 0;
1211                                          $this->body_offset += $next;
1212                                          $this->body_parser_state = MIME_PARSER_BODY_DONE;
1213                                          break 2;
1214                                      }
1215                                      elseif(!strcmp($line, $boundary))
1216                                      {
1217                                          if(!$this->ParseBody(substr($this->body_buffer, 0, $position), 1, $this->body_offset))
1218                                              return(0);
1219                                          if(!$this->QueueBodyParts())
1220                                              return(0);
1221                                          $part=array(
1222                                              'Type'=>'EndPart',
1223                                              'Part'=>$this->headers['Boundary'],
1224                                              'Position'=>$this->body_offset + $position
1225                                          );
1226                                          $this->parts[] = $part;
1227                                          $part=array(
1228                                              'Type'=>'StartPart',
1229                                              'Part'=>$this->headers['Boundary'],
1230                                              'Position'=>$this->body_offset + $next
1231                                          );
1232                                          $this->parts[] = $part;
1233                                          UnSet($this->body_parser);
1234                                          $this->body_parser = new mime_parser_class;
1235                                          $this->body_parser->decode_bodies = 1;
1236                                          $this->body_parser->decode_headers = $this->decode_headers;
1237                                          $this->body_parser->mbox = 0;
1238                                          $this->body_buffer = substr($this->body_buffer, $next);
1239                                          $this->body_buffer_position = 0;
1240                                          $this->body_offset += $next;
1241                                          $position=0;
1242                                          continue;
1243                                      }
1244                                      $position = $next;
1245                                  }
1246                                  break;
1247                              case MIME_PARSER_BODY_DONE:
1248                                  return(1);
1249                              default:
1250                                  return($this->SetPositionedError($this->state.' is not a valid body parser state', $this->body_buffer_position));
1251                          }
1252                      }
1253                      elseif(IsSet($this->headers['QuotedPrintable']))
1254                      {
1255                          for($end = strlen($this->body_buffer), $decoded = '', $position = $this->body_buffer_position; $position < $end; )
1256                          {
1257                              if(GetType($equal = strpos($this->body_buffer, '=', $position))!='integer')
1258                              {
1259                                  $decoded .= substr($this->body_buffer, $position);
1260                                  $position = $end;
1261                                  break;
1262                              }
1263                              $next = $equal + 1;
1264                              switch($end - $equal)
1265                              {
1266                                  case 1:
1267                                      $decoded .= substr($this->body_buffer, $position, $equal - $position);
1268                                      $position = $equal;
1269                                      break 2;
1270                                  case 2:
1271                                      $decoded .= substr($this->body_buffer, $position, $equal - $position);
1272                                      if(!strcmp($this->body_buffer[$next],"\n"))
1273                                          $position = $end;
1274                                      else
1275                                          $position = $equal;
1276                                      break 2;
1277                              }
1278                              if(!strcmp(substr($this->body_buffer, $next, 2), $break="\r\n")
1279                              || !strcmp($this->body_buffer[$next], $break="\n")
1280                              || !strcmp($this->body_buffer[$next], $break="\r"))
1281                              {
1282                                  $decoded .= substr($this->body_buffer, $position, $equal - $position);
1283                                  $position = $next + strlen($break);
1284                                  continue;
1285                              }
1286                              $decoded .= substr($this->body_buffer, $position, $equal - $position);
1287                              $h = HexDec($hex=strtolower(substr($this->body_buffer, $next, 2)));
1288                              if(strcmp(sprintf('%02x', $h), $hex))
1289                              {
1290                                  if(!$this->SetPositionedWarning('the body specified an invalid quoted-printable encoded character', $this->body_offset + $next))
1291                                      return(0);
1292                                  $decoded.='=';
1293                                  $position=$next;
1294                              }
1295                              else
1296                              {
1297                                  $decoded .= Chr($h);
1298                                  $position = $equal + 3;
1299                              }
1300                          }
1301                          if(strlen($decoded)==0)
1302                          {
1303                              $this->body_buffer_position = $position;
1304                              return(1);
1305                          }
1306                          $part['Data'] = $decoded;
1307                          $this->body_buffer = substr($this->body_buffer, $position);
1308                          $this->body_buffer_position = 0;
1309                          $this->body_offset += $position;
1310                      }
1311                      elseif(IsSet($this->headers['Base64']))
1312                      {
1313                          $part['Data'] = base64_decode($this->body_buffer_position ? substr($this->body_buffer,$this->body_buffer_position) : $this->body_buffer);
1314                          $this->body_offset += strlen($this->body_buffer) - $this->body_buffer_position;
1315                          $this->body_buffer_position = 0;
1316                          $this->body_buffer = '';
1317                      }
1318                      else
1319                      {
1320                          $part['Data'] = substr($this->body_buffer, $this->body_buffer_position);
1321                          $this->body_buffer_position = 0;
1322                          $this->body_buffer = '';
1323                      }
1324                  }
1325                  break;
1326          }
1327          $this->parts[]=$part;
1328          return(1);
1329      }
1330  
1331      Function DecodeStream($parameters, $position, &$end_of_message, &$decoded)
1332      {
1333          $this->message_position = $position;
1334          $end_of_message = 1;
1335          $state = MIME_MESSAGE_START;
1336          for(;;)
1337          {
1338              if(!$this->GetPart($part, $end))
1339                  return(0);
1340              if($end)
1341              {
1342                  if(IsSet($parameters['File']))
1343                  {
1344                      $end_of_data = feof($this->file);
1345                      if($end_of_data)
1346                          break;
1347                      $data = @fread($this->file, $this->message_buffer_length);
1348                      if(GetType($data)!='string')
1349                          return($this->SetPHPError('could not read the message file', $php_errormsg));
1350                      $end_of_data = feof($this->file);
1351                  }
1352                  else
1353                  {
1354                      $end_of_data=($this->position>=strlen($parameters['Data']));
1355                      if($end_of_data)
1356                          break;
1357                      $data = substr($parameters['Data'], $this->position, $this->message_buffer_length);
1358                      $this->position += strlen($data);
1359                      $end_of_data = ($this->position >= strlen($parameters['Data']));
1360                  }
1361                  if(!$this->Parse($data, $end_of_data))
1362                      return(0);
1363                  continue;
1364              }
1365              $type = $part['Type'];
1366              switch($state)
1367              {
1368                  case MIME_MESSAGE_START:
1369                      switch($type)
1370                      {
1371                          case 'MessageStart':
1372                              $decoded=array(
1373                                  'Headers'=>array(),
1374                                  'Parts'=>array(),
1375                                  'Position'=>$this->message_position,
1376                              );
1377                              $end_of_message = 0;
1378                              $state = MIME_MESSAGE_GET_HEADER_NAME;
1379                              continue 3;
1380                          case 'MessageEnd':
1381                              return($this->SetPositionedWarning('incorrectly ended body part', $part['Position']));
1382                      }
1383                      break;
1384  
1385                  case MIME_MESSAGE_GET_HEADER_NAME:
1386                      switch($type)
1387                      {
1388                          case 'HeaderName':
1389                              $header = strtolower($part['Name']);
1390                              $state = MIME_MESSAGE_GET_HEADER_VALUE;
1391                              continue 3;
1392                          case 'BodyStart':
1393                              $state = MIME_MESSAGE_GET_BODY;
1394                              $part_number = 0;
1395                              continue 3;
1396                      }
1397                      break;
1398  
1399                  case MIME_MESSAGE_GET_HEADER_VALUE:
1400                      switch($type)
1401                      {
1402                          case 'HeaderValue':
1403                              $value = trim($part['Value']);
1404                              if(!IsSet($decoded['Headers'][$header]))
1405                              {
1406                                  $h = 0;
1407                                  $decoded['Headers'][$header]=$value;
1408                                  if($this->extract_addresses
1409                                  && IsSet($this->address_headers[$header]))
1410                                      $decoded['HeaderPositions'][$header] = $part['Position'];
1411                              }
1412                              elseif(GetType($decoded['Headers'][$header])=='string')
1413                              {
1414                                  $h = 1;
1415                                  $decoded['Headers'][$header]=array($decoded['Headers'][$header], $value);
1416                                  if($this->extract_addresses
1417                                  && IsSet($this->address_headers[$header]))
1418                                      $decoded['HeaderPositions'][$header] = array($decoded['HeaderPositions'][$header], $part['Position']);
1419                              }
1420                              else
1421                              {
1422                                  $h = count($decoded['Headers'][$header]);
1423                                  $decoded['Headers'][$header][]=$value;
1424                                  if($this->extract_addresses
1425                                  && IsSet($this->address_headers[$header]))
1426                                      $decoded['HeaderPositions'][$header][] = $part['Position'];
1427                              }
1428                              if(IsSet($part['Decoded'])
1429                              && (count($part['Decoded'])>1
1430                              || strcmp($part['Decoded'][0]['Encoding'],'ASCII')
1431                              || strcmp($value, trim($part['Decoded'][0]['Value']))))
1432                              {
1433                                  $p=$part['Decoded'];
1434                                  $p[0]['Value']=ltrim($p[0]['Value']);
1435                                  $last=count($p)-1;
1436                                  $p[$last]['Value']=rtrim($p[$last]['Value']);
1437                                  $decoded['DecodedHeaders'][$header][$h]=$p;
1438                              }
1439                              switch($header)
1440                              {
1441                                  case 'content-disposition:':
1442                                      $filename='filename';
1443                                      break;
1444                                  case 'content-type:':
1445                                      if(!IsSet($decoded['FileName']))
1446                                      {
1447                                          $filename='name';
1448                                          break;
1449                                      }
1450                                  default:
1451                                      $filename='';
1452                                      break;
1453                              }
1454                              if(strlen($filename))
1455                              {
1456                                  if(IsSet($decoded['DecodedHeaders'][$header][$h])
1457                                  && count($decoded['DecodedHeaders'][$header][$h]) == 1)
1458                                  {
1459                                      $value = $decoded['DecodedHeaders'][$header][$h][0]['Value'];
1460                                      $encoding = $decoded['DecodedHeaders'][$header][$h][0]['Encoding'];
1461                                  }
1462                                  else
1463                                      $encoding = '';
1464                                  $this->ParseStructuredHeader($value, $type, $header_parameters, $character_sets, $languages);
1465                                  if(IsSet($header_parameters[$filename]))
1466                                  {
1467                                      $decoded['FileName']=$header_parameters[$filename];
1468                                      if(IsSet($character_sets[$filename])
1469                                      && strlen($character_sets[$filename]))
1470                                          $decoded['FileNameCharacterSet']=$character_sets[$filename];
1471                                      if(IsSet($character_sets['language'])
1472                                      && strlen($character_sets['language']))
1473                                          $decoded['FileNameCharacterSet']=$character_sets[$filename];
1474                                      if(!IsSet($decoded['FileNameCharacterSet'])
1475                                      && strlen($encoding))
1476                                          $decoded['FileNameCharacterSet'] = $encoding;
1477                                      if(!strcmp($header, 'content-disposition:'))
1478                                          $decoded['FileDisposition']=$type;
1479                                  }
1480                              }
1481                              $state = MIME_MESSAGE_GET_HEADER_NAME;
1482                              continue 3;
1483                      }
1484                      break;
1485  
1486                  case MIME_MESSAGE_GET_BODY:
1487                      switch($type)
1488                      {
1489                          case 'BodyData':
1490                              if(IsSet($parameters['SaveBody']))
1491                              {
1492                                  if(!IsSet($decoded['BodyFile']))
1493                                  {
1494                                      $directory_separator=(defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/');
1495                                      $path = (strlen($parameters['SaveBody']) ? ($parameters['SaveBody'].(strcmp($parameters['SaveBody'][strlen($parameters['SaveBody'])-1], $directory_separator) ? $directory_separator : '')) : '');
1496                                      $filename =    strval($this->body_part_number);
1497                                      if($this->use_part_file_names
1498                                      && !$this->GetPartFileName($decoded, $filename))
1499                                          return(0);
1500                                      if(file_exists($path.$filename))
1501                                      {
1502                                          if(GetType($dot = strrpos($filename, '.'))=='integer')
1503                                          {
1504                                              $base = substr($filename, 0, $dot);
1505                                              $extension = substr($filename, $dot);
1506                                          }
1507                                          else
1508                                          {
1509                                              $base = $filename;
1510                                              $extension = '';
1511                                          }
1512                                          $appendix = 0;
1513                                          do
1514                                          {
1515                                              ++$appendix;
1516                                              $filename = $base.$appendix.$extension;
1517                                          }
1518                                          while(file_exists($path.$filename));
1519                                      }
1520                                      $path .= $filename;
1521                                      if(!($this->body_file = fopen($path, 'wb')))
1522                                          return($this->SetPHPError('could not create file '.$path.' to save the message body part', $php_errormsg));
1523                                      $decoded['BodyFile'] = $path;
1524                                      $decoded['BodyPart'] = $this->body_part_number;
1525                                      $decoded['BodyLength'] = 0;
1526                                      $this->body_part_number++;
1527                                  }
1528                                  if(strlen($part['Data'])
1529                                  && !fwrite($this->body_file, $part['Data']))
1530                                  {
1531                                      $this->SetPHPError('could not save the message body part to file '.$decoded['BodyFile'], $php_errormsg);
1532                                      fclose($this->body_file);
1533                                      @unlink($decoded['BodyFile']);
1534                                      return(0);
1535                                  }
1536                              }
1537                              elseif(IsSet($parameters['SkipBody'])
1538                              && $parameters['SkipBody'])
1539                              {
1540                                  if(!IsSet($decoded['BodyPart']))
1541                                  {
1542                                      $decoded['BodyPart'] = $this->body_part_number;
1543                                      $decoded['BodyLength'] = 0;
1544                                      $this->body_part_number++;
1545                                  }
1546                              }
1547                              else
1548                              {
1549                                  if(IsSet($decoded['Body']))
1550                                      $decoded['Body'].=$part['Data'];
1551                                  else
1552                                  {
1553                                      $decoded['Body']=$part['Data'];
1554                                      $decoded['BodyPart'] = $this->body_part_number;
1555                                      $decoded['BodyLength'] = 0;
1556                                      $this->body_part_number++;
1557                                  }
1558                              }
1559                              $decoded['BodyLength'] += strlen($part['Data']);
1560                              continue 3;
1561                          case 'StartPart':
1562                              if(!$this->DecodeStream($parameters, $position + $part['Position'], $end_of_part, $decoded_part))
1563                                  return(0);
1564                              $decoded['Parts'][$part_number]=$decoded_part;
1565                              $part_number++;
1566                              $state = MIME_MESSAGE_GET_BODY_PART;
1567                              continue 3;
1568                          case 'MessageEnd':
1569                              if(IsSet($decoded['BodyFile']))
1570                                  fclose($this->body_file);
1571                              return(1);
1572                      }
1573                      break;
1574  
1575                  case MIME_MESSAGE_GET_BODY_PART:
1576                      switch($type)
1577                      {
1578                          case 'EndPart':
1579                              $state = MIME_MESSAGE_GET_BODY;
1580                              continue 3;
1581                      }
1582                      break;
1583              }
1584              return($this->SetError('unexpected decoded message part type '.$type.' in state '.$state));
1585          }
1586          return(1);
1587      }
1588  
1589      /* Public functions */
1590  
1591      Function GetPartFileName($decoded, &$filename)
1592      {
1593          if(IsSet($decoded['FileName']))
1594              $filename = basename($decoded['FileName']);
1595          return(1);
1596      }
1597  
1598      Function Parse($data, $end)
1599      {
1600          if(strlen($this->error))
1601              return(0);
1602          if($this->state==MIME_PARSER_END)
1603              return($this->SetError('the parser already reached the end'));
1604          $length = strlen($data);
1605          if($this->track_lines
1606          && $length)
1607          {
1608              $line = $this->last_line;
1609              $position = 0;
1610              if($this->last_carriage_return)
1611              {
1612                  if($data[0] == "\n")
1613                      ++$position;
1614                  $this->lines[++$line] = $this->line_offset + $position;
1615                  $this->last_carriage_return = 0;
1616              }
1617              while($position < $length)
1618              {
1619                  $position += strcspn($data, "\r\n", $position) ;
1620                  if($position >= $length)
1621                      break;
1622                  if($data[$position] == "\r")
1623                  {
1624                      ++$position;
1625                      if($position >= $length)
1626                      {
1627                          $this->last_carriage_return = 1;
1628                          break;
1629                      }
1630                      if($data[$position] == "\n")
1631                          ++$position;
1632                      $this->lines[++$line] = $this->line_offset + $position;
1633                  }
1634                  else
1635                  {
1636                      ++$position;
1637                      $this->lines[++$line] = $this->line_offset + $position;
1638                  }
1639              }
1640              $this->last_line = $line;
1641              $this->line_offset += $length;
1642          }
1643          $this->buffer .= $data;
1644          do
1645          {
1646              Unset($part);
1647              if(!$this->ParsePart($end, $part, $need_more_data))
1648                  return(0);
1649              if(IsSet($part)
1650              && !$this->DecodePart($part))
1651                  return(0);
1652          }
1653          while(!$need_more_data
1654          && $this->state!=MIME_PARSER_END);
1655          if($end
1656          && $this->state!=MIME_PARSER_END)
1657              return($this->SetError('reached a premature end of data'));
1658          if($this->buffer_position>0)
1659          {
1660              $this->offset += $this->buffer_position;
1661              $this->buffer = substr($this->buffer, $this->buffer_position);
1662              $this->buffer_position = 0;
1663          }
1664          return(1);
1665      }
1666  
1667      Function ParseFile($file)
1668      {
1669          if(strlen($this->error))
1670              return(0);
1671          if(!($stream = @fopen($file, 'r')))
1672              return($this->SetPHPError('Could not open the file '.$file, $php_errormsg));
1673          for($end = 0;!$end;)
1674          {
1675              if(!($data = @fread($stream, $this->message_buffer_length)))
1676              {
1677                  $this->SetPHPError('Could not read the file '.$file, $php_errormsg);
1678                  fclose($stream);
1679                  return(0);
1680              }
1681              $end=feof($stream);
1682              if(!$this->Parse($data, $end))
1683              {
1684                  fclose($stream);
1685                  return(0);
1686              }
1687          }
1688          fclose($stream);
1689          return(1);
1690      }
1691  
1692      Function GetPart(&$part, &$end)
1693      {
1694          $end = ($this->part_position >= count($this->parts));
1695          if($end)
1696          {
1697              if($this->part_position)
1698              {
1699                  $this->part_position = 0;
1700                  $this->parts = array();
1701              }
1702          }
1703          else
1704          {
1705              $part = $this->parts[$this->part_position];
1706              $this->part_position ++;
1707          }
1708          return(1);
1709      }
1710  
1711  /*
1712  {metadocument}
1713      <function>
1714          <name>Decode</name>
1715          <type>BOOLEAN</type>
1716          <documentation>
1717              <purpose>Parse and decode message data and retrieve its structure.</purpose>
1718              <usage>Pass an array to the <argumentlink>
1719                      <function>Decode</function>
1720                      <argument>parameters</argument>
1721                  </argumentlink>
1722                  parameter to define whether the message data should be read and
1723                  parsed from a file or a data string, as well additional parsing
1724                  options. The <argumentlink>
1725                      <function>Decode</function>
1726                      <argument>decoded</argument>
1727                  </argumentlink> returns the
1728                  data structure of the parsed messages.</usage>
1729              <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
1730                  the specified message data is parsed successfully. Otherwise,
1731                  check the variables <variablelink>error</variablelink> and
1732                  <variablelink>error_position</variablelink> to determine what
1733                  error occurred and the relevant message position.</returnvalue>
1734          </documentation>
1735          <argument>
1736              <name>parameters</name>
1737              <type>HASH</type>
1738              <documentation>
1739                  <purpose>Associative array to specify parameters for the message
1740                      data parsing and decoding operation. Here follows the list of
1741                      supported parameters that should be used as indexes of the
1742                      array:<paragraphbreak />
1743                      <tt>File</tt><paragraphbreak />
1744                      Name of the file from which the message data will be read. It
1745                      may be the name of a file stream or a remote URL, as long as
1746                      your PHP installation is configured to allow accessing remote
1747                      files with the <tt>fopen()</tt> function.<paragraphbreak />
1748                      <tt>Data</tt><paragraphbreak />
1749                      String that specifies the message data. This should be used
1750                      as alternative data source for passing data available in memory,
1751                      like for instance messages stored in a database that was queried
1752                      dynamically and the message data was fetched into a string
1753                      variable.<paragraphbreak />
1754                      <tt>SaveBody</tt><paragraphbreak />
1755                      If this parameter is specified, the message body parts are saved
1756                      to files. The path of the directory where the files are saved is
1757                      defined by this parameter value. The information about the
1758                      message body part structure is returned by the <argumentlink>
1759                          <function>Decode</function>
1760                          <argument>decoded</argument>
1761                      </argumentlink> argument, but it just returns the body data part
1762                      file name instead of the actual body data. It is recommended for
1763                      retrieving messages larger than the available memory. The names
1764                      of the body part files are numbers starting from
1765                      <stringvalue>1</stringvalue>.<paragraphbreak />
1766                      <tt>SkipBody</tt><paragraphbreak />
1767                      If this parameter is set to <booleanvalue>1</booleanvalue>, the
1768                      message body parts are skipped. This means the information about
1769                      the message body part structure is returned by the <argumentlink>
1770                          <function>Decode</function>
1771                          <argument>decoded</argument>
1772                      </argumentlink> but it does not return any body data. It is
1773                      recommended just for parsing messages without the need to
1774                      retrieve the message body part data.</purpose>
1775              </documentation>
1776          </argument>
1777          <argument>
1778              <name>decoded</name>
1779              <type>ARRAY</type>
1780              <out />
1781              <documentation>
1782                  <purpose>Retrieve the structure of the parsed message headers and
1783                      body data.<paragraphbreak />
1784                      The argument is used to return by reference an array of message
1785                      structure definitions. Each array entry refers to the structure
1786                      of each message that is found and parsed successfully.<paragraphbreak />
1787                      Each message entry consists of an associative array with several
1788                      entries that describe the message structure. Here follows the
1789                      list of message structure entries names and the meaning of the
1790                      respective values:<paragraphbreak />
1791                      <tt>Headers</tt><paragraphbreak />
1792                      Associative array that returns the list of all the message
1793                      headers. The array entries are the header names mapped to
1794                      lower case, including the end colon. The array values are the
1795                      respective header raw values without any start or trailing white
1796                      spaces. Long header values split between multiple message lines
1797                      are gathered in single string without line breaks. If an header
1798                      with the same name appears more than once in the message, the
1799                      respective value is an array with the values of all of the
1800                      header occurrences.<paragraphbreak />
1801                      <tt>DecodedHeaders</tt><paragraphbreak />
1802                      Associative array that returns the list of all the encoded
1803                      message headers when the
1804                      <variablelink>decode_headers</variablelink> variable is set. The
1805                      array entries are the header names mapped to lower case,
1806                      including the end colon. The array values are also arrays that
1807                      list only the occurrences of the header that originally were
1808                      encoded. Each entry of the decoded header array contains more
1809                      associative arrays that describe each part of the decoded
1810                      header. Each of those associative arrays have an entry named
1811                      <tt>Value</tt> that contains the decoded header part value, and
1812                      another entry named <tt>Encoding</tt> that specifies the
1813                      character set encoding of the value in upper case.<paragraphbreak />
1814                      <tt>ExtractedAddresses</tt><paragraphbreak />
1815                      If the <variablelink>extract_addresses</variablelink> variable
1816                      is set to <booleanvalue>1</booleanvalue>, this entry is set to an
1817                      associative array with the addresses found in the headers
1818                      specified by the <variablelink>address_headers</variablelink>
1819                      variable.<paragraphbreak />
1820                      The parsed addresses found on each header are returned as an
1821                      array with the format of the <link>
1822                          <data>addresses</data>
1823                          <url>rfc822_addresses_class.html#argument_ParseAddressList_addresses</url>
1824                      </link> argument of the <link>
1825                          <data>ParseAddressList</data>
1826                          <url>rfc822_addresses_class.html#function_ParseAddressList</url>
1827                      </link> function of the <link>
1828                          <data>RFC 822 addresses</data>
1829                          <url>rfc822_addresses_class.html</url>
1830                      </link> class.<paragraphbreak />
1831                      <tt>Parts</tt><paragraphbreak />
1832                      If this message content type is multipart, this entry is an
1833                      array that describes each of the parts contained in the message
1834                      body. Each message part is described by an associative array
1835                      with the same structure of a complete message
1836                      definition.<paragraphbreak />
1837                      <tt>Body</tt><paragraphbreak />
1838                      String with the decoded data contained in the message body. If
1839                      the <tt>SaveBody</tt> or <tt>SkipBody</tt> parameters are
1840                      defined, the <tt>Body</tt> entry is not set.<paragraphbreak />
1841                      <tt>BodyFile</tt><paragraphbreak />
1842                      Name of the file to which the message body data was saved when
1843                      the <tt>SaveBody</tt> parameter is defined.<paragraphbreak />
1844                      <tt>BodyLength</tt><paragraphbreak />
1845                      Length of the current decoded body part.<paragraphbreak />
1846                      <tt>BodyPart</tt><paragraphbreak />
1847                      Number of the current message body part.<paragraphbreak />
1848                      <tt>FileName</tt><paragraphbreak />
1849                      Name of the file for body parts composed from
1850                      files.<paragraphbreak />
1851                      <tt>FileNameCharacterSet</tt><paragraphbreak />
1852                      Character set encoding for file parts with names that may
1853                      include non-ASCII characters.<paragraphbreak />
1854                      <tt>FileNameLanguage</tt><paragraphbreak />
1855                      Language of file parts with names that may include non-ASCII
1856                      characters.<paragraphbreak />
1857                      <tt>FileDisposition</tt><paragraphbreak />
1858                      Disposition of parts which are files. It may be either
1859                      <tt><stringvalue>inline</stringvalue></tt> for file parts to be
1860                      displayed with the message, or
1861                      <tt><stringvalue>attachment</stringvalue></tt>
1862                      otherwise.<paragraphbreak />
1863                      <tt>Position</tt><paragraphbreak />
1864                      Position of the part in terms of bytes since the beginning of
1865                      the message.</purpose>
1866              </documentation>
1867          </argument>
1868          <do>
1869  {/metadocument}
1870  */
1871      Function Decode($parameters, &$decoded)
1872      {
1873          if(IsSet($parameters['File']))
1874          {
1875              if(!($this->file = @fopen($parameters['File'], 'r')))
1876                  return($this->SetPHPError('could not open the message file to decode '.$parameters['File'], $php_errormsg));
1877          }
1878          elseif(IsSet($parameters['Data']))
1879              $this->position = 0;
1880          else
1881              return($this->SetError('it was not specified a valid message to decode'));
1882          $this->warnings = $decoded = array();
1883          $this->ResetParserState();
1884          $addresses = new rfc822_addresses_class;
1885          $addresses->ignore_syntax_errors = $this->ignore_syntax_errors;
1886          for($message = 0; ($success = $this->DecodeStream($parameters, 0, $end_of_message, $decoded_message)) && !$end_of_message; $message++)
1887          {
1888              if($this->extract_addresses)
1889              {
1890                  $headers = $decoded_message['Headers'];
1891                  $positions = (IsSet($decoded_message['HeaderPositions']) ? $decoded_message['HeaderPositions'] : array());
1892                  $th = count($headers);
1893                  for(Reset($headers), $h = 0; $h<$th; Next($headers), ++$h)
1894                  {
1895                      $header = Key($headers);
1896                      if(IsSet($this->address_headers[$header])
1897                      && $this->address_headers[$header])
1898                      {
1899                          $values = (GetType($headers[$header]) == 'array' ? $headers[$header] : array($headers[$header]));
1900                          $p = (GetType($positions[$header]) == 'array' ? $positions[$header] : array($positions[$header]));
1901                          $tv = count($values);
1902                          for($v = 0; $v<$tv; ++$v)
1903                          {
1904                              if($addresses->ParseAddressList($values[$v], $a))
1905                              {
1906                                  if($v==0)
1907                                      $decoded_message['ExtractedAddresses'][$header] = $a;
1908                                  else
1909                                  {
1910                                      $tl = count($a);
1911                                      for($l = 0; $l<$tl; ++$l)
1912                                          $decoded_message['ExtractedAddresses'][$header][] = $a[$l];
1913                                  }
1914                                  $tw = count($addresses->warnings);
1915                                  for($w = 0, Reset($addresses->warnings); $w < $tw; Next($addresses->warnings), $w++)
1916                                  {
1917                                      $warning = Key($addresses->warnings);
1918                                      if(!$this->SetPositionedWarning('Address extraction warning from header '.$header.' '.$addresses->warnings[$warning], $warning + $p[$v]))
1919                                          return(0);
1920                                  }
1921                              }
1922                              elseif(!$this->SetPositionedWarning('Address extraction error from header '.$header.' '.$addresses->error, $addresses->error_position + $p[$v]))
1923                                  return(0);
1924                          }
1925                      }
1926                  }
1927                  UnSet($decoded_message['HeaderPositions']);
1928              }
1929              $decoded[$message]=$decoded_message;
1930          }
1931          if(IsSet($parameters['File']))
1932              fclose($this->file);
1933          return($success);
1934      }
1935  /*
1936  {metadocument}
1937          </do>
1938      </function>
1939  {/metadocument}
1940  */
1941  
1942      Function CopyAddresses($message, &$results, $header)
1943      {
1944          if(!IsSet($message['Headers'][$header]))
1945              return;
1946          if(!IsSet($message['ExtractedAddresses'][$header]))
1947          {
1948              $parser = new rfc822_addresses_class;
1949              $parser->ignore_syntax_errors = $this->ignore_syntax_errors;
1950              $values = (GetType($message['Headers'][$header]) == 'array' ? $message['Headers'][$header] : array($message['Headers'][$header]));
1951              $tv = count($values);
1952              $addresses = array();
1953              for($v = 0; $v<$tv; ++$v)
1954              {
1955                  if($parser->ParseAddressList($values[$v], $a))
1956                  {
1957                      if($v==0)
1958                          $addresses = $a;
1959                      else
1960                      {
1961                          $tl = count($a);
1962                          for($l = 0; $l<$tl; ++$l)
1963                              $addresses[] = $a[$l];
1964                      }
1965                  }
1966              }
1967          }
1968          else
1969              $addresses = $message['ExtractedAddresses'][$header];
1970          if(count($addresses))
1971              $results[ucfirst(substr($header, 0, strlen($header) -1))] = $addresses;
1972      }
1973  
1974      Function ReadMessageBody($message, &$body, $prefix)
1975      {
1976          if(IsSet($message[$prefix]))
1977              $body = $message[$prefix];
1978          elseif(IsSet($message[$prefix.'File']))
1979          {
1980              $path = $message[$prefix.'File'];
1981              if(!($file = @fopen($path, 'rb')))
1982                  return($this->SetPHPError('could not open the message body file '.$path, $php_errormsg));
1983              for($body = '', $end = 0;!$end;)
1984              {
1985                  if(!($data = @fread($file, $this->message_buffer_length)))
1986                  {
1987                      $this->SetPHPError('Could not open the message body file '.$path, $php_errormsg);
1988                      fclose($stream);
1989                      return(0);
1990                  }
1991                  $end=feof($file);
1992                  $body.=$data;
1993              }
1994              fclose($file);
1995          }
1996          else
1997              $body = '';
1998          return(1);
1999      }
2000  /*
2001  {metadocument}
2002      <function>
2003          <name>Analyze</name>
2004          <type>BOOLEAN</type>
2005          <documentation>
2006              <purpose>Analyze a parsed message to describe its contents.</purpose>
2007              <usage>Pass an array to the <argumentlink>
2008                      <function>Analyze</function>
2009                      <argument>message</argument>
2010                  </argumentlink>
2011                  parameter with the decoded message array structure returned by the
2012                  <functionlink>Decode</functionlink> function. The <argumentlink>
2013                      <function>Analyze</function>
2014                      <argument>results</argument>
2015                  </argumentlink> returns details about the type of message that was
2016                  analyzed and its contents.</usage>
2017              <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
2018                  the specified message is analyzed successfully. Otherwise,
2019                  check the variables <variablelink>error</variablelink> and
2020                  <variablelink>error_position</variablelink> to determine what
2021                  error occurred.</returnvalue>
2022          </documentation>
2023          <argument>
2024              <name>message</name>
2025              <type>HASH</type>
2026              <documentation>
2027                  <purpose>Pass an associative array with the definition of an
2028                      individual message returned by the <argumentlink>
2029                      <function>Decode</function>
2030                      <argument>decoded</argument>
2031                  </argumentlink> argument of the
2032                  <functionlink>Decode</functionlink> function..</purpose>
2033              </documentation>
2034          </argument>
2035          <argument>
2036              <name>results</name>
2037              <type>HASH</type>
2038              <out />
2039              <documentation>
2040                  <purpose>Returns an associative array with the results of the
2041                      analysis. Some types of entries are returned for all types of
2042                      analyzed messages. Other entries are specific to each type of
2043                      message.<paragraphbreak />
2044                      <tt>Type</tt><paragraphbreak />
2045                      Type of message that was analyzed. Currently it supports the
2046                      types: <tt>binary</tt>, <tt>text</tt>, <tt>html</tt>,
2047                      <tt>video</tt>, <tt>image</tt>, <tt>audio</tt>, <tt>zip</tt>,
2048                      <tt>pdf</tt>, <tt>postscript</tt>, <tt>ms-word</tt>,
2049                      <tt>ms-excel</tt>, <tt>ms-powerpoint</tt>, <tt>ms-tnef</tt>,
2050                      <tt>odf-writer</tt>, <tt>signature</tt>, <tt>report-type</tt>,
2051                      <tt>delivery-status</tt> and <tt>message</tt>.<paragraphbreak />
2052                      <tt>SubType</tt><paragraphbreak />
2053                      Name of the variant of the message type format.<paragraphbreak />
2054                      <tt>Description</tt><paragraphbreak />
2055                      Human readable description in English of the message type.<paragraphbreak />
2056                      <paragraphbreak />
2057                      <paragraphbreak />
2058                      <paragraphbreak />
2059                      <b>From message headers:</b><paragraphbreak />
2060                      <tt>Encoding</tt><paragraphbreak />
2061                      Character set encoding of the message part.<paragraphbreak />
2062                      <tt>Subject</tt><paragraphbreak />
2063                      The message subject.<paragraphbreak />
2064                      <tt>SubjectEncoding</tt><paragraphbreak />
2065                      Character set encoding of the message subject.<paragraphbreak />
2066                      <tt>Date</tt><paragraphbreak />
2067                      The message date.<paragraphbreak />
2068                      <tt>From</tt><paragraphbreak />
2069                      <tt>To</tt><paragraphbreak />
2070                      <tt>Cc</tt><paragraphbreak />
2071                      <tt>Bcc</tt><paragraphbreak />
2072                      Array of e-mail addresses found in the <tt>From</tt>,
2073                      <tt>To</tt>, <tt>Cc</tt>, <tt>Bcc</tt>.<paragraphbreak />
2074                      Each of the entries consists of an associative array with an
2075                      entry named <tt>address</tt> with the e-mail address and
2076                      optionally another named <tt>name</tt> with the associated
2077                      name.<paragraphbreak />
2078                      <paragraphbreak />
2079                      <paragraphbreak />
2080                      <b>For content message parts:</b><paragraphbreak />
2081                      <paragraphbreak />
2082                      <tt>Data</tt><paragraphbreak />
2083                      String of data of the message part.<paragraphbreak />
2084                      <tt>DataFile</tt><paragraphbreak />
2085                      File with data of the message part.<paragraphbreak />
2086                      <tt>DataLength</tt><paragraphbreak />
2087                      Length of the data of the message part.<paragraphbreak />
2088                      <paragraphbreak />
2089                      <paragraphbreak />
2090                      <paragraphbreak />
2091                      <b>For message with embedded files:</b><paragraphbreak />
2092                      <paragraphbreak />
2093                      <tt>FileName</tt><paragraphbreak />
2094                      Original name of the file.<paragraphbreak />
2095                      <tt>ContentID</tt><paragraphbreak />
2096                      Content identifier of the file to be used in references from
2097                      other message parts.<paragraphbreak />
2098                      For instance, an HTML message may reference images embedded in
2099                      the message using URLs that start with the
2100                      <stringvalue>cid:</stringvalue> followed by the content
2101                      identifier of the embedded image file part.<paragraphbreak />
2102                      <tt>Disposition</tt><paragraphbreak />
2103                      Information of whether the embedded file should be displayed
2104                      inline when the message is presented, or it is an attachment
2105                      file.<paragraphbreak />
2106                      <paragraphbreak />
2107                      <paragraphbreak />
2108                      <b>For composite message:</b><paragraphbreak />
2109                      <paragraphbreak />
2110                      <tt>Attachments</tt><paragraphbreak />
2111                      List of files attached to the message.<paragraphbreak />
2112                      <tt>Alternative</tt><paragraphbreak />
2113                      List of alternative message parts that can be displayed if the
2114                      main message type is not supported by the program displaying
2115                      the message.<paragraphbreak />
2116                      <tt>Related</tt><paragraphbreak />
2117                      List of message parts related with the main message type.<paragraphbreak />
2118                      It may list for instance embedded images or CSS files related
2119                      with an HTML message type.<paragraphbreak />
2120                      <paragraphbreak />
2121                      <paragraphbreak />
2122                      <b>For bounced messages or other types of delivery status report
2123                      messages:</b><paragraphbreak />
2124                      <paragraphbreak />
2125                      <tt>Recipients</tt><paragraphbreak />
2126                      List of recipients of the original message.<paragraphbreak />
2127                      Each entry contains an associative array that may have the
2128                      entries: <tt>Recipient</tt> with the original recipient address,
2129                      <tt>Action</tt> with the name action that triggered the delivery
2130                      status report, <tt>Status</tt> with the code of the status of
2131                      the message delivery.<paragraphbreak />
2132                      <tt>Response</tt><paragraphbreak />
2133                      Human readable response sent by the server the originated the
2134                      report.<paragraphbreak />
2135                      </purpose>
2136              </documentation>
2137          </argument>
2138          <do>
2139  {/metadocument}
2140  */
2141      Function Analyze($message, &$results)
2142      {
2143          $results = array();
2144          if(!IsSet($message['Headers']['content-type:']))
2145              $content_type = 'text/plain';
2146          elseif(count($message['Headers']['content-type:']) == 1)
2147              $content_type = $message['Headers']['content-type:'];
2148          else
2149          {
2150              if(!$this->SetPositionedWarning('message contains multiple content-type headers', $message['Position']))
2151                  return(0);
2152              $content_type = $message['Headers']['content-type:'][0];
2153          }
2154          $disposition = $this->ParseParameters($content_type, $content_type, $parameters, 'disposition');
2155          $type = $this->Tokenize($content_type, '/');
2156          $sub_type = $this->Tokenize(';');
2157          $copy_body = 1;
2158          $tolerate_unrecognized = 1;
2159          switch($type)
2160          {
2161              case 'multipart':
2162                  $tolerate_unrecognized = 0;
2163                  $copy_body = 0;
2164                  $lp = count($message['Parts']);
2165                  if($lp == 0)
2166                      return($this->SetError($this->decode_bodies ? 'No parts were found in the '.$content_type.' part message' : 'It is not possible to analyze multipart messages without parsing the contained message parts. Please set the decode_bodies variable to 1 before parsing the message'));
2167                  $parts = array();
2168                  for($p = 0; $p < $lp; ++$p)
2169                  {
2170                      if(!$this->Analyze($message['Parts'][$p], $parts[$p]))
2171                          return(0);
2172                  }
2173                  switch($sub_type)
2174                  {
2175                      case 'alternative':
2176                          $p = $lp;
2177                          $results = $parts[--$p];
2178                          for(--$p ; $p >=0 ; --$p)
2179                              $results['Alternative'][] = $parts[$p];
2180                          break;
2181  
2182                      case 'related':
2183                          $results = $parts[0];
2184                          for($p = 1; $p < $lp; ++$p)
2185                              $results['Related'][] = $parts[$p];
2186                          break;
2187  
2188                      case 'mixed':
2189                          $results = $parts[0];
2190                          for($p = 1; $p < $lp; ++$p)
2191                              $results['Attachments'][] = $parts[$p];
2192                          break;
2193  
2194                      case 'report':
2195                          if(IsSet($parameters['report-type']))
2196                          {
2197                              switch($parameters['report-type'])
2198                              {
2199                                  case 'delivery-status':
2200                                      for($p = 1; $p < $lp; ++$p)
2201                                      {
2202                                          if(!strcmp($parts[$p]['Type'], $parameters['report-type']))
2203                                          {
2204                                              $results = $parts[$p];
2205                                              break;
2206                                          }
2207                                      }
2208                                      if(!$this->ReadMessageBody($parts[0], $body, 'Data'))
2209                                          return(0);
2210                                      if(strlen($body))
2211                                          $results['Response'] = $body;
2212                                      break;
2213                              }
2214                          }
2215                          $results['Type'] = $parameters['report-type'];
2216                          break;
2217  
2218                      case 'signed':
2219                          if($lp != 2)
2220                              return($this->SetError('this '.$content_type.' message does not have just 2 parts'));
2221                          if(strcmp($parts[1]['Type'], 'signature'))
2222                          {
2223                              $this->SetErrorWithContact('this '.$content_type.' message does not contain a signature');
2224                              $this->error = '';
2225                          }
2226                          $results = $parts[0];
2227                          $results['Signature'] = $parts[1];
2228                          break;
2229  
2230                      case 'appledouble':
2231                          if($lp != 2)
2232                              return($this->SetError('this '.$content_type.' message does not have just 2 parts'));
2233                          if(strcmp($parts[0]['Type'], 'applefile'))
2234                          {
2235                              $this->SetErrorWithContact('this '.$content_type.' message does not contain an Apple file header');
2236                              $this->error = '';
2237                          }
2238                          $results = $parts[1];
2239                          $results['AppleFileHeader'] = $parts[0];
2240                          break;
2241  
2242                      case 'form-data':
2243                          $results['Type'] = 'form-data';
2244                          $results['FormData'] = array();
2245                          for($p = 0; $p < $lp; ++$p)
2246                          {
2247                              if(!IsSet($message['Parts'][$p]['Headers']['content-disposition:']))
2248                                  return($this->SetError('the form data part '.$p.' is missing the content-disposition header'));
2249                              $disposition = $message['Parts'][$p]['Headers']['content-disposition:'];
2250                              $name = $this->ParseParameters($disposition, $disposition, $parameters, 'name');
2251                              if(strcmp($disposition, 'form-data'))
2252                              {
2253                                  if(!$this->SetPositionedWarning('disposition of part '.$p.' is not form-data', $message['Parts'][$p]['Position']))
2254                                      return(0);
2255                                  continue;
2256                              }
2257                              $results['FormData'][$name] = $parts[$p];
2258                          }
2259                          break;
2260                  }
2261                  break;
2262              case 'text':
2263                  switch($sub_type)
2264                  {
2265                      case 'plain':
2266                          $results['Type'] = 'text';
2267                          $results['Description'] = 'Text message';
2268                          break;
2269                      case 'html':
2270                          $results['Type'] = 'html';
2271                          $results['Description'] = 'HTML message';
2272                          break;
2273                      case 'rtf':
2274                          $results['Type'] = 'rtf';
2275                          $results['Description'] = 'Document in Rich Text Format';
2276                          break;
2277                      default:
2278                          $results['Type'] = $type;
2279                          $results['SubType'] = $sub_type;
2280                          $results['Description'] = 'Text file in the '.strtoupper($sub_type).' format';
2281                          break;
2282                  }
2283                  break;
2284              case 'video':
2285                  $results['Type'] = $type;
2286                  $results['SubType'] = $sub_type;
2287                  $results['Description'] = 'Video file in the '.strtoupper($sub_type).' format';
2288                  break;
2289              case 'image':
2290                  $results['Type'] = $type;
2291                  $results['SubType'] = $sub_type;
2292                  $results['Description'] = 'Image file in the '.strtoupper($sub_type).' format';
2293                  break;
2294              case 'audio':
2295                  $results['Type'] = $type;
2296                  $results['SubType'] = $sub_type;
2297                  $results['Description'] = 'Audio file in the '.strtoupper($sub_type).' format';
2298                  break;
2299              case 'application':
2300                  switch($sub_type)
2301                  {
2302                      case 'octet-stream':
2303                      case 'x-msdownload':
2304                          $results['Type'] = 'binary';
2305                          $results['Description'] = 'Binary file';
2306                          break;
2307                      case 'pdf':
2308                          $results['Type'] = $sub_type;
2309                          $results['Description'] = 'Document in PDF format';
2310                          break;
2311                      case 'postscript':
2312                          $results['Type'] = $sub_type;
2313                          $results['Description'] = 'Document in Postscript format';
2314                          break;
2315                      case 'msword':
2316                          $results['Type'] = 'ms-word';
2317                          $results['Description'] = 'Word processing document in Microsoft Word format';
2318                          break;
2319                      case 'vnd.ms-powerpoint':
2320                          $results['Type'] = 'ms-powerpoint';
2321                          $results['Description'] = 'Presentation in Microsoft PowerPoint format';
2322                          break;
2323                      case 'vnd.ms-excel':
2324                          $results['Type'] = 'ms-excel';
2325                          $results['Description'] = 'Spreadsheet in Microsoft Excel format';
2326                          break;
2327                      case 'x-compressed':
2328                          if(!IsSet($parameters['name'])
2329                          || GetType($dot = strpos($parameters['name'], '.'))!='integer'
2330                          || strcmp($extension = strtolower(substr($parameters['name'], $dot + 1)), 'zip'))
2331                              break;
2332                      case 'zip':
2333                      case 'x-zip':
2334                      case 'x-zip-compressed':
2335                          $results['Type'] = 'zip';
2336                          $results['Description'] = 'ZIP archive with compressed files';
2337                          break;
2338                      case 'ms-tnef':
2339                          $results['Type'] = $sub_type;
2340                          $results['Description'] = 'Microsoft Exchange data usually sent by Microsoft Outlook';
2341                          break;
2342                      case 'pgp-signature':
2343                          $results['Type'] = 'signature';
2344                          $results['SubType'] = $sub_type;
2345                          $results['Description'] = 'Message signature for PGP';
2346                          break;
2347                      case 'x-pkcs7-signature':
2348                      case 'pkcs7-signature':
2349                          $results['Type'] = 'signature';
2350                          $results['SubType'] = $sub_type;
2351                          $results['Description'] = 'PKCS message signature';
2352                          break;
2353                      case 'vnd.oasis.opendocument.text':
2354                          $results['Type'] = 'odf-writer';
2355                          $results['Description'] = 'Word processing document in ODF text format used by OpenOffice Writer';
2356                          break;
2357                      case 'applefile':
2358                          $results['Type'] = 'applefile';
2359                          $results['Description'] = 'Apple file resource header';
2360                          break;
2361                      case 'rtf':
2362                          $results['Type'] = $sub_type;
2363                          $results['Description'] = 'Document in Rich Text Format';
2364                          break;
2365                      case 'x-httpd-php':
2366                          $results['Type'] = 'php';
2367                          $results['Description'] = 'PHP script';
2368                          break;
2369                  }
2370                  break;
2371              case 'message':
2372                  $tolerate_unrecognized = 0;
2373                  switch($sub_type)
2374                  {
2375                      case 'delivery-status':
2376                          $results['Type'] = $sub_type;
2377                          $results['Description'] = 'Notification of the status of delivery of a message';
2378                          if(!$this->ReadMessageBody($message, $body, 'Body'))
2379                              return(0);
2380                          if(($l = strlen($body)))
2381                          {
2382                              $position = 0;
2383                              $this->ParseHeaderString($body, $position, $headers);
2384                              $recipients = array();
2385                              for(;$position<$l;)
2386                              {
2387                                  $this->ParseHeaderString($body, $position, $headers);
2388                                  if(count($headers))
2389                                  {
2390                                      $r = count($recipients);
2391                                      if(IsSet($headers['action']))
2392                                          $recipients[$r]['Action'] = $headers['action'];
2393                                      if(IsSet($headers['status']))
2394                                          $recipients[$r]['Status'] = $headers['status'];
2395                                      if(IsSet($headers['original-recipient']))
2396                                      {
2397                                          strtok($headers['original-recipient'], ';');
2398                                          $recipients[$r]['Address'] = trim(strtok(''));
2399                                      }
2400                                      elseif(IsSet($headers['final-recipient']))
2401                                      {
2402                                          strtok($headers['final-recipient'], ';');
2403                                          $recipients[$r]['Address'] = trim(strtok(''));
2404                                      }
2405                                  }
2406                              }
2407                              $results['Recipients'] = $recipients;
2408                          }
2409                          $copy_body = 0;
2410                          break;
2411                      case 'rfc822':
2412                          $results['Type'] = 'message';
2413                          $results['Description'] = 'E-mail message';
2414                          break;
2415                  }
2416                  break;
2417              default:
2418                  $tolerate_unrecognized = 0;
2419                  break;
2420          }
2421          if(!IsSet($results['Type']))
2422          {
2423              $this->SetErrorWithContact($content_type.' message parts are not yet recognized');
2424              $results['Type'] = $this->error;
2425              $this->error = '';
2426          }
2427          if(IsSet($parameters['charset']))
2428              $results['Encoding'] = strtolower($parameters['charset']);
2429          if(IsSet($message['Headers']['subject:']))
2430          {
2431              if(IsSet($message['DecodedHeaders']['subject:'])
2432              && count($message['DecodedHeaders']['subject:']) == 1
2433              && count($message['DecodedHeaders']['subject:'][0]) == 1)
2434              {
2435                  $results['Subject'] = $message['DecodedHeaders']['subject:'][0][0]['Value'];
2436                  $results['SubjectEncoding'] = strtolower($message['DecodedHeaders']['subject:'][0][0]['Encoding']);
2437              }
2438              else
2439                  $results['Subject'] = $message['Headers']['subject:'];
2440          }
2441          if(IsSet($message['Headers']['date:']))
2442          {
2443              if(IsSet($message['DecodedHeaders']['date:'])
2444              && count($message['DecodedHeaders']['date:']) == 1
2445              && count($message['DecodedHeaders']['date:'][0]) == 1)
2446                  $results['Date'] = $message['DecodedHeaders']['date:'][0][0]['Value'];
2447              else
2448                  $results['Date'] = $message['Headers']['date:'];
2449          }
2450          $l = count($this->address_headers);
2451          for(Reset($this->address_headers), $h = 0; $h<$l; Next($this->address_headers), ++$h)
2452              $this->CopyAddresses($message, $results, Key($this->address_headers));
2453          if($copy_body)
2454          {
2455              if(IsSet($message['Body']))
2456                  $results['Data'] = $message['Body'];
2457              elseif(IsSet($message['BodyFile']))
2458                  $results['DataFile'] = $message['BodyFile'];
2459              elseif(IsSet($message['BodyLength']))
2460                  $results['DataLength'] = $message['BodyLength'];
2461              if(IsSet($message['FileName']))
2462                  $results['FileName'] = $message['FileName'];
2463              if(IsSet($message['FileDisposition']))
2464                  $results['FileDisposition'] = $message['FileDisposition'];
2465              if(IsSet($message['Headers']['content-id:']))
2466              {
2467                  $content_id = trim($message['Headers']['content-id:']);
2468                  $l = strlen($content_id);
2469                  if(!strcmp($content_id[0], '<')
2470                  && !strcmp($content_id[$l - 1], '>'))
2471                      $results['ContentID'] = substr($content_id, 1, $l - 2);
2472              }
2473          }
2474          return(1);
2475      }
2476  /*
2477  {metadocument}
2478          </do>
2479      </function>
2480  {/metadocument}
2481  */
2482  
2483  /*
2484  {metadocument}
2485      <function>
2486          <name>GetPositionLine</name>
2487          <type>BOOLEAN</type>
2488          <documentation>
2489              <purpose>Get the line number of the document that corresponds to a
2490                  given position.</purpose>
2491              <usage>Pass the document offset number as the position to be
2492                  located. Make sure the <variablelink>track_lines</variablelink>
2493                  variable is set to <booleanvalue>1</booleanvalue> before parsing
2494                  the document.</usage>
2495              <returnvalue>This function returns <booleanvalue>1</booleanvalue> if
2496                   the <variablelink>track_lines</variablelink> variable is set to
2497                  <booleanvalue>1</booleanvalue> and it was given a valid positive
2498                  position number that does not exceed the position of the last
2499                  parsed document line.</returnvalue>
2500          </documentation>
2501          <argument>
2502              <name>position</name>
2503              <type>INTEGER</type>
2504              <documentation>
2505                  <purpose>Position of the line to be located.</purpose>
2506              </documentation>
2507          </argument>
2508          <argument>
2509              <name>line</name>
2510              <type>INTEGER</type>
2511              <out />
2512              <documentation>
2513                  <purpose>Returns the number of the line that corresponds to the
2514                      given document position.</purpose>
2515              </documentation>
2516          </argument>
2517          <argument>
2518              <name>column</name>
2519              <type>INTEGER</type>
2520              <out />
2521              <documentation>
2522                  <purpose>Returns the number of the column of the line that
2523                      corresponds to the given document position.</purpose>
2524              </documentation>
2525          </argument>
2526          <do>
2527  {/metadocument}
2528  */
2529      Function GetPositionLine($position, &$line, &$column)
2530      {
2531          if(!$this->track_lines)
2532              return($this->SetPositionedError('line positions are not being tracked', $position));
2533          $bottom = 0;
2534          $top = count($this->lines) - 1;
2535          if($position < 0)
2536              return($this->SetPositionedError('it was not specified a valid position', $position));
2537          for(;;)
2538          {
2539              $line = intval(($bottom + $top) / 2);
2540              $current = $this->lines[$line];
2541              if($current < $position)
2542                  $bottom = $line + 1;
2543              elseif($current > $position)
2544                  $top = $line - 1;
2545              else
2546                  break;
2547              if($top < $bottom)
2548              {
2549                  $line = $top;
2550                  break;
2551              }
2552          }
2553          $column = $position - $this->lines[$line] + 1;
2554          ++$line;
2555          return(1);
2556      }
2557  /*
2558  {metadocument}
2559          </do>
2560      </function>
2561  {/metadocument}
2562  */
2563  };
2564  
2565  /*
2566  
2567  {metadocument}
2568  </class>
2569  {/metadocument}
2570  
2571  */
2572  
2573  ?>

title

Description

title

Description

title

Description

title

title

Body