Source for file Message.class.php
Documentation is available at Message.class.php
* This file contains functions needed to handle mime messages.
* @copyright 2003-2020 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id: Message.class.php 14845 2020-01-07 08:09:34Z pdontthink $
* The object that contains a message.
* message is the object that contains messages. It is a recursive object in
* that through the $entities variable, it can contain more objects of type
* message. See documentation in mime.txt for a better description of how this
* Message \answered status
* Message \deleted status
* Message \flagged status
* for fetching body parts out of raw messages
* for fetching body parts out of raw messages
* Local attachment filename location where the tempory attachment is
* stored. For use in delivery class.
* @param string $ent entity id
* Add nested message part
* Get file name used for mime part
* @return string file name
$filename =
$header->disposition->getProperty('filename');
if (trim($filename) ==
'') {
$name =
decodeHeader($header->disposition->getProperty('name'));
$name =
$header->getParameter('name');
if (!trim( $header->id )) {
$filename =
'cid: ' .
$header->id .
'.' .
strtolower($header->type1);
$filename =
$header->getParameter('filename');
$filename =
$header->getParameter('name');
if (!trim( $header->id )) {
$filename =
'cid: ' .
$header->id .
'.' .
strtolower($header->type1);
* Add header object to message object.
* WARNING: Unfinished code. Don't expect it to work in older sm versions.
* @param mixed $read array or string with message headers
* @todo FIXME: rfc822header->parseHeader() does not return rfc822header object
* @return mixed (object or string?)
if (($cur_ent ==
'') ||
($cur_ent ==
'0')) {
for ($i =
0,$entCount =
count($ent_a) -
1; $i <
$entCount; ++
$i) {
if (isset
($cur_ent_a[$i]) &&
($cur_ent_a[$i] !=
$ent_a[$i])) {
$cur_ent_a =
explode('.', $msg->entity_id);
} else if (!isset
($cur_ent_a[$i])) {
if (isset
($msg->entities[($ent_a[$i]-
1)])) {
$msg =
$msg->entities[($ent_a[$i]-
1)];
$msg =
$msg->entities[0];
if (($msg->type0 ==
'message') &&
($msg->type1 ==
'rfc822')) {
/*this is a header for a message/rfc822 entity */
$msg =
$msg->entities[0];
if (($msg->type0 ==
'message') &&
($msg->type1 ==
'rfc822')) {
/*this is a header for a message/rfc822 entity */
$msg =
$msg->entities[0];
if (isset
($msg->entities[($ent_a[$entCount])-
1])) {
if (is_object($msg->entities[($ent_a[$entCount])-
1])) {
$msg =
$msg->entities[($ent_a[$entCount]-
1)];
* @param string $s message body
foreach ($msg->entities as $m) {
* Bodystructure parser, a recursive function for generating the
* entity-tree with all the mime-parts.
* It follows RFC2060 and stores all the described fields in the
* @return object Message object
* @todo define argument and return types
if($msg) $msg->setEntIds($msg,false,0);
$iCnt =
count($msg->entities);
if ($msg->parent->type0 ==
'message' &&
$msg->parent->type1 ==
'rfc822' &&
$msg->type0 ==
'multipart') {
$msg->entity_id =
"$init.$iEntSub";
$msg->entity_id =
$iEntSub;
for ($i=
0;$i<
$iCnt;++
$i) {
$msg->entities[$i]->parent =
& $msg;
if (strrchr($msg->entity_id, '.') !=
'.0') {
$msg->entities[$i]->setEntIds($msg->entities[$i],$msg->entity_id,$i);
$msg->entities[$i]->setEntIds($msg->entities[$i],$msg->parent->entity_id,$i);
* @return object Message object
* @since 1.4.0 (code was part of parseStructure() in 1.3.x)
for ($cnt =
strlen($read); $i <
$cnt; ++
$i) {
$msg->header->type0 =
'multipart';
$msg->type0 =
'multipart';
while ($read{$i} ==
'(') {
$msg->addEntity($msg->parseBodyStructure($read, $i, $msg));
/* multipart properties */
$arg_a[] =
$msg->parseProperties($read, $i);
if (isset
($msg->type0) &&
($msg->type0 ==
'multipart')) {
$arg_a[] =
$msg->parseDisposition($read, $i);
} else { /* properties */
$arg_a[] =
$msg->parseProperties($read, $i);
if (isset
($msg->type0) &&
($msg->type0 ==
'multipart')) {
$arg_a[]=
$msg->parseLanguage($read, $i);
if (($arg_a[0] ==
'message') &&
($arg_a[1] ==
'rfc822')) {
$msg->header->type0 =
$arg_a[0];
$msg->header->type1 =
$arg_a[1];
$msg->rfc822_header =
$msg->parseEnvelope($read, $i, $rfc822_hdr);
while (($i <
$cnt) &&
($read{$i} !=
'(')) {
$msg->addEntity($msg->parseBodyStructure($read, $i,$msg));
$arg_a[] =
$msg->parseDisposition($read, $i);
if (($arg_a[0] ==
'text') ||
(($arg_a[0] ==
'message') &&
($arg_a[1] ==
'rfc822'))) {
$arg_a[] =
$msg->parseDisposition($read, $i);
$arg_a[] =
$msg->parseLanguage($read, $i);
if (($arg_a[0] ==
'text') ||
(($arg_a[0] ==
'message') &&
($arg_a[1] ==
'rfc822'))) {
$arg_a[] =
$msg->parseLanguage($read, $i);
$i =
$msg->parseParenthesis($read, $i);
$arg_a[] =
''; /* not yet described in rfc2060 */
/* unknown argument, skip this part */
$i =
$msg->parseParenthesis($read, $i);
/* inside an entity -> start processing */
$arg_s =
$msg->parseQuote($read, $i);
$arg_s =
strtolower($arg_s); /* type0 and type1 */
/* probably NIL argument */
if ($tmpnil ==
'NIL ' ||
$tmpnil ==
'NIL)') {
/* process the literal value */
$arg_a[] =
$msg->parseLiteral($read, $i);
if ($read{$i} ==
' ') { break; }
$multipart =
(isset
($msg->type0) &&
($msg->type0 ==
'multipart'));
$shifted_args =
(($arg_a[0] ==
'text') ||
(($arg_a[0] ==
'message') &&
($arg_a[1] ==
'rfc822')));
$hdr->parameters =
$arg_a[2];
$hdr->description =
$arg_a[4];
$hdr->entity_id =
$msg->entity_id;
$hdr->md5 =
(isset
($arg_a[7+
$s]) ?
$arg_a[7+
$s] :
$hdr->md5);
$hdr->disposition =
(isset
($arg_a[8+
$s]) ?
$arg_a[8+
$s] :
$hdr->disposition);
$hdr->language =
(isset
($arg_a[9+
$s]) ?
$arg_a[9+
$s] :
$hdr->language);
$hdr->type0 =
'multipart';
$msg->type0 =
'multipart';
$hdr->parameters =
(isset
($arg_a[1]) ?
$arg_a[1] :
$hdr->parameters);
$hdr->disposition =
(isset
($arg_a[2]) ?
$arg_a[2] :
$hdr->disposition);
$hdr->language =
(isset
($arg_a[3]) ?
$arg_a[3] :
$hdr->language);
for (; $read{$i} !=
')'; ++
$i) {
} else if ($read{$i} ==
'{') {
$properties[$prop_name] =
'';
} else if ($prop_name !=
'') {
$properties[$prop_name] =
$arg_s;
* Joins RFC-2231 continuations, converts encoding to RFC-2047 style
* @param array $properties
/* STAGE 1: look for multi-line parameters, convert to the single line
form, and normalize values */
foreach($properties as $key=>
$value) {
/* Look for parameters followed by "*", a number, and an optional "*"
if (preg_match('/^(.*\*)(\d+)(|\*)$/', $key, $matches)) {
unset
($properties[$key]);
$prop_name =
$matches[1];
if (!isset
($cont[$prop_name])) $cont[$prop_name] =
array();
/* An asterisk at the end of parameter name indicates that there
may be an encoding information present, and the parameter
value is percent-hex encoded. If parameter is not encoded, we
encode it to simplify further processing.
/* Use the number from parameter name as segment index */
$cont[$prop_name][$matches[2]] =
$value;
foreach($cont as $key=>
$values) {
/* Sort segments of multi-line parameters by index number. */
/* Join segments. We can do it safely, because:
- All segments are encoded.
- Per RFC-2231, chapter 4.1 notes.
/* Add language and character set field delimiters if not present,
as required per RFC-2231, chapter 4.1, note #5. */
if (strpos($value, "'") ===
false) $value =
"''".
$value;
$properties[$key] =
$value;
/* STAGE 2: Convert single line RFC-2231 encoded parameters, and
previously converted multi-line parameters, to RFC-2047 encoding */
foreach($properties as $key=>
$value) {
if ($idx =
strpos($key, '*')) {
unset
($properties[$key]);
/* Extract the charset & language. */
/* No character set defaults to US-ASCII */
if (!$charset) $charset =
'US-ASCII';
if ($language) $language =
'*'.
$language;
/* Convert to RFC-2047 base64 encoded string. */
* @param object $hdr MessageHeader object
* @return object MessageHeader object
for ($cnt =
strlen($read); ($i <
$cnt) &&
($read{$i} !=
')'); ++
$i) {
/* temp bugfix (SM 1.5 will have a working clean version)
too much work to implement that version right now */
/* probably NIL argument */
/* Address structure (with group support)
* Note: Group support is useless on SMTP connections
* because the protocol doesn't support it
for (; $i <
$cnt &&
$read{$i} !=
')'; ++
$i) {
if (($addr->host ==
'') &&
($addr->mailbox !=
'')) {
} else if ($group &&
($addr->host ==
'') &&
($addr->mailbox ==
'')) {
if ($a ==
($j+
1)) { /* no group members */
$group_addr->group =
$group;
$group_addr->mailbox =
'';
$group_addr->personal =
"$group: Undisclosed recipients;";
$d =
strtr($arg_a[0], array(' ' =>
' '));
if (!$arg_a[1]) $arg_a[1] =
_("(no subject)");
$hdr->date_unparsed =
strtr($d,'<>',' '); /* original date */
$hdr->subject =
$arg_a[1]; /* argument 2: subject */
$hdr->from =
is_array($arg_a[2]) ?
$arg_a[2][0] :
''; /* argument 3: from */
$hdr->sender =
is_array($arg_a[3]) ?
$arg_a[3][0] :
''; /* argument 4: sender */
$hdr->reply_to =
is_array($arg_a[4]) ?
$arg_a[4][0] :
''; /* argument 5: reply-to */
$hdr->to =
$arg_a[5]; /* argument 6: to */
$hdr->cc =
$arg_a[6]; /* argument 7: cc */
$hdr->bcc =
$arg_a[7]; /* argument 8: bcc */
$hdr->in_reply_to =
$arg_a[8]; /* argument 9: in-reply-to */
$hdr->message_id =
$arg_a[9]; /* argument 10: message-id */
$lit_cnt =
substr($read, $i, $iPos -
$i);
$i +=
strlen($lit_cnt) +
3; /* skip } + \r + \n */
/* Now read the literal */
$s =
($lit_cnt ?
substr($read,$i,$lit_cnt):
'');
/* temp bugfix (SM 1.5 will have a working clean version)
too much work to implement that version right now */
} else { /* should never happen */
$i +=
3; /* } + \r + \n */
* This extract the string value from a quoted string. After the end-quote
* character is found it returns the string. The offset $i when calling
* this function points to the first double quote. At the end it points to
* The ending quote. This function takes care of escaped double quotes.
* initial $i end position $i
* @param integer $i offset in $read
* @return string string inbetween the double quotes
* @author Marc Groot Koerkamp
$iPos =
strpos($read,'"',$iPos);
if ($iPos &&
$read{$iPos -
1} !=
'\\') {
$s =
substr($read,$i,($iPos-
$i));
} else if ($iPos >
1 &&
$read{$iPos -
1} ==
'\\' &&
$read{$iPos-
2} ==
'\\') {
// This is an unique situation where the fast detection of the string
// fails. If the quote string ends with \\ then we need to iterate
// through the entire string to make sure we detect the unexcaped
// double quotes correctly.
for ($j=
$iPosStart,$iCnt=
strlen($read);$j<
$iCnt;++
$j) {
* @return object AddressStructure object
for (; $read{$i} !=
')'; ++
$i) {
case '"':
$arg_a[] =
$this->parseQuote($read, $i); break;
if (count($arg_a) ==
4) {
$adr->personal =
$arg_a[0];
$adr->mailbox =
$arg_a[2];
* @param object Disposition object or empty string
for (; $read{$i} !=
')'; ++
$i) {
case '"':
$arg_a[] =
$this->parseQuote($read, $i); break;
$disp->properties =
$arg_a[1];
* @return object Language object or empty string
/* no idea how to process this one without examples */
for (; $read{$i} !=
')'; ++
$i) {
case '"':
$arg_a[] =
$this->parseQuote($read, $i); break;
$lang->properties =
$arg_a[1];
* Parse message text enclosed in parenthesis
for ($i++
; $read{$i} !=
')'; ++
$i) {
* Function to fill the message structure in case the
* bodystructure is not available
* @param string $type0 message part type
* @param string $type1 message part subtype
* @return string (only when type0 is not message or multipart)
for ($i =
1; $i <
$count; ++
$i) {
if (($mime_header ||
$rfc822_header) &&
(preg_match("/^.*boundary=\"?(.+(?=\")|.+).*/i", $line, $reg))) {
} else if ($rfc822_header &&
$line ==
'') {
if ($msg->type0 ==
'multipart') {
if ((($line{0} ==
'-') ||
$rfc822_header) && isset
($boundaries[0])) {
$cnt =
count($boundaries)-
1;
$bnd =
$boundaries[$cnt]['bnd'];
$bndreg =
$boundaries[$cnt]['bndreg'];
$regstr =
'/^--'.
"($bndreg)".
".*".
'/';
if (strlen($line) >
($bndlen +
3)) {
if (($line{$bndlen+
2} ==
'-') &&
($line{$bndlen+
3} ==
'-')) {
/* calc offset and return $msg */
//$entStr = CalcEntity("$entStr", -1);
//$entStr = CalcEntity("$entStr", 0);
* @param array $alt_order
function findDisplayEntity($entity =
array(), $alt_order =
array('text/plain', 'text/html'), $strict=
false) {
if ($this->type0 ==
'multipart') {
if($this->type1 ==
'alternative') {
if (count($msg->entities) ==
0) {
$entity[] =
$msg->entity_id;
$entity =
$msg->findDisplayEntity($entity, $alt_order, $strict);
} else if ($this->type1 ==
'related') { /* RFC 2387 */
foreach ($msgs as $msg) {
if (count($msg->entities) ==
0) {
$entity[] =
$msg->entity_id;
$entity =
$msg->findDisplayEntity($entity, $alt_order, $strict);
} else { /* Treat as multipart/mixed */
if(!(is_object($ent->header->disposition) &&
strtolower($ent->header->disposition->name) ==
'attachment') &&
(!isset
($ent->header->parameters['filename'])) &&
(!isset
($ent->header->parameters['name'])) &&
(($ent->type0 !=
'message') &&
($ent->type1 !=
'rfc822'))) {
$entity =
$ent->findDisplayEntity($entity, $alt_order, $strict);
} else { /* If not multipart, then just compare with each entry from $alt_order */
// $alt_order[] = "message/rfc822";
foreach ($alt_order as $alt) {
if( ($alt ==
$type) && isset
($this->entity_id) ) {
(!isset
($this->header->parameters['filename'])) &&
(!isset
($this->header->parameters['name'])) &&
isset
($this->header->disposition) &&
is_object($this->header->disposition) &&
!(is_object($this->header->disposition) &&
strtolower($this->header->disposition->name) ==
'attachment')) {
if(!(is_object($ent->header->disposition) &&
strtolower($ent->header->disposition->name) ==
'attachment') &&
(($ent->type0 !=
'message') &&
($ent->type1 !=
'rfc822'))) {
$entity =
$ent->findDisplayEntity($entity, $alt_order, $strict);
if(!$strict &&
!$found) {
if (($this->type0 ==
'text') &&
if (!is_object($this->header->disposition) ||
strtolower($this->header->disposition->name) !=
'attachment') {
* @param array $alt_order
/* If we are dealing with alternative parts then we */
/* choose the best viewable message supported by SM. */
$type =
$ent->header->type0 .
'/' .
$ent->header->type1;
if ($type ==
'multipart/related') {
$type =
$ent->header->getParameter('type');
// Mozilla bug. Mozilla does not provide the parameter type.
if (!$type) $type =
'text/html';
$altCount =
count($alt_order);
for ($j =
$best_view; $j <
$altCount; ++
$j) {
if (($alt_order[$j] ==
$type) &&
($j >=
$best_view)) {
$related_type =
$this->header->getParameter('type');
// Mozilla bug. Mozilla does not provide the parameter type.
if (!$related_type) $related_type =
'text/html';
for ($i =
0; $i <
$entCount; ++
$i) {
$type =
$this->entities[$i]->header->type0.
'/'.
$this->entities[$i]->header->type1;
if ($related_type ==
$type) {
* @param array $exclude_id
if (($this->type0 == 'message') &&
($this->type1 == 'rfc822') &&
$this = $this->entities[0];
foreach ($exclude_id as $excl) {
if ($entity->entity_id ===
$excl) {
if ($entity->type0 ==
'multipart') {
$result =
$entity->getAttachments($exclude_id, $result);
} else if ($entity->type0 !=
'multipart') {
foreach ($exclude_id as $excl) {
$exclude =
$exclude ||
($this->entity_id ==
$excl);
* Add attachment to message object
* @param string $type attachment type
* @param string $name attachment name
* @param string $location path to attachment
$mime_header->setParameter('name', $name);
// FIXME: duplicate code. see ContentType class
$mime_header->type0 =
substr($type, 0, $pos);
$mime_header->type1 =
substr($type, $pos+
1);
$mime_header->type0 =
$type;
$attachment->att_local_name =
$location;
$disposition->properties['filename'] =
$name;
$mime_header->disposition =
$disposition;
$attachment->mime_header =
$mime_header;
* Delete all attachments from this object from disk.
$hashed_attachment_dir =
getHashedDir($username, $attachment_dir);
// recursively delete attachments from entities contained in this object
for ($i=
0, $entCount=
count($this->entities);$i<
$entCount; ++
$i) {
$this->entities[$i]->purgeAttachments();
Documentation generated on Mon, 13 Jan 2020 04:23:04 +0100 by phpDocumentor 1.4.3