vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php line 199

Open in your IDE?
  1. <?php
  2. namespace PhpMimeMailParser;
  3. use PhpMimeMailParser\Contracts\CharsetManager;
  4. /**
  5.  * Parser of php-mime-mail-parser
  6.  *
  7.  * Fully Tested Mailparse Extension Wrapper for PHP 5.4+
  8.  *
  9.  */
  10. class Parser
  11. {
  12.     /**
  13.      * Attachment filename argument option for ->saveAttachments().
  14.      */
  15.     const ATTACHMENT_DUPLICATE_THROW  'DuplicateThrow';
  16.     const ATTACHMENT_DUPLICATE_SUFFIX 'DuplicateSuffix';
  17.     const ATTACHMENT_RANDOM_FILENAME  'RandomFilename';
  18.     /**
  19.      * PHP MimeParser Resource ID
  20.      *
  21.      * @var resource $resource
  22.      */
  23.     protected $resource;
  24.     /**
  25.      * A file pointer to email
  26.      *
  27.      * @var resource $stream
  28.      */
  29.     protected $stream;
  30.     /**
  31.      * A text of an email
  32.      *
  33.      * @var string $data
  34.      */
  35.     protected $data;
  36.     /**
  37.      * Parts of an email
  38.      *
  39.      * @var array $parts
  40.      */
  41.     protected $parts;
  42.     /**
  43.      * @var CharsetManager object
  44.      */
  45.     protected $charset;
  46.     /**
  47.      * Valid stream modes for reading
  48.      *
  49.      * @var array
  50.      */
  51.     protected static $readableModes = [
  52.         'r''r+''w+''a+''x+''c+''rb''r+b''w+b''a+b',
  53.         'x+b''c+b''rt''r+t''w+t''a+t''x+t''c+t'
  54.     ];
  55.     /**
  56.      * Stack of middleware registered to process data
  57.      *
  58.      * @var MiddlewareStack
  59.      */
  60.     protected $middlewareStack;
  61.     /**
  62.      * Parser constructor.
  63.      *
  64.      * @param CharsetManager|null $charset
  65.      */
  66.     public function __construct(CharsetManager $charset null)
  67.     {
  68.         if ($charset == null) {
  69.             $charset = new Charset();
  70.         }
  71.         $this->charset $charset;
  72.         $this->middlewareStack = new MiddlewareStack();
  73.     }
  74.     /**
  75.      * Free the held resources
  76.      *
  77.      * @return void
  78.      */
  79.     public function __destruct()
  80.     {
  81.         // clear the email file resource
  82.         if (is_resource($this->stream)) {
  83.             fclose($this->stream);
  84.         }
  85.         // clear the MailParse resource
  86.         if (is_resource($this->resource)) {
  87.             mailparse_msg_free($this->resource);
  88.         }
  89.     }
  90.     /**
  91.      * Set the file path we use to get the email text
  92.      *
  93.      * @param string $path File path to the MIME mail
  94.      *
  95.      * @return Parser MimeMailParser Instance
  96.      */
  97.     public function setPath($path)
  98.     {
  99.         if (is_writable($path)) {
  100.             $file fopen($path'a+');
  101.             fseek($file, -1SEEK_END);
  102.             if (fread($file1) != "\n") {
  103.                 fwrite($filePHP_EOL);
  104.             }
  105.             fclose($file);
  106.         }
  107.         // should parse message incrementally from file
  108.         $this->resource mailparse_msg_parse_file($path);
  109.         $this->stream fopen($path'r');
  110.         $this->parse();
  111.         return $this;
  112.     }
  113.     /**
  114.      * Set the Stream resource we use to get the email text
  115.      *
  116.      * @param resource $stream
  117.      *
  118.      * @return Parser MimeMailParser Instance
  119.      * @throws Exception
  120.      */
  121.     public function setStream($stream)
  122.     {
  123.         // streams have to be cached to file first
  124.         $meta = @stream_get_meta_data($stream);
  125.         if (!$meta || !$meta['mode'] || !in_array($meta['mode'], self::$readableModestrue) || $meta['eof']) {
  126.             throw new Exception(
  127.                 'setStream() expects parameter stream to be readable stream resource.'
  128.             );
  129.         }
  130.         /** @var resource $tmp_fp */
  131.         $tmp_fp tmpfile();
  132.         if ($tmp_fp) {
  133.             while (!feof($stream)) {
  134.                 fwrite($tmp_fpfread($stream2028));
  135.             }
  136.             if (fread($tmp_fp1) != "\n") {
  137.                 fwrite($tmp_fpPHP_EOL);
  138.             }
  139.             fseek($tmp_fp0);
  140.             $this->stream = &$tmp_fp;
  141.         } else {
  142.             throw new Exception(
  143.                 'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
  144.             );
  145.         }
  146.         fclose($stream);
  147.         $this->resource mailparse_msg_create();
  148.         // parses the message incrementally (low memory usage but slower)
  149.         while (!feof($this->stream)) {
  150.             mailparse_msg_parse($this->resourcefread($this->stream2082));
  151.         }
  152.         $this->parse();
  153.         return $this;
  154.     }
  155.     /**
  156.      * Set the email text
  157.      *
  158.      * @param string $data
  159.      *
  160.      * @return Parser MimeMailParser Instance
  161.      */
  162.     public function setText($data)
  163.     {
  164.         if (empty($data)) {
  165.             throw new Exception('You must not call MimeMailParser::setText with an empty string parameter');
  166.         }
  167.         if (substr($data, -1) != "\n") {
  168.             $data $data.PHP_EOL;
  169.         }
  170.         $this->resource mailparse_msg_create();
  171.         // does not parse incrementally, fast memory hog might explode
  172.         mailparse_msg_parse($this->resource$data);
  173.         $this->data $data;
  174.         $this->parse();
  175.         return $this;
  176.     }
  177.     /**
  178.      * Parse the Message into parts
  179.      *
  180.      * @return void
  181.      */
  182.     protected function parse()
  183.     {
  184.         $structure mailparse_msg_get_structure($this->resource);
  185.         $this->parts = [];
  186.         foreach ($structure as $part_id) {
  187.             $part mailparse_msg_get_part($this->resource$part_id);
  188.             $part_data mailparse_msg_get_part_data($part);
  189.             $mimePart = new MimePart($part_id$part_data);
  190.             // let each middleware parse the part before saving
  191.             $this->parts[$part_id] = $this->middlewareStack->parse($mimePart)->getPart();
  192.         }
  193.     }
  194.     /**
  195.      * Retrieve a specific Email Header, without charset conversion.
  196.      *
  197.      * @param string $name Header name (case-insensitive)
  198.      *
  199.      * @return string|bool
  200.      * @throws Exception
  201.      */
  202.     public function getRawHeader($name)
  203.     {
  204.         $name strtolower($name);
  205.         if (isset($this->parts[1])) {
  206.             $headers $this->getPart('headers'$this->parts[1]);
  207.             return isset($headers[$name]) ? $headers[$name] : false;
  208.         } else {
  209.             throw new Exception(
  210.                 'setPath() or setText() or setStream() must be called before retrieving email headers.'
  211.             );
  212.         }
  213.     }
  214.     /**
  215.      * Retrieve a specific Email Header
  216.      *
  217.      * @param string $name Header name (case-insensitive)
  218.      *
  219.      * @return string|bool
  220.      */
  221.     public function getHeader($name)
  222.     {
  223.         $rawHeader $this->getRawHeader($name);
  224.         if ($rawHeader === false) {
  225.             return false;
  226.         }
  227.         return $this->decodeHeader($rawHeader);
  228.     }
  229.     /**
  230.      * Retrieve all mail headers
  231.      *
  232.      * @return array
  233.      * @throws Exception
  234.      */
  235.     public function getHeaders()
  236.     {
  237.         if (isset($this->parts[1])) {
  238.             $headers $this->getPart('headers'$this->parts[1]);
  239.             foreach ($headers as &$value) {
  240.                 if (is_array($value)) {
  241.                     foreach ($value as &$v) {
  242.                         $v $this->decodeSingleHeader($v);
  243.                     }
  244.                 } else {
  245.                     $value $this->decodeSingleHeader($value);
  246.                 }
  247.             }
  248.             return $headers;
  249.         } else {
  250.             throw new Exception(
  251.                 'setPath() or setText() or setStream() must be called before retrieving email headers.'
  252.             );
  253.         }
  254.     }
  255.     /**
  256.      * Retrieve the raw mail headers as a string
  257.      *
  258.      * @return string
  259.      * @throws Exception
  260.      */
  261.     public function getHeadersRaw()
  262.     {
  263.         if (isset($this->parts[1])) {
  264.             return $this->getPartHeader($this->parts[1]);
  265.         } else {
  266.             throw new Exception(
  267.                 'setPath() or setText() or setStream() must be called before retrieving email headers.'
  268.             );
  269.         }
  270.     }
  271.     /**
  272.      * Retrieve the raw Header of a MIME part
  273.      *
  274.      * @return String
  275.      * @param $part Object
  276.      * @throws Exception
  277.      */
  278.     protected function getPartHeader(&$part)
  279.     {
  280.         $header '';
  281.         if ($this->stream) {
  282.             $header $this->getPartHeaderFromFile($part);
  283.         } elseif ($this->data) {
  284.             $header $this->getPartHeaderFromText($part);
  285.         }
  286.         return $header;
  287.     }
  288.     /**
  289.      * Retrieve the Header from a MIME part from file
  290.      *
  291.      * @return String Mime Header Part
  292.      * @param $part Array
  293.      */
  294.     protected function getPartHeaderFromFile(&$part)
  295.     {
  296.         $start $part['starting-pos'];
  297.         $end $part['starting-pos-body'];
  298.         fseek($this->stream$startSEEK_SET);
  299.         $header fread($this->stream$end $start);
  300.         return $header;
  301.     }
  302.     /**
  303.      * Retrieve the Header from a MIME part from text
  304.      *
  305.      * @return String Mime Header Part
  306.      * @param $part Array
  307.      */
  308.     protected function getPartHeaderFromText(&$part)
  309.     {
  310.         $start $part['starting-pos'];
  311.         $end $part['starting-pos-body'];
  312.         $header substr($this->data$start$end $start);
  313.         return $header;
  314.     }
  315.     /**
  316.      * Checks whether a given part ID is a child of another part
  317.      * eg. an RFC822 attachment may have one or more text parts
  318.      *
  319.      * @param string $partId
  320.      * @param string $parentPartId
  321.      * @return bool
  322.      */
  323.     protected function partIdIsChildOfPart($partId$parentPartId)
  324.     {
  325.         $parentPartId $parentPartId.'.';
  326.         return substr($partId0strlen($parentPartId)) == $parentPartId;
  327.     }
  328.     /**
  329.      * Whether the given part ID is a child of any attachment part in the message.
  330.      *
  331.      * @param string $checkPartId
  332.      * @return bool
  333.      */
  334.     protected function partIdIsChildOfAnAttachment($checkPartId)
  335.     {
  336.         foreach ($this->parts as $partId => $part) {
  337.             if ($this->getPart('content-disposition'$part) == 'attachment') {
  338.                 if ($this->partIdIsChildOfPart($checkPartId$partId)) {
  339.                     return true;
  340.                 }
  341.             }
  342.         }
  343.         return false;
  344.     }
  345.     /**
  346.      * Returns the email message body in the specified format
  347.      *
  348.      * @param string $type text, html or htmlEmbedded
  349.      *
  350.      * @return string Body
  351.      * @throws Exception
  352.      */
  353.     public function getMessageBody($type 'text')
  354.     {
  355.         $mime_types = [
  356.             'text'         => 'text/plain',
  357.             'html'         => 'text/html',
  358.             'htmlEmbedded' => 'text/html',
  359.         ];
  360.         if (in_array($typearray_keys($mime_types))) {
  361.             $part_type $type === 'htmlEmbedded' 'html' $type;
  362.             $inline_parts $this->getInlineParts($part_type);
  363.             $body = empty($inline_parts) ? '' $inline_parts[0];
  364.         } else {
  365.             throw new Exception(
  366.                 'Invalid type specified for getMessageBody(). Expected: text, html or htmlEmbeded.'
  367.             );
  368.         }
  369.         if ($type == 'htmlEmbedded') {
  370.             $attachments $this->getAttachments();
  371.             foreach ($attachments as $attachment) {
  372.                 if ($attachment->getContentID() != '') {
  373.                     $body str_replace(
  374.                         '"cid:'.$attachment->getContentID().'"',
  375.                         '"'.$this->getEmbeddedData($attachment->getContentID()).'"',
  376.                         $body
  377.                     );
  378.                 }
  379.             }
  380.         }
  381.         return $body;
  382.     }
  383.     /**
  384.      * Returns the embedded data structure
  385.      *
  386.      * @param string $contentId Content-Id
  387.      *
  388.      * @return string
  389.      */
  390.     protected function getEmbeddedData($contentId)
  391.     {
  392.         foreach ($this->parts as $part) {
  393.             if ($this->getPart('content-id'$part) == $contentId) {
  394.                 $embeddedData 'data:';
  395.                 $embeddedData .= $this->getPart('content-type'$part);
  396.                 $embeddedData .= ';'.$this->getPart('transfer-encoding'$part);
  397.                 $embeddedData .= ','.$this->getPartBody($part);
  398.                 return $embeddedData;
  399.             }
  400.         }
  401.         return '';
  402.     }
  403.     /**
  404.      * Return an array with the following keys display, address, is_group
  405.      *
  406.      * @param string $name Header name (case-insensitive)
  407.      *
  408.      * @return array
  409.      */
  410.     public function getAddresses($name)
  411.     {
  412.         $value $this->getRawHeader($name);
  413.         $value = (is_array($value)) ? $value[0] : $value;
  414.         $addresses mailparse_rfc822_parse_addresses($value);
  415.         foreach ($addresses as $i => $item) {
  416.             $addresses[$i]['display'] = $this->decodeHeader($item['display']);
  417.         }
  418.         return $addresses;
  419.     }
  420.     /**
  421.      * Returns the attachments contents in order of appearance
  422.      *
  423.      * @return Attachment[]
  424.      */
  425.     public function getInlineParts($type 'text')
  426.     {
  427.         $inline_parts = [];
  428.         $mime_types = [
  429.             'text'         => 'text/plain',
  430.             'html'         => 'text/html',
  431.         ];
  432.         if (!in_array($typearray_keys($mime_types))) {
  433.             throw new Exception('Invalid type specified for getInlineParts(). "type" can either be text or html.');
  434.         }
  435.         foreach ($this->parts as $partId => $part) {
  436.             if ($this->getPart('content-type'$part) == $mime_types[$type]
  437.                 && $this->getPart('content-disposition'$part) != 'attachment'
  438.                 && !$this->partIdIsChildOfAnAttachment($partId)
  439.             ) {
  440.                 $headers $this->getPart('headers'$part);
  441.                 $encodingType array_key_exists('content-transfer-encoding'$headers) ?
  442.                     $headers['content-transfer-encoding'] : '';
  443.                 $undecoded_body $this->decodeContentTransfer($this->getPartBody($part), $encodingType);
  444.                 $inline_parts[] = $this->charset->decodeCharset($undecoded_body$this->getPartCharset($part));
  445.             }
  446.         }
  447.         return $inline_parts;
  448.     }
  449.     /**
  450.      * Returns the attachments contents in order of appearance
  451.      *
  452.      * @return Attachment[]
  453.      */
  454.     public function getAttachments($include_inline true)
  455.     {
  456.         $attachments = [];
  457.         $dispositions $include_inline ? ['attachment''inline'] : ['attachment'];
  458.         $non_attachment_types = ['text/plain''text/html'];
  459.         $nonameIter 0;
  460.         foreach ($this->parts as $part) {
  461.             $disposition $this->getPart('content-disposition'$part);
  462.             $filename 'noname';
  463.             if (isset($part['disposition-filename'])) {
  464.                 $filename $this->decodeHeader($part['disposition-filename']);
  465.             } elseif (isset($part['content-name'])) {
  466.                 // if we have no disposition but we have a content-name, it's a valid attachment.
  467.                 // we simulate the presence of an attachment disposition with a disposition filename
  468.                 $filename $this->decodeHeader($part['content-name']);
  469.                 $disposition 'attachment';
  470.             } elseif (in_array($part['content-type'], $non_attachment_typestrue)
  471.                 && $disposition !== 'attachment') {
  472.                 // it is a message body, no attachment
  473.                 continue;
  474.             } elseif (substr($part['content-type'], 010) !== 'multipart/'
  475.                 && $part['content-type'] !== 'text/plain; (error)') {
  476.                 // if we cannot get it by getMessageBody(), we assume it is an attachment
  477.                 $disposition 'attachment';
  478.             }
  479.             if (in_array($disposition, ['attachment''inline']) === false && !empty($disposition)) {
  480.                 $disposition 'attachment';
  481.             }
  482.             if (in_array($disposition$dispositions) === true) {
  483.                 if ($filename == 'noname') {
  484.                     $nonameIter++;
  485.                     $filename 'noname'.$nonameIter;
  486.                 } else {
  487.                     // Escape all potentially unsafe characters from the filename
  488.                     $filename preg_replace('((^\.)|\/|[\n|\r|\n\r]|(\.$))''_'$filename);
  489.                 }
  490.                 $headersAttachments $this->getPart('headers'$part);
  491.                 $contentidAttachments $this->getPart('content-id'$part);
  492.                 $attachmentStream $this->getAttachmentStream($part);
  493.                 $mimePartStr $this->getPartComplete($part);
  494.                 $attachments[] = new Attachment(
  495.                     $filename,
  496.                     $this->getPart('content-type'$part),
  497.                     $attachmentStream,
  498.                     $disposition,
  499.                     $contentidAttachments,
  500.                     $headersAttachments,
  501.                     $mimePartStr
  502.                 );
  503.             }
  504.         }
  505.         return $attachments;
  506.     }
  507.     /**
  508.      * Save attachments in a folder
  509.      *
  510.      * @param string $attach_dir directory
  511.      * @param bool $include_inline
  512.      * @param string $filenameStrategy How to generate attachment filenames
  513.      *
  514.      * @return array Saved attachments paths
  515.      * @throws Exception
  516.      */
  517.     public function saveAttachments(
  518.         $attach_dir,
  519.         $include_inline true,
  520.         $filenameStrategy self::ATTACHMENT_DUPLICATE_SUFFIX
  521.     ) {
  522.         $attachments $this->getAttachments($include_inline);
  523.         $attachments_paths = [];
  524.         foreach ($attachments as $attachment) {
  525.             $attachments_paths[] = $attachment->save($attach_dir$filenameStrategy);
  526.         }
  527.         return $attachments_paths;
  528.     }
  529.     /**
  530.      * Read the attachment Body and save temporary file resource
  531.      *
  532.      * @param array $part
  533.      *
  534.      * @return resource Mime Body Part
  535.      * @throws Exception
  536.      */
  537.     protected function getAttachmentStream(&$part)
  538.     {
  539.         /** @var resource $temp_fp */
  540.         $temp_fp tmpfile();
  541.         $headers $this->getPart('headers'$part);
  542.         $encodingType array_key_exists('content-transfer-encoding'$headers) ?
  543.             $headers['content-transfer-encoding'] : '';
  544.         if ($temp_fp) {
  545.             if ($this->stream) {
  546.                 $start $part['starting-pos-body'];
  547.                 $end $part['ending-pos-body'];
  548.                 fseek($this->stream$startSEEK_SET);
  549.                 $len $end $start;
  550.                 $written 0;
  551.                 while ($written $len) {
  552.                     $write $len;
  553.                     $data fread($this->stream$write);
  554.                     fwrite($temp_fp$this->decodeContentTransfer($data$encodingType));
  555.                     $written += $write;
  556.                 }
  557.             } elseif ($this->data) {
  558.                 $attachment $this->decodeContentTransfer($this->getPartBodyFromText($part), $encodingType);
  559.                 fwrite($temp_fp$attachmentstrlen($attachment));
  560.             }
  561.             fseek($temp_fp0SEEK_SET);
  562.         } else {
  563.             throw new Exception(
  564.                 'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
  565.             );
  566.         }
  567.         return $temp_fp;
  568.     }
  569.     /**
  570.      * Decode the string from Content-Transfer-Encoding
  571.      *
  572.      * @param string $encodedString The string in its original encoded state
  573.      * @param string $encodingType  The encoding type from the Content-Transfer-Encoding header of the part.
  574.      *
  575.      * @return string The decoded string
  576.      */
  577.     protected function decodeContentTransfer($encodedString$encodingType)
  578.     {
  579.         if (is_array($encodingType)) {
  580.             $encodingType $encodingType[0];
  581.         }
  582.         $encodingType strtolower($encodingType);
  583.         if ($encodingType == 'base64') {
  584.             return base64_decode($encodedString);
  585.         } elseif ($encodingType == 'quoted-printable') {
  586.             return quoted_printable_decode($encodedString);
  587.         } else {
  588.             return $encodedString;
  589.         }
  590.     }
  591.     /**
  592.      * $input can be a string or array
  593.      *
  594.      * @param string|array $input
  595.      *
  596.      * @return string
  597.      */
  598.     protected function decodeHeader($input)
  599.     {
  600.         //Sometimes we have 2 label From so we take only the first
  601.         if (is_array($input)) {
  602.             return $this->decodeSingleHeader($input[0]);
  603.         }
  604.         return $this->decodeSingleHeader($input);
  605.     }
  606.     /**
  607.      * Decodes a single header (= string)
  608.      *
  609.      * @param string $input
  610.      *
  611.      * @return string
  612.      */
  613.     protected function decodeSingleHeader($input)
  614.     {
  615.         // For each encoded-word...
  616.         while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)((\s+)=\?)?/i'$input$matches)) {
  617.             $encoded $matches[1];
  618.             $charset $matches[2];
  619.             $encoding $matches[3];
  620.             $text $matches[4];
  621.             $space = isset($matches[6]) ? $matches[6] : '';
  622.             switch (strtolower($encoding)) {
  623.                 case 'b':
  624.                     $text $this->decodeContentTransfer($text'base64');
  625.                     break;
  626.                 case 'q':
  627.                     $text str_replace('_'' '$text);
  628.                     preg_match_all('/=([a-f0-9]{2})/i'$text$matches);
  629.                     foreach ($matches[1] as $value) {
  630.                         $text str_replace('='.$valuechr(hexdec($value)), $text);
  631.                     }
  632.                     break;
  633.             }
  634.             $text $this->charset->decodeCharset($text$this->charset->getCharsetAlias($charset));
  635.             $input str_replace($encoded.$space$text$input);
  636.         }
  637.         return $input;
  638.     }
  639.     /**
  640.      * Return the charset of the MIME part
  641.      *
  642.      * @param array $part
  643.      *
  644.      * @return string
  645.      */
  646.     protected function getPartCharset($part)
  647.     {
  648.         if (isset($part['charset'])) {
  649.             return $this->charset->getCharsetAlias($part['charset']);
  650.         } else {
  651.             return 'us-ascii';
  652.         }
  653.     }
  654.     /**
  655.      * Retrieve a specified MIME part
  656.      *
  657.      * @param string $type
  658.      * @param array  $parts
  659.      *
  660.      * @return string|array
  661.      */
  662.     protected function getPart($type$parts)
  663.     {
  664.         return (isset($parts[$type])) ? $parts[$type] : false;
  665.     }
  666.     /**
  667.      * Retrieve the Body of a MIME part
  668.      *
  669.      * @param array $part
  670.      *
  671.      * @return string
  672.      */
  673.     protected function getPartBody(&$part)
  674.     {
  675.         $body '';
  676.         if ($this->stream) {
  677.             $body $this->getPartBodyFromFile($part);
  678.         } elseif ($this->data) {
  679.             $body $this->getPartBodyFromText($part);
  680.         }
  681.         return $body;
  682.     }
  683.     /**
  684.      * Retrieve the Body from a MIME part from file
  685.      *
  686.      * @param array $part
  687.      *
  688.      * @return string Mime Body Part
  689.      */
  690.     protected function getPartBodyFromFile(&$part)
  691.     {
  692.         $start $part['starting-pos-body'];
  693.         $end $part['ending-pos-body'];
  694.         $body '';
  695.         if ($end $start 0) {
  696.             fseek($this->stream$startSEEK_SET);
  697.             $body fread($this->stream$end $start);
  698.         }
  699.         return $body;
  700.     }
  701.     /**
  702.      * Retrieve the Body from a MIME part from text
  703.      *
  704.      * @param array $part
  705.      *
  706.      * @return string Mime Body Part
  707.      */
  708.     protected function getPartBodyFromText(&$part)
  709.     {
  710.         $start $part['starting-pos-body'];
  711.         $end $part['ending-pos-body'];
  712.         return substr($this->data$start$end $start);
  713.     }
  714.     /**
  715.      * Retrieve the content of a MIME part
  716.      *
  717.      * @param array $part
  718.      *
  719.      * @return string
  720.      */
  721.     protected function getPartComplete(&$part)
  722.     {
  723.         $body '';
  724.         if ($this->stream) {
  725.             $body $this->getPartFromFile($part);
  726.         } elseif ($this->data) {
  727.             $body $this->getPartFromText($part);
  728.         }
  729.         return $body;
  730.     }
  731.     /**
  732.      * Retrieve the content from a MIME part from file
  733.      *
  734.      * @param array $part
  735.      *
  736.      * @return string Mime Content
  737.      */
  738.     protected function getPartFromFile(&$part)
  739.     {
  740.         $start $part['starting-pos'];
  741.         $end $part['ending-pos'];
  742.         $body '';
  743.         if ($end $start 0) {
  744.             fseek($this->stream$startSEEK_SET);
  745.             $body fread($this->stream$end $start);
  746.         }
  747.         return $body;
  748.     }
  749.     /**
  750.      * Retrieve the content from a MIME part from text
  751.      *
  752.      * @param array $part
  753.      *
  754.      * @return string Mime Content
  755.      */
  756.     protected function getPartFromText(&$part)
  757.     {
  758.         $start $part['starting-pos'];
  759.         $end $part['ending-pos'];
  760.         return substr($this->data$start$end $start);
  761.     }
  762.     /**
  763.      * Retrieve the resource
  764.      *
  765.      * @return resource resource
  766.      */
  767.     public function getResource()
  768.     {
  769.         return $this->resource;
  770.     }
  771.     /**
  772.      * Retrieve the file pointer to email
  773.      *
  774.      * @return resource stream
  775.      */
  776.     public function getStream()
  777.     {
  778.         return $this->stream;
  779.     }
  780.     /**
  781.      * Retrieve the text of an email
  782.      *
  783.      * @return string data
  784.      */
  785.     public function getData()
  786.     {
  787.         return $this->data;
  788.     }
  789.     /**
  790.      * Retrieve the parts of an email
  791.      *
  792.      * @return array parts
  793.      */
  794.     public function getParts()
  795.     {
  796.         return $this->parts;
  797.     }
  798.     /**
  799.      * Retrieve the charset manager object
  800.      *
  801.      * @return CharsetManager charset
  802.      */
  803.     public function getCharset()
  804.     {
  805.         return $this->charset;
  806.     }
  807.     /**
  808.      * Add a middleware to the parser MiddlewareStack
  809.      * Each middleware is invoked when:
  810.      *   a MimePart is retrieved by mailparse_msg_get_part_data() during $this->parse()
  811.      * The middleware will receive MimePart $part and the next MiddlewareStack $next
  812.      *
  813.      * Eg:
  814.      *
  815.      * $Parser->addMiddleware(function(MimePart $part, MiddlewareStack $next) {
  816.      *      // do something with the $part
  817.      *      return $next($part);
  818.      * });
  819.      *
  820.      * @param callable $middleware Plain Function or Middleware Instance to execute
  821.      * @return void
  822.      */
  823.     public function addMiddleware(callable $middleware)
  824.     {
  825.         if (!$middleware instanceof Middleware) {
  826.             $middleware = new Middleware($middleware);
  827.         }
  828.         $this->middlewareStack $this->middlewareStack->add($middleware);
  829.     }
  830. }