ad4e1fd79544f964573571dedfa595011a7fa9d6
6 * This contains all the functions needed to send messages through
9 * @author Marc Groot Koerkamp
10 * @copyright © 1999-2007 The SquirrelMail Project Team
11 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
13 * @package squirrelmail
17 * Deliver Class - called to actually deliver the message
19 * This class is called by compose.php and other code that needs
20 * to send messages. All delivery functionality should be centralized
23 * Do not place UI code in this class, as UI code should be placed in templates
26 * @author Marc Groot Koerkamp
27 * @package squirrelmail
32 * function mail - send the message parts to the SMTP stream
34 * @param Message $message Message object to send
35 * NOTE that this is passed by
36 * reference and will be modified
37 * upon return with updated
38 * fields such as Message ID, References,
39 * In-Reply-To and Date headers.
40 * @param resource $stream Handle to the outgoing stream
41 * (when FALSE, nothing will be
42 * written to the stream; this can
43 * be used to determine the actual
44 * number of bytes that will be
45 * written to the stream)
46 * @param string $reply_id Identifies message being replied to
47 * (OPTIONAL; caller should ONLY specify
48 * a value for this when the message
49 * being sent is a reply)
50 * @param string $reply_ent_id Identifies message being replied to
51 * in the case it was an embedded/attached
52 * message inside another (OPTIONAL; caller
53 * should ONLY specify a value for this
54 * when the message being sent is a reply)
55 * @param mixed $extra Any implementation-specific variables
56 * can be passed in here and used in
57 * an overloaded version of this method
60 * @return integer The number of bytes written (or that would have been
61 * written) to the output stream.
64 function mail(&$message, $stream=false, $reply_id=0, $reply_ent_id=0,
67 $rfc822_header = &$message->rfc822_header
;
69 if (count($message->entities
)) {
70 $boundary = $this->mimeBoundary();
71 $rfc822_header->content_type
->properties
['boundary']='"'.$boundary.'"';
78 // calculate reply header if needed
81 global $imapConnection, $username, $imapServerAddress,
84 if (!is_resource($imapConnection))
85 $imapConnection = sqimap_login($username, FALSE,
86 $imapServerAddress, $imapPort, 0);
88 sqimap_mailbox_select($imapConnection, $mailbox);
89 $reply_message = sqimap_get_message($imapConnection, $reply_id, $mailbox);
92 /* redefine the messsage in case of message/rfc822 */
93 $reply_message = $message->getEntity($reply_ent_id);
94 /* message is an entity which contains the envelope and type0=message
95 * and type1=rfc822. The actual entities are childs from
96 * $reply_message->entities[0]. That's where the encoding and is located
99 $orig_header = $reply_message->rfc822_header
; /* here is the envelope located */
102 $orig_header = $reply_message->rfc822_header
;
104 $message->reply_rfc822_header
= $orig_header;
108 $reply_rfc822_header = (isset($message->reply_rfc822_header
)
109 ?
$message->reply_rfc822_header
: '');
110 $header = $this->prepareRFC822_Header($rfc822_header, $reply_rfc822_header, $raw_length);
112 $this->send_mail($message, $header, $boundary, $stream, $raw_length, $extra);
118 * function send_mail - send the message parts to the IMAP stream
120 * @param Message $message Message object to send
121 * @param string $header Headers ready to send
122 * @param string $boundary Message parts boundary
123 * @param resource $stream Handle to the SMTP stream
124 * (when FALSE, nothing will be
125 * written to the stream; this can
126 * be used to determine the actual
127 * number of bytes that will be
128 * written to the stream)
129 * @param int &$raw_length The number of bytes written (or that
130 * would have been written) to the
131 * output stream - NOTE that this is
132 * passed by reference
133 * @param mixed $extra Any implementation-specific variables
134 * can be passed in here and used in
135 * an overloaded version of this method
141 function send_mail($message, $header, $boundary, $stream=false,
142 &$raw_length, $extra=NULL) {
146 $this->preWriteToStream($header);
147 $this->writeToStream($stream, $header);
149 $this->writeBody($message, $stream, $raw_length, $boundary);
153 * function writeBody - generate and write the mime boundaries around each part to the stream
155 * Recursively formats and writes the MIME boundaries of the $message
156 * to the output stream.
158 * @param Message $message Message object to transform
159 * @param resource $stream SMTP output stream
160 * (when FALSE, nothing will be
161 * written to the stream; this can
162 * be used to determine the actual
163 * number of bytes that will be
164 * written to the stream)
165 * @param integer &$length_raw raw length of the message (part)
166 * as returned by mail fn
167 * @param string $boundary custom boundary to call, usually for subparts
171 function writeBody($message, $stream, &$length_raw, $boundary='') {
172 // calculate boundary in case of multidimensional mime structures
173 if ($boundary && $message->entity_id
&& count($message->entities
)) {
174 if (strpos($boundary,'_part_')) {
175 $boundary = substr($boundary,0,strpos($boundary,'_part_'));
177 // the next four lines use strrev to reverse any nested boundaries
178 // because RFC 2046 (5.1.1) says that if a line starts with the outer
179 // boundary string (doesn't matter what the line ends with), that
180 // can be considered a match for the outer boundary; thus the nested
181 // boundary needs to be unique from the outer one
183 } else if (strpos($boundary,'_trap_')) {
184 $boundary = substr(strrev($boundary),0,strpos(strrev($boundary),'_part_'));
186 $boundary_new = strrev($boundary . '_part_'.$message->entity_id
);
188 $boundary_new = $boundary;
190 if ($boundary && !$message->rfc822_header
) {
191 $s = '--'.$boundary."\r\n";
192 $s .= $this->prepareMIME_Header($message, $boundary_new);
193 $length_raw +
= strlen($s);
195 $this->preWriteToStream($s);
196 $this->writeToStream($stream, $s);
199 $this->writeBodyPart($message, $stream, $length_raw);
202 for ($i=0, $entCount=count($message->entities
);$i<$entCount;$i++
) {
203 $msg = $this->writeBody($message->entities
[$i], $stream, $length_raw, $boundary_new);
204 if ($i == $entCount-1) $last = true;
206 if ($boundary && $last) {
207 $s = "--".$boundary_new."--\r\n\r\n";
208 $length_raw +
= strlen($s);
210 $this->preWriteToStream($s);
211 $this->writeToStream($stream, $s);
217 * function writeBodyPart - write each individual mimepart
219 * Recursively called by WriteBody to write each mime part to the SMTP stream
221 * @param Message $message Message object to transform
222 * @param resource $stream SMTP output stream
223 * (when FALSE, nothing will be
224 * written to the stream; this can
225 * be used to determine the actual
226 * number of bytes that will be
227 * written to the stream)
228 * @param integer &$length length of the message part
229 * as returned by mail fn
233 function writeBodyPart($message, $stream, &$length) {
234 if ($message->mime_header
) {
235 $type0 = $message->mime_header
->type0
;
237 $type0 = $message->rfc822_header
->content_type
->type0
;
240 $body_part_trailing = $last = '';
245 if ($message->body_part
) {
246 $body_part = $message->body_part
;
247 // remove NUL characters
248 $body_part = str_replace("\0",'',$body_part);
249 $length +
= $this->clean_crlf($body_part);
251 $this->preWriteToStream($body_part);
252 $this->writeToStream($stream, $body_part);
255 } elseif ($message->att_local_name
) {
256 global $username, $attachment_dir;
257 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
258 $filename = $message->att_local_name
;
259 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
260 while ($body_part = fgets($file, 4096)) {
261 // remove NUL characters
262 $body_part = str_replace("\0",'',$body_part);
263 $length +
= $this->clean_crlf($body_part);
265 $this->preWriteToStream($body_part);
266 $this->writeToStream($stream, $body_part);
274 if ($message->body_part
) {
275 $body_part = $message->body_part
;
276 // remove NUL characters
277 $body_part = str_replace("\0",'',$body_part);
278 $length +
= $this->clean_crlf($body_part);
280 $this->writeToStream($stream, $body_part);
282 } elseif ($message->att_local_name
) {
283 global $username, $attachment_dir;
284 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
285 $filename = $message->att_local_name
;
286 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
287 while ($tmp = fread($file, 570)) {
288 $body_part = chunk_split(base64_encode($tmp));
289 // Up to 4.3.10 chunk_split always appends a newline,
290 // while in 4.3.11 it doesn't if the string to split
291 // is shorter than the chunk length.
292 if( substr($body_part, -1 , 1 ) != "\n" )
294 $length +
= $this->clean_crlf($body_part);
296 $this->writeToStream($stream, $body_part);
303 $body_part_trailing = '';
304 if ($last && substr($last,-1) != "\n") {
305 $body_part_trailing = "\r\n";
307 if ($body_part_trailing) {
308 $length +
= strlen($body_part_trailing);
310 $this->preWriteToStream($body_part_trailing);
311 $this->writeToStream($stream, $body_part_trailing);
317 * function clean_crlf - change linefeeds and newlines to legal characters
319 * The SMTP format only allows CRLF as line terminators.
320 * This function replaces illegal teminators with the correct terminator.
322 * @param string &$s string to clean linefeeds on
326 function clean_crlf(&$s) {
327 $s = str_replace("\r\n", "\n", $s);
328 $s = str_replace("\r", "\n", $s);
329 $s = str_replace("\n", "\r\n", $s);
334 * function strip_crlf - strip linefeeds and newlines from a string
336 * The SMTP format only allows CRLF as line terminators.
337 * This function strips all line terminators from the string.
339 * @param string &$s string to clean linefeeds on
343 function strip_crlf(&$s) {
344 $s = str_replace("\r\n ", '', $s);
345 $s = str_replace("\r", '', $s);
346 $s = str_replace("\n", '', $s);
350 * function preWriteToStream - reserved for extended functionality
352 * This function is not yet implemented.
353 * Reserved for extended functionality.
355 * @param string &$s string to operate on
359 function preWriteToStream(&$s) {
363 * function writeToStream - write data to the SMTP stream
365 * @param resource $stream SMTP output stream
366 * @param string $data string with data to send to the SMTP stream
370 function writeToStream($stream, $data) {
371 fputs($stream, $data);
375 * function initStream - reserved for extended functionality
377 * This function is not yet implemented.
378 * Reserved for extended functionality.
380 * @param Message $message Message object
381 * @param string $host host name or IP to connect to
382 * @param string $user username to log into the SMTP server with
383 * @param string $pass password to log into the SMTP server with
384 * @param integer $length
386 * @return handle $stream file handle resource to SMTP stream
388 function initStream($message, $length=0, $host='', $port='', $user='', $pass='') {
393 * function getBCC - reserved for extended functionality
395 * This function is not yet implemented.
396 * Reserved for extended functionality.
404 * function prepareMIME_Header - creates the mime header
406 * @param Message $message Message object to act on
407 * @param string $boundary mime boundary from fn MimeBoundary
409 * @return string $header properly formatted mime header
411 function prepareMIME_Header($message, $boundary) {
412 $mime_header = $message->mime_header
;
416 $contenttype = 'Content-Type: '. $mime_header->type0
.'/'.
418 if (count($message->entities
)) {
419 $contenttype .= ';' . 'boundary="'.$boundary.'"';
421 if (isset($mime_header->parameters
['name'])) {
422 $contenttype .= '; name="'.
423 encodeHeader($mime_header->parameters
['name']). '"';
425 if (isset($mime_header->parameters
['charset'])) {
426 $charset = $mime_header->parameters
['charset'];
427 $contenttype .= '; charset="'.
428 encodeHeader($charset). '"';
431 $header[] = $contenttype . $rn;
432 if ($mime_header->description
) {
433 $header[] = 'Content-Description: ' . $mime_header->description
. $rn;
435 if ($mime_header->encoding
) {
436 $header[] = 'Content-Transfer-Encoding: ' . $mime_header->encoding
. $rn;
438 if ($mime_header->type0
== 'text' ||
$mime_header->type0
== 'message') {
439 $header[] = 'Content-Transfer-Encoding: 8bit' . $rn;
440 } else if ($mime_header->type0
== 'multipart' ||
$mime_header->type0
== 'alternative') {
441 /* no-op; no encoding needed */
443 $header[] = 'Content-Transfer-Encoding: base64' . $rn;
446 if ($mime_header->id
) {
447 $header[] = 'Content-ID: ' . $mime_header->id
. $rn;
449 if ($mime_header->disposition
) {
450 $disposition = $mime_header->disposition
;
451 $contentdisp = 'Content-Disposition: ' . $disposition->name
;
452 if ($disposition->getProperty('filename')) {
453 $contentdisp .= '; filename="'.
454 encodeHeader($disposition->getProperty('filename')). '"';
456 $header[] = $contentdisp . $rn;
458 if ($mime_header->md5
) {
459 $header[] = 'Content-MD5: ' . $mime_header->md5
. $rn;
461 if ($mime_header->language
) {
462 $header[] = 'Content-Language: ' . $mime_header->language
. $rn;
465 $cnt = count($header);
467 for ($i = 0 ; $i < $cnt ; $i++
) {
468 $hdr_s .= $this->foldLine($header[$i], 78,str_pad('',4));
471 $header .= $rn; /* One blank line to separate mimeheader and body-entity */
476 * function prepareRFC822_Header - prepares the RFC822 header string from Rfc822Header object(s)
478 * This function takes the Rfc822Header object(s) and formats them
479 * into the RFC822Header string to send to the SMTP server as part
480 * of the SMTP message.
482 * @param Rfc822Header $rfc822_header
483 * @param Rfc822Header $reply_rfc822_header
484 * @param integer &$raw_length length of the message
486 * @return string $header
488 function prepareRFC822_Header(&$rfc822_header, $reply_rfc822_header, &$raw_length) {
489 global $domain, $username, $encode_header_key,
490 $edit_identity, $hide_auth_header;
492 /* if server var SERVER_NAME not available, or contains
493 ":" (e.g. IPv6) which is illegal in a Message-ID, use $domain */
494 if(!sqGetGlobalVar('SERVER_NAME', $SERVER_NAME, SQ_SERVER
) ||
495 strpos($SERVER_NAME,':') !== FALSE) {
496 $SERVER_NAME = $domain;
499 sqGetGlobalVar('REMOTE_ADDR', $REMOTE_ADDR, SQ_SERVER
);
500 sqGetGlobalVar('REMOTE_PORT', $REMOTE_PORT, SQ_SERVER
);
501 sqGetGlobalVar('REMOTE_HOST', $REMOTE_HOST, SQ_SERVER
);
502 sqGetGlobalVar('HTTP_VIA', $HTTP_VIA, SQ_SERVER
);
503 sqGetGlobalVar('HTTP_X_FORWARDED_FOR', $HTTP_X_FORWARDED_FOR, SQ_SERVER
);
507 /* This creates an RFC 822 date */
508 $date = date('D, j M Y H:i:s ', time()) . $this->timezone();
510 /* Create a message-id */
511 $message_id = 'MESSAGE ID GENERATION ERROR! PLEASE CONTACT SQUIRRELMAIL DEVELOPERS';
512 if (empty($rfc822_header->message_id
)) {
514 /* user-specifc data to decrease collision chance */
515 $seed_data = $username . '.';
516 $seed_data .= (!empty($REMOTE_PORT) ?
$REMOTE_PORT . '.' : '');
517 $seed_data .= (!empty($REMOTE_ADDR) ?
$REMOTE_ADDR . '.' : '');
518 /* add the current time in milliseconds and randomness */
519 $seed_data .= uniqid(mt_rand(),true);
520 /* put it through one-way hash and add it to the ID */
521 $message_id .= md5($seed_data) . '.squirrel@' . $SERVER_NAME .'>';
524 /* Make an RFC822 Received: line */
525 if (isset($REMOTE_HOST)) {
526 $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
528 $received_from = $REMOTE_ADDR;
530 if (isset($HTTP_VIA) ||
isset ($HTTP_X_FORWARDED_FOR)) {
531 if (!isset($HTTP_X_FORWARDED_FOR) ||
$HTTP_X_FORWARDED_FOR == '') {
532 $HTTP_X_FORWARDED_FOR = 'unknown';
534 $received_from .= " (proxying for $HTTP_X_FORWARDED_FOR)";
539 * SquirrelMail header
541 * This Received: header provides information that allows to track
542 * user and machine that was used to send email. Don't remove it
543 * unless you understand all possible forging issues or your
544 * webmail installation does not prevent changes in user's email address.
545 * See SquirrelMail bug tracker #847107 for more details about it.
547 * Add $hide_squirrelmail_header as a candidate for config_local.php
548 * to allow completely hiding SquirrelMail participation in message
549 * processing; This is dangerous, especially if users can modify their
550 * account information, as it makes mapping a sent message back to the
551 * original sender almost impossible.
553 $show_sm_header = ( defined('hide_squirrelmail_header') ?
! hide_squirrelmail_header
: 1 );
555 // FIXME: The following headers may generate slightly differently between the message sent to the destination and that stored in the Sent folder because this code will be called before both actions. This is not necessarily a big problem, but other headers such as Message-ID and Date are preserved between both actions
556 if ( $show_sm_header ) {
557 if (isset($encode_header_key) &&
558 trim($encode_header_key)!='') {
559 // use encoded headers, if encryption key is set and not empty
560 $header[] = 'X-Squirrel-UserHash: '.OneTimePadEncrypt($username,base64_encode($encode_header_key)).$rn;
561 $header[] = 'X-Squirrel-FromHash: '.OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key)).$rn;
562 if (isset($HTTP_X_FORWARDED_FOR))
563 $header[] = 'X-Squirrel-ProxyHash:'.OneTimePadEncrypt($this->ip2hex($HTTP_X_FORWARDED_FOR),base64_encode($encode_header_key)).$rn;
565 // use default received headers
566 $header[] = "Received: from $received_from" . $rn;
567 if ($edit_identity ||
! isset($hide_auth_header) ||
! $hide_auth_header)
568 $header[] = " (SquirrelMail authenticated user $username)" . $rn;
569 $header[] = " by $SERVER_NAME with HTTP;" . $rn;
570 $header[] = " $date" . $rn;
574 /* Insert the rest of the header fields */
576 if (!empty($rfc822_header->message_id
)) {
577 $header[] = 'Message-ID: '. $rfc822_header->message_id
. $rn;
579 $header[] = 'Message-ID: '. $message_id . $rn;
580 $rfc822_header->message_id
= $message_id;
583 if (is_object($reply_rfc822_header) &&
584 isset($reply_rfc822_header->message_id
) &&
585 $reply_rfc822_header->message_id
) {
586 $rep_message_id = $reply_rfc822_header->message_id
;
587 $header[] = 'In-Reply-To: '.$rep_message_id . $rn;
588 $rfc822_header->in_reply_to
= $rep_message_id;
589 $references = $this->calculate_references($reply_rfc822_header);
590 $header[] = 'References: '.$references . $rn;
591 $rfc822_header->references
= $references;
594 if (!empty($rfc822_header->date
) && $rfc822_header->date
!= -1) {
595 $header[] = 'Date: '. $rfc822_header->date
. $rn;
597 $header[] = "Date: $date" . $rn;
598 $rfc822_header->date
= $date;
601 $header[] = 'Subject: '.encodeHeader($rfc822_header->subject
) . $rn;
602 $header[] = 'From: '. $rfc822_header->getAddr_s('from',",$rn ",true) . $rn;
604 // folding address list [From|To|Cc|Bcc] happens by using ",$rn<space>"
606 // Do not use foldLine for that.
608 // RFC2822 if from contains more then 1 address
609 if (count($rfc822_header->from
) > 1) {
610 $header[] = 'Sender: '. $rfc822_header->getAddr_s('sender',',',true) . $rn;
612 if (count($rfc822_header->to
)) {
613 $header[] = 'To: '. $rfc822_header->getAddr_s('to',",$rn ",true) . $rn;
615 if (count($rfc822_header->cc
)) {
616 $header[] = 'Cc: '. $rfc822_header->getAddr_s('cc',",$rn ",true) . $rn;
618 if (count($rfc822_header->reply_to
)) {
619 $header[] = 'Reply-To: '. $rfc822_header->getAddr_s('reply_to',',',true) . $rn;
621 /* Sendmail should return true. Default = false */
622 $bcc = $this->getBcc();
623 if (count($rfc822_header->bcc
)) {
624 $s = 'Bcc: '. $rfc822_header->getAddr_s('bcc',",$rn ",true) . $rn;
626 $raw_length +
= strlen($s);
631 /* Identify SquirrelMail */
632 $header[] = 'User-Agent: SquirrelMail/' . SM_VERSION
. $rn;
633 /* Do the MIME-stuff */
634 $header[] = 'MIME-Version: 1.0' . $rn;
635 $contenttype = 'Content-Type: '. $rfc822_header->content_type
->type0
.'/'.
636 $rfc822_header->content_type
->type1
;
637 if (count($rfc822_header->content_type
->properties
)) {
638 foreach ($rfc822_header->content_type
->properties
as $k => $v) {
640 $contenttype .= ';' .$k.'='.$v;
644 $header[] = $contenttype . $rn;
645 if ($encoding = $rfc822_header->encoding
) {
646 $header[] = 'Content-Transfer-Encoding: ' . $encoding . $rn;
648 if (isset($rfc822_header->dnt
) && $rfc822_header->dnt
) {
649 $dnt = $rfc822_header->getAddr_s('dnt');
651 $header[] = 'X-Confirm-Reading-To: '.$dnt. $rn;
653 $header[] = 'Disposition-Notification-To: '.$dnt. $rn;
655 if ($rfc822_header->priority
) {
656 switch($rfc822_header->priority
)
659 $header[] = 'X-Priority: 1 (Highest)'.$rn;
660 $header[] = 'Importance: High'. $rn; break;
662 $header[] = 'X-Priority: 5 (Lowest)'.$rn;
663 $header[] = 'Importance: Low'. $rn; break;
667 /* Insert headers from the $more_headers array */
668 if(count($rfc822_header->more_headers
)) {
669 reset($rfc822_header->more_headers
);
670 foreach ($rfc822_header->more_headers
as $k => $v) {
671 $header[] = $k.': '.$v .$rn;
674 $cnt = count($header);
677 for ($i = 0 ; $i < $cnt ; $i++
) {
678 $sKey = substr($header[$i],0,strpos($header[$i],':'));
683 $hdr_s .= $header[$i];
686 $sRefs = substr($header[$i],12);
687 $aRefs = explode(' ',$sRefs);
688 $sLine = 'References:';
689 foreach ($aRefs as $sReference) {
690 if ( trim($sReference) == '' ) {
691 /* Don't add spaces. */
692 } elseif (strlen($sLine)+
strlen($sReference) >76) {
694 $sLine = $rn . ' ' . $sReference;
696 $sLine .= ' '. $sReference;
705 $hdr_s .= $header[$i];
707 default: $hdr_s .= $this->foldLine($header[$i], 78, str_pad('',4)); break;
711 $header .= $rn; /* One blank line to separate header and body */
712 $raw_length +
= strlen($header);
717 * function foldLine - for cleanly folding of headerlines
719 * @param string $line
720 * @param integer $length length to fold the line at
721 * @param string $pre prefix the line with...
723 * @return string $line folded line with trailing CRLF
725 function foldLine($line, $length, $pre='') {
726 $line = substr($line,0, -2);
727 $length -= 2; /* do not fold between \r and \n */
728 $cnt = strlen($line);
729 if ($cnt > $length) { /* try folding */
730 $fold_string = "\r\n " . $pre;
732 $aFoldLine = array();
733 while (strlen($line) > $length) {
735 /* handle encoded parts */
736 if (preg_match('/(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(\s+|.*)/Ui',$line,$regs)) {
737 $fold_tmp = $regs[1];
738 if (!trim($regs[5])) {
739 $fold_tmp .= $regs[5];
741 $iPosEnc = strpos($line,$fold_tmp);
742 $iLengthEnc = strlen($fold_tmp);
743 $iPosEncEnd = $iPosEnc+
$iLengthEnc;
744 if ($iPosEnc < $length && (($iPosEncEnd) > $length)) {
746 /* fold just before the start of the encoded string */
748 $aFoldLine[] = substr($line,0,$iPosEnc);
750 $line = substr($line,$iPosEnc);
753 $length -= strlen($fold_string);
755 if ($iLengthEnc > $length) { /* place the encoded
756 string on a separate line and do not fold inside it*/
757 /* minimize foldstring */
758 $fold_string = "\r\n ";
759 $aFoldLine[] = substr($line,0,$iLengthEnc);
760 $line = substr($line,$iLengthEnc);
762 } else if ($iPosEnc < $length) { /* the encoded string fits into the foldlength */
764 $sLineRem = substr($line,$iPosEncEnd,$length - $iPosEncEnd);
765 if (preg_match('/^(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(.*)/Ui',$sLineRem) ||
!preg_match('/[=,;\s]/',$sLineRem)) {
766 /*impossible to fold clean in the next part -> fold after the enc string */
767 $aFoldLine[] = substr($line,0,$iPosEncEnd);
768 $line = substr($line,$iPosEncEnd);
772 $length -= strlen($fold_string);
778 $line_tmp = substr($line,0,$length);
780 /* try to fold at logical places */
783 case ($iFoldPos = strrpos($line_tmp,',')): break;
784 case ($iFoldPos = strrpos($line_tmp,';')): break;
785 case ($iFoldPos = strrpos($line_tmp,' ')): break;
786 case ($iFoldPos = strrpos($line_tmp,'=')): break;
790 if (!$iFoldPos) { /* clean folding didn't work */
793 $aFoldLine[] = substr($line,0,$iFoldPos+
1);
794 $line = substr($line,$iFoldPos+
1);
797 $length -= strlen($fold_string);
801 /*$reconstruct the line */
803 $aFoldLine[] = $line;
805 $line = implode($fold_string,$aFoldLine);
811 * function mimeBoundary - calculates the mime boundary to use
813 * This function will generate a random mime boundary base part
814 * for the message if the boundary has not already been set.
816 * @return string $mimeBoundaryString random mime boundary string
818 function mimeBoundary () {
819 static $mimeBoundaryString;
821 if ( !isset( $mimeBoundaryString ) ||
822 $mimeBoundaryString == '') {
823 $mimeBoundaryString = '----=_' . date( 'YmdHis' ) . '_' .
824 mt_rand( 10000, 99999 );
826 return $mimeBoundaryString;
830 * function timezone - Time offset for correct timezone
832 * @return string $result with timezone and offset
834 function timezone () {
837 $diff_second = date('Z');
839 $diff_second = - $diff_second;
841 if ($diff_second > 0) {
846 $diff_second = abs($diff_second);
847 $diff_hour = floor ($diff_second / 3600);
848 $diff_minute = floor (($diff_second-3600*$diff_hour) / 60);
849 $zonename = '('.strftime('%Z').')';
850 $result = sprintf ("%s%02d%02d %s", $sign, $diff_hour, $diff_minute,
856 * function calculate_references - calculate correct References string
857 * Adds the current message ID, and makes sure it doesn't grow forever,
858 * to that extent it drops message-ID's in a smart way until the string
859 * length is under the recommended value of 1000 ("References: <986>\r\n").
860 * It always keeps the first and the last three ID's.
862 * @param Rfc822Header $hdr message header to calculate from
864 * @return string $refer concatenated and trimmed References string
866 function calculate_references($hdr) {
867 $aReferences = preg_split('/\s+/', $hdr->references
);
868 $message_id = $hdr->message_id
;
869 $in_reply_to = $hdr->in_reply_to
;
871 // if References already exists, add the current message ID at the end.
872 // no References exists; if we know a IRT, add that aswell
873 if (count($aReferences) == 0 && $in_reply_to) {
874 $aReferences[] = $in_reply_to;
876 $aReferences[] = $message_id;
878 // sanitize the array: trim whitespace, remove dupes
879 array_walk($aReferences, 'sq_trim_value');
880 $aReferences = array_unique($aReferences);
882 while ( count($aReferences) > 4 && strlen(implode(' ', $aReferences)) >= 986 ) {
883 $aReferences = array_merge(array_slice($aReferences,0,1),array_slice($aReferences,2));
885 return implode(' ', $aReferences);
889 * Converts ip address to hexadecimal string
891 * Function is used to convert ipv4 and ipv6 addresses to hex strings.
892 * It removes all delimiter symbols from ip addresses, converts decimal
893 * ipv4 numbers to hex and pads strings in order to present full length
894 * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses
895 * are represented as 32 byte string.
897 * If function fails to detect address format, it returns unprocessed string.
898 * @param string $string ip address string
899 * @return string processed ip address string
900 * @since 1.5.1 and 1.4.5
902 function ip2hex($string) {
903 if (preg_match("/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/",$string,$match)) {
905 $ret = str_pad(dechex($match[1]),2,'0',STR_PAD_LEFT
)
906 . str_pad(dechex($match[2]),2,'0',STR_PAD_LEFT
)
907 . str_pad(dechex($match[3]),2,'0',STR_PAD_LEFT
)
908 . str_pad(dechex($match[4]),2,'0',STR_PAD_LEFT
);
909 } elseif (preg_match("/^([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)\:([0-9a-h]+)$/i",$string,$match)) {
911 $ret = str_pad($match[1],4,'0',STR_PAD_LEFT
)
912 . str_pad($match[2],4,'0',STR_PAD_LEFT
)
913 . str_pad($match[3],4,'0',STR_PAD_LEFT
)
914 . str_pad($match[4],4,'0',STR_PAD_LEFT
)
915 . str_pad($match[5],4,'0',STR_PAD_LEFT
)
916 . str_pad($match[6],4,'0',STR_PAD_LEFT
)
917 . str_pad($match[7],4,'0',STR_PAD_LEFT
)
918 . str_pad($match[8],4,'0',STR_PAD_LEFT
);
919 } elseif (preg_match("/^\:\:([0-9a-h\:]+)$/i",$string,$match)) {
920 // short ipv6 with all starting symbols nulled
921 $aAddr=explode(':',$match[1]);
923 foreach ($aAddr as $addr) {
924 $ret.=str_pad($addr,4,'0',STR_PAD_LEFT
);
926 $ret=str_pad($ret,32,'0',STR_PAD_LEFT
);
927 } elseif (preg_match("/^([0-9a-h\:]+)::([0-9a-h\:]+)$/i",$string,$match)) {
928 // short ipv6 with middle part nulled
929 $aStart=explode(':',$match[1]);
931 foreach($aStart as $addr) {
932 $sStart.=str_pad($addr,4,'0',STR_PAD_LEFT
);
934 $aEnd = explode(':',$match[2]);
936 foreach($aEnd as $addr) {
937 $sEnd.=str_pad($addr,4,'0',STR_PAD_LEFT
);
940 . str_pad('',(32 - strlen($sStart . $sEnd)),'0',STR_PAD_LEFT
)
943 // unknown addressing