Path: blob/master/externals/mimemailparser/MimeMailParser.class.php
12240 views
<?php12require_once('attachment.class.php');34/**5* Fast Mime Mail parser Class using PHP's MailParse Extension6* @author [email protected]7* @url http://www.fijiwebdesign.com/8* @license http://creativecommons.org/licenses/by-sa/3.0/us/9* @version $Id$10*/11class MimeMailParser {1213/**14* PHP MimeParser Resource ID15*/16public $resource;1718/**19* A file pointer to email20*/21public $stream;2223/**24* A text of an email25*/26public $data;2728/**29* Stream Resources for Attachments30*/31public $attachment_streams;3233/**34* Inialize some stuff35* @return36*/37public function __construct() {38$this->attachment_streams = array();39}4041/**42* Free the held resouces43* @return void44*/45public function __destruct() {46// clear the email file resource47if (is_resource($this->stream)) {48fclose($this->stream);49}50// clear the MailParse resource51if (is_resource($this->resource)) {52mailparse_msg_free($this->resource);53}54// remove attachment resources55foreach($this->attachment_streams as $stream) {56fclose($stream);57}58}5960/**61* Set the file path we use to get the email text62* @return Object MimeMailParser Instance63* @param $mail_path Object64*/65public function setPath($path) {66// should parse message incrementally from file67$this->resource = mailparse_msg_parse_file($path);68$this->stream = fopen($path, 'r');69$this->parse();70return $this;71}7273/**74* Set the Stream resource we use to get the email text75* @return Object MimeMailParser Instance76* @param $stream Resource77*/78public function setStream($stream) {7980// streams have to be cached to file first81if (get_resource_type($stream) == 'stream') {82$tmp_fp = tmpfile();83if ($tmp_fp) {84while(!feof($stream)) {85fwrite($tmp_fp, fread($stream, 2028));86}87fseek($tmp_fp, 0);88$this->stream =& $tmp_fp;89} else {90throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');91return false;92}93fclose($stream);94} else {95$this->stream = $stream;96}9798$this->resource = mailparse_msg_create();99// parses the message incrementally low memory usage but slower100while(!feof($this->stream)) {101mailparse_msg_parse($this->resource, fread($this->stream, 2082));102}103$this->parse();104return $this;105}106107/**108* Set the email text109* @return Object MimeMailParser Instance110* @param $data String111*/112public function setText($data) {113// NOTE: This has been modified for Phabricator. If the input data does not114// end in a newline, Mailparse fails to include the last line in the mail115// body. This happens somewhere deep, deep inside the mailparse extension,116// so adding a newline here seems like the most straightforward fix.117if (!preg_match('/\n\z/', $data)) {118$data = $data."\n";119}120121$this->resource = mailparse_msg_create();122// does not parse incrementally, fast memory hog might explode123mailparse_msg_parse($this->resource, $data);124$this->data = $data;125$this->parse();126return $this;127}128129/**130* Parse the Message into parts131* @return void132* @private133*/134private function parse() {135$structure = mailparse_msg_get_structure($this->resource);136$this->parts = array();137foreach($structure as $part_id) {138$part = mailparse_msg_get_part($this->resource, $part_id);139$this->parts[$part_id] = mailparse_msg_get_part_data($part);140}141}142143/**144* Retrieve the Email Headers145* @return Array146*/147public function getHeaders() {148if (isset($this->parts[1])) {149return $this->getPartHeaders($this->parts[1]);150} else {151throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');152}153return false;154}155/**156* Retrieve the raw Email Headers157* @return string158*/159public function getHeadersRaw() {160if (isset($this->parts[1])) {161return $this->getPartHeaderRaw($this->parts[1]);162} else {163throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');164}165return false;166}167168/**169* Retrieve a specific Email Header170* @return String171* @param $name String Header name172*/173public function getHeader($name) {174if (isset($this->parts[1])) {175$headers = $this->getPartHeaders($this->parts[1]);176if (isset($headers[$name])) {177return $headers[$name];178}179} else {180throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');181}182return false;183}184185/**186* Returns the email message body in the specified format187* @return Mixed String Body or False if not found188* @param $type Object[optional]189*/190public function getMessageBody($type = 'text') {191192// NOTE: This function has been modified for Phabricator. The default193// implementation returns the last matching part, which throws away text194// for many emails. Instead, we concatenate all matching parts. See195// issue 22 for discussion:196// http://code.google.com/p/php-mime-mail-parser/issues/detail?id=22197198$body = false;199$mime_types = array(200'text'=> 'text/plain',201'html'=> 'text/html'202);203if (in_array($type, array_keys($mime_types))) {204foreach($this->parts as $part) {205$disposition = $this->getPartContentDisposition($part);206if ($disposition == 'attachment') {207// text/plain parts with "Content-Disposition: attachment" are208// attachments, not part of the text body.209continue;210}211if ($this->getPartContentType($part) == $mime_types[$type]) {212$headers = $this->getPartHeaders($part);213// Concatenate all the matching parts into the body text. For example,214// if a user sends a message with some text, then an image, and then215// some more text, the text body of the email gets split over several216// attachments.217$body .= $this->decode(218$this->getPartBody($part),219array_key_exists('content-transfer-encoding', $headers)220? $headers['content-transfer-encoding']221: '');222}223}224} else {225throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');226}227return $body;228}229230/**231* get the headers for the message body part.232* @return Array233* @param $type Object[optional]234*/235public function getMessageBodyHeaders($type = 'text') {236$headers = false;237$mime_types = array(238'text'=> 'text/plain',239'html'=> 'text/html'240);241if (in_array($type, array_keys($mime_types))) {242foreach($this->parts as $part) {243if ($this->getPartContentType($part) == $mime_types[$type]) {244$headers = $this->getPartHeaders($part);245}246}247} else {248throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');249}250return $headers;251}252253/**254* Returns the attachments contents in order of appearance255* @return Array256* @param $type Object[optional]257*/258public function getAttachments() {259// NOTE: This has been modified for Phabricator. Some mail clients do not260// send attachments with "Content-Disposition" headers.261$attachments = array();262$dispositions = array("attachment","inline");263$non_attachment_types = array("text/plain", "text/html");264$nonameIter = 0;265foreach ($this->parts as $part) {266$disposition = $this->getPartContentDisposition($part);267$filename = 'noname';268if (isset($part['disposition-filename'])) {269$filename = $part['disposition-filename'];270} elseif (isset($part['content-name'])) {271// if we have no disposition but we have a content-name, it's a valid attachment.272// we simulate the presence of an attachment disposition with a disposition filename273$filename = $part['content-name'];274$disposition = 'attachment';275} elseif (!in_array($part['content-type'], $non_attachment_types, true)276&& substr($part['content-type'], 0, 10) !== 'multipart/'277) {278// if we cannot get it with getMessageBody, we assume it is an attachment279$disposition = 'attachment';280}281282if (in_array($disposition, $dispositions) && isset($filename) === true) {283if ($filename == 'noname') {284$nonameIter++;285$filename = 'noname'.$nonameIter;286}287$attachments[] = new MimeMailParser_attachment(288$filename,289$this->getPartContentType($part),290$this->getAttachmentStream($part),291$disposition,292$this->getPartHeaders($part)293);294}295}296return $attachments;297}298299/**300* Return the Headers for a MIME part301* @return Array302* @param $part Array303*/304private function getPartHeaders($part) {305if (isset($part['headers'])) {306return $part['headers'];307}308return false;309}310311/**312* Return a Specific Header for a MIME part313* @return Array314* @param $part Array315* @param $header String Header Name316*/317private function getPartHeader($part, $header) {318if (isset($part['headers'][$header])) {319return $part['headers'][$header];320}321return false;322}323324/**325* Return the ContentType of the MIME part326* @return String327* @param $part Array328*/329private function getPartContentType($part) {330if (isset($part['content-type'])) {331return $part['content-type'];332}333return false;334}335336/**337* Return the Content Disposition338* @return String339* @param $part Array340*/341private function getPartContentDisposition($part) {342if (isset($part['content-disposition'])) {343return $part['content-disposition'];344}345return false;346}347348/**349* Retrieve the raw Header of a MIME part350* @return String351* @param $part Object352*/353private function getPartHeaderRaw(&$part) {354$header = '';355if ($this->stream) {356$header = $this->getPartHeaderFromFile($part);357} else if ($this->data) {358$header = $this->getPartHeaderFromText($part);359} else {360throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');361}362return $header;363}364/**365* Retrieve the Body of a MIME part366* @return String367* @param $part Object368*/369private function getPartBody(&$part) {370$body = '';371if ($this->stream) {372$body = $this->getPartBodyFromFile($part);373} else if ($this->data) {374$body = $this->getPartBodyFromText($part);375} else {376throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');377}378return $body;379}380381/**382* Retrieve the Header from a MIME part from file383* @return String Mime Header Part384* @param $part Array385*/386private function getPartHeaderFromFile(&$part) {387$start = $part['starting-pos'];388$end = $part['starting-pos-body'];389fseek($this->stream, $start, SEEK_SET);390$header = fread($this->stream, $end-$start);391return $header;392}393/**394* Retrieve the Body from a MIME part from file395* @return String Mime Body Part396* @param $part Array397*/398private function getPartBodyFromFile(&$part) {399$start = $part['starting-pos-body'];400$end = $part['ending-pos-body'];401fseek($this->stream, $start, SEEK_SET);402$body = fread($this->stream, $end-$start);403return $body;404}405406/**407* Retrieve the Header from a MIME part from text408* @return String Mime Header Part409* @param $part Array410*/411private function getPartHeaderFromText(&$part) {412$start = $part['starting-pos'];413$end = $part['starting-pos-body'];414$header = substr($this->data, $start, $end-$start);415return $header;416}417/**418* Retrieve the Body from a MIME part from text419* @return String Mime Body Part420* @param $part Array421*/422private function getPartBodyFromText(&$part) {423$start = $part['starting-pos-body'];424$end = $part['ending-pos-body'];425$body = substr($this->data, $start, $end-$start);426return $body;427}428429/**430* Read the attachment Body and save temporary file resource431* @return String Mime Body Part432* @param $part Array433*/434private function getAttachmentStream(&$part) {435$temp_fp = tmpfile();436437array_key_exists('content-transfer-encoding', $part['headers']) ? $encoding = $part['headers']['content-transfer-encoding'] : $encoding = '';438439if ($temp_fp) {440if ($this->stream) {441$start = $part['starting-pos-body'];442$end = $part['ending-pos-body'];443fseek($this->stream, $start, SEEK_SET);444$len = $end-$start;445$written = 0;446$write = 2028;447$body = '';448while($written < $len) {449if (($written+$write < $len )) {450$write = $len - $written;451}452$part = fread($this->stream, $write);453fwrite($temp_fp, $this->decode($part, $encoding));454$written += $write;455}456} else if ($this->data) {457$attachment = $this->decode($this->getPartBodyFromText($part), $encoding);458fwrite($temp_fp, $attachment, strlen($attachment));459}460fseek($temp_fp, 0, SEEK_SET);461} else {462throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');463return false;464}465return $temp_fp;466}467468469/**470* Decode the string depending on encoding type.471* @return String the decoded string.472* @param $encodedString The string in its original encoded state.473* @param $encodingType The encoding type from the Content-Transfer-Encoding header of the part.474*/475private function decode($encodedString, $encodingType) {476if (strtolower($encodingType) == 'base64') {477return base64_decode($encodedString);478} else if (strtolower($encodingType) == 'quoted-printable') {479return quoted_printable_decode($encodedString);480} else {481return $encodedString;482}483}484485}486487488?>489490491