introduce new constant SM_COPYRIGHT that has the copyright years
[squirrelmail.git] / class / deliver / Deliver.class.php
CommitLineData
4960ec8e 1<?php
4b4abf93 2
69298c28 3/**
4 * Deliver.class.php
5 *
69298c28 6 * This contains all the functions needed to send messages through
7 * a delivery backend.
8 *
4b4abf93 9 * @author Marc Groot Koerkamp
4b5049de 10 * @copyright &copy; 1999-2007 The SquirrelMail Project Team
4b4abf93 11 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
883d9cd3 12 * @version $Id$
72b004e6 13 * @package squirrelmail
69298c28 14 */
15
72b004e6 16/**
17 * Deliver Class - called to actually deliver the message
18 *
19 * This class is called by compose.php and other code that needs
20 * to send messages. All delivery functionality should be centralized
21 * in this class.
22 *
23 * Do not place UI code in this class, as UI code should be placed in templates
24 * going forward.
25 *
26 * @author Marc Groot Koerkamp
27 * @package squirrelmail
28 */
4960ec8e 29class Deliver {
30
72b004e6 31 /**
32 * function mail - send the message parts to the SMTP stream
33 *
0fdb0aa1 34 * @param Message $message Message object to send
10adeb76 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
e2ccf284 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)
0fdb0aa1 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)
e2ccf284 55 * @param mixed $extra Any implementation-specific variables
56 * can be passed in here and used in
57 * an overloaded version of this method
58 * if needed.
72b004e6 59 *
10adeb76 60 * @return integer The number of bytes written (or that would have been
61 * written) to the output stream.
b67d61ee 62 *
72b004e6 63 */
10adeb76 64 function mail(&$message, $stream=false, $reply_id=0, $reply_ent_id=0,
e2ccf284 65 $extra=NULL) {
66
10adeb76 67 $rfc822_header = &$message->rfc822_header;
68
78f39e78 69 if (count($message->entities)) {
70 $boundary = $this->mimeBoundary();
71 $rfc822_header->content_type->properties['boundary']='"'.$boundary.'"';
72 } else {
73 $boundary='';
74 }
75 $raw_length = 0;
0fdb0aa1 76
77
78 // calculate reply header if needed
79 //
80 if ($reply_id) {
81 global $imapConnection, $username, $imapServerAddress,
82 $imapPort, $mailbox;
e2ccf284 83
84 if (!is_resource($imapConnection))
0fdb0aa1 85 $imapConnection = sqimap_login($username, FALSE,
86 $imapServerAddress, $imapPort, 0);
87
88 sqimap_mailbox_select($imapConnection, $mailbox);
89 $reply_message = sqimap_get_message($imapConnection, $reply_id, $mailbox);
90
91 if ($reply_ent_id) {
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
97 */
98
99 $orig_header = $reply_message->rfc822_header; /* here is the envelope located */
100
101 } else {
102 $orig_header = $reply_message->rfc822_header;
103 }
08e33475 104 $message->reply_rfc822_header = $orig_header;
0fdb0aa1 105 }
0fdb0aa1 106
107
72b004e6 108 $reply_rfc822_header = (isset($message->reply_rfc822_header)
78f39e78 109 ? $message->reply_rfc822_header : '');
110 $header = $this->prepareRFC822_Header($rfc822_header, $reply_rfc822_header, $raw_length);
75b14a70 111
e2ccf284 112 $this->send_mail($message, $header, $boundary, $stream, $raw_length, $extra);
113
10adeb76 114 return $raw_length;
e2ccf284 115 }
116
117 /**
118 * function send_mail - send the message parts to the IMAP stream
119 *
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
136 * if needed.
137 *
138 * @return void
139 *
140 */
141 function send_mail($message, $header, $boundary, $stream=false,
142 &$raw_length, $extra=NULL) {
143
144
78f39e78 145 if ($stream) {
59387d1c 146 $this->preWriteToStream($header);
147 $this->writeToStream($stream, $header);
78f39e78 148 }
149 $this->writeBody($message, $stream, $raw_length, $boundary);
3b53d87f 150 }
78f39e78 151
72b004e6 152 /**
153 * function writeBody - generate and write the mime boundaries around each part to the stream
154 *
155 * Recursively formats and writes the MIME boundaries of the $message
156 * to the output stream.
157 *
158 * @param Message $message Message object to transform
159 * @param resource $stream SMTP output stream
e2ccf284 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)
72b004e6 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
168 *
3a70ee56 169 * @return void
72b004e6 170 */
3b53d87f 171 function writeBody($message, $stream, &$length_raw, $boundary='') {
caf6a9a4 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_'));
ba86230e 176
593d083d 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
ba86230e 182 //
183 } else if (strpos($boundary,'_trap_')) {
184 $boundary = substr(strrev($boundary),0,strpos(strrev($boundary),'_part_'));
caf6a9a4 185 }
ba86230e 186 $boundary_new = strrev($boundary . '_part_'.$message->entity_id);
caf6a9a4 187 } else {
188 $boundary_new = $boundary;
189 }
1274f430 190 if ($boundary && !$message->rfc822_header) {
78f39e78 191 $s = '--'.$boundary."\r\n";
caf6a9a4 192 $s .= $this->prepareMIME_Header($message, $boundary_new);
78f39e78 193 $length_raw += strlen($s);
194 if ($stream) {
3b53d87f 195 $this->preWriteToStream($s);
78f39e78 196 $this->writeToStream($stream, $s);
197 }
198 }
199 $this->writeBodyPart($message, $stream, $length_raw);
72b004e6 200
78f39e78 201 $last = false;
202 for ($i=0, $entCount=count($message->entities);$i<$entCount;$i++) {
caf6a9a4 203 $msg = $this->writeBody($message->entities[$i], $stream, $length_raw, $boundary_new);
78f39e78 204 if ($i == $entCount-1) $last = true;
205 }
1274f430 206 if ($boundary && $last) {
dcc5c913 207 $s = "--".$boundary_new."--\r\n\r\n";
78f39e78 208 $length_raw += strlen($s);
209 if ($stream) {
3b53d87f 210 $this->preWriteToStream($s);
78f39e78 211 $this->writeToStream($stream, $s);
212 }
213 }
3b53d87f 214 }
215
72b004e6 216 /**
217 * function writeBodyPart - write each individual mimepart
218 *
219 * Recursively called by WriteBody to write each mime part to the SMTP stream
220 *
221 * @param Message $message Message object to transform
222 * @param resource $stream SMTP output stream
e2ccf284 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)
72b004e6 228 * @param integer &$length length of the message part
229 * as returned by mail fn
230 *
3a70ee56 231 * @return void
72b004e6 232 */
3b53d87f 233 function writeBodyPart($message, $stream, &$length) {
1274f430 234 if ($message->mime_header) {
78f39e78 235 $type0 = $message->mime_header->type0;
236 } else {
237 $type0 = $message->rfc822_header->content_type->type0;
238 }
239
240 $body_part_trailing = $last = '';
241 switch ($type0)
242 {
243 case 'text':
244 case 'message':
245 if ($message->body_part) {
246 $body_part = $message->body_part;
4cf85ddd 247 // remove NUL characters
248 $body_part = str_replace("\0",'',$body_part);
78f39e78 249 $length += $this->clean_crlf($body_part);
250 if ($stream) {
72b004e6 251 $this->preWriteToStream($body_part);
78f39e78 252 $this->writeToStream($stream, $body_part);
253 }
254 $last = $body_part;
255 } elseif ($message->att_local_name) {
1f270d3c 256 global $username, $attachment_dir;
257 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
78f39e78 258 $filename = $message->att_local_name;
1f270d3c 259 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
78f39e78 260 while ($body_part = fgets($file, 4096)) {
257dea3f 261 // remove NUL characters
262 $body_part = str_replace("\0",'',$body_part);
78f39e78 263 $length += $this->clean_crlf($body_part);
264 if ($stream) {
265 $this->preWriteToStream($body_part);
266 $this->writeToStream($stream, $body_part);
267 }
268 $last = $body_part;
269 }
270 fclose($file);
271 }
272 break;
273 default:
274 if ($message->body_part) {
275 $body_part = $message->body_part;
257dea3f 276 // remove NUL characters
277 $body_part = str_replace("\0",'',$body_part);
78f39e78 278 $length += $this->clean_crlf($body_part);
279 if ($stream) {
280 $this->writeToStream($stream, $body_part);
281 }
282 } elseif ($message->att_local_name) {
1f270d3c 283 global $username, $attachment_dir;
284 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
78f39e78 285 $filename = $message->att_local_name;
1f270d3c 286 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
78f39e78 287 while ($tmp = fread($file, 570)) {
27ff4efb 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" )
293 $body_part .= "\n";
78f39e78 294 $length += $this->clean_crlf($body_part);
295 if ($stream) {
296 $this->writeToStream($stream, $body_part);
297 }
298 }
299 fclose($file);
3b53d87f 300 }
78f39e78 301 break;
302 }
303 $body_part_trailing = '';
304 if ($last && substr($last,-1) != "\n") {
305 $body_part_trailing = "\r\n";
306 }
307 if ($body_part_trailing) {
308 $length += strlen($body_part_trailing);
309 if ($stream) {
310 $this->preWriteToStream($body_part_trailing);
311 $this->writeToStream($stream, $body_part_trailing);
312 }
313 }
3b53d87f 314 }
78f39e78 315
72b004e6 316 /**
317 * function clean_crlf - change linefeeds and newlines to legal characters
318 *
319 * The SMTP format only allows CRLF as line terminators.
320 * This function replaces illegal teminators with the correct terminator.
321 *
322 * @param string &$s string to clean linefeeds on
323 *
3a70ee56 324 * @return void
72b004e6 325 */
69298c28 326 function clean_crlf(&$s) {
3b53d87f 327 $s = str_replace("\r\n", "\n", $s);
328 $s = str_replace("\r", "\n", $s);
329 $s = str_replace("\n", "\r\n", $s);
78f39e78 330 return strlen($s);
3b53d87f 331 }
78f39e78 332
72b004e6 333 /**
334 * function strip_crlf - strip linefeeds and newlines from a string
335 *
336 * The SMTP format only allows CRLF as line terminators.
337 * This function strips all line terminators from the string.
338 *
339 * @param string &$s string to clean linefeeds on
340 *
3a70ee56 341 * @return void
72b004e6 342 */
e4f9307a 343 function strip_crlf(&$s) {
344 $s = str_replace("\r\n ", '', $s);
78f39e78 345 $s = str_replace("\r", '', $s);
346 $s = str_replace("\n", '', $s);
e4f9307a 347 }
3b53d87f 348
72b004e6 349 /**
350 * function preWriteToStream - reserved for extended functionality
351 *
352 * This function is not yet implemented.
353 * Reserved for extended functionality.
354 *
355 * @param string &$s string to operate on
356 *
3a70ee56 357 * @return void
72b004e6 358 */
69298c28 359 function preWriteToStream(&$s) {
3b53d87f 360 }
78f39e78 361
72b004e6 362 /**
363 * function writeToStream - write data to the SMTP stream
364 *
365 * @param resource $stream SMTP output stream
366 * @param string $data string with data to send to the SMTP stream
367 *
3a70ee56 368 * @return void
72b004e6 369 */
3b53d87f 370 function writeToStream($stream, $data) {
78f39e78 371 fputs($stream, $data);
3b53d87f 372 }
78f39e78 373
72b004e6 374 /**
375 * function initStream - reserved for extended functionality
376 *
377 * This function is not yet implemented.
378 * Reserved for extended functionality.
379 *
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
385 *
3a70ee56 386 * @return handle $stream file handle resource to SMTP stream
72b004e6 387 */
3b53d87f 388 function initStream($message, $length=0, $host='', $port='', $user='', $pass='') {
78f39e78 389 return $stream;
3b53d87f 390 }
72b004e6 391
392 /**
393 * function getBCC - reserved for extended functionality
394 *
395 * This function is not yet implemented.
396 * Reserved for extended functionality.
397 *
398 */
399 function getBCC() {
78f39e78 400 return false;
59387d1c 401 }
3b53d87f 402
72b004e6 403 /**
404 * function prepareMIME_Header - creates the mime header
405 *
406 * @param Message $message Message object to act on
407 * @param string $boundary mime boundary from fn MimeBoundary
408 *
3a70ee56 409 * @return string $header properly formatted mime header
72b004e6 410 */
3b53d87f 411 function prepareMIME_Header($message, $boundary) {
78f39e78 412 $mime_header = $message->mime_header;
413 $rn="\r\n";
414 $header = array();
1274f430 415
78f39e78 416 $contenttype = 'Content-Type: '. $mime_header->type0 .'/'.
417 $mime_header->type1;
418 if (count($message->entities)) {
fcce1b3c 419 $contenttype .= ';' . 'boundary="'.$boundary.'"';
78f39e78 420 }
421 if (isset($mime_header->parameters['name'])) {
422 $contenttype .= '; name="'.
423 encodeHeader($mime_header->parameters['name']). '"';
424 }
425 if (isset($mime_header->parameters['charset'])) {
426 $charset = $mime_header->parameters['charset'];
427 $contenttype .= '; charset="'.
428 encodeHeader($charset). '"';
429 }
1274f430 430
78f39e78 431 $header[] = $contenttype . $rn;
432 if ($mime_header->description) {
eb72e151 433 $header[] = 'Content-Description: ' . $mime_header->description . $rn;
78f39e78 434 }
435 if ($mime_header->encoding) {
eb72e151 436 $header[] = 'Content-Transfer-Encoding: ' . $mime_header->encoding . $rn;
78f39e78 437 } else {
438 if ($mime_header->type0 == 'text' || $mime_header->type0 == 'message') {
eb72e151 439 $header[] = 'Content-Transfer-Encoding: 8bit' . $rn;
c6dd29ba 440 } else if ($mime_header->type0 == 'multipart' || $mime_header->type0 == 'alternative') {
441 /* no-op; no encoding needed */
78f39e78 442 } else {
eb72e151 443 $header[] = 'Content-Transfer-Encoding: base64' . $rn;
78f39e78 444 }
445 }
446 if ($mime_header->id) {
eb72e151 447 $header[] = 'Content-ID: ' . $mime_header->id . $rn;
78f39e78 448 }
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')). '"';
455 }
72b004e6 456 $header[] = $contentdisp . $rn;
78f39e78 457 }
458 if ($mime_header->md5) {
eb72e151 459 $header[] = 'Content-MD5: ' . $mime_header->md5 . $rn;
78f39e78 460 }
461 if ($mime_header->language) {
eb72e151 462 $header[] = 'Content-Language: ' . $mime_header->language . $rn;
78f39e78 463 }
4960ec8e 464
78f39e78 465 $cnt = count($header);
466 $hdr_s = '';
467 for ($i = 0 ; $i < $cnt ; $i++) {
468 $hdr_s .= $this->foldLine($header[$i], 78,str_pad('',4));
469 }
470 $header = $hdr_s;
471 $header .= $rn; /* One blank line to separate mimeheader and body-entity */
472 return $header;
473 }
4960ec8e 474
72b004e6 475 /**
476 * function prepareRFC822_Header - prepares the RFC822 header string from Rfc822Header object(s)
477 *
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.
481 *
482 * @param Rfc822Header $rfc822_header
483 * @param Rfc822Header $reply_rfc822_header
484 * @param integer &$raw_length length of the message
485 *
3a70ee56 486 * @return string $header
72b004e6 487 */
10adeb76 488 function prepareRFC822_Header(&$rfc822_header, $reply_rfc822_header, &$raw_length) {
b37e457f 489 global $domain, $username, $encode_header_key,
e930c9fe 490 $edit_identity, $hide_auth_header;
60a46e65 491
49c7f411 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) {
60a46e65 496 $SERVER_NAME = $domain;
78f39e78 497 }
60a46e65 498
78f39e78 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);
60a46e65 504
78f39e78 505 $rn = "\r\n";
223872cb 506
78f39e78 507 /* This creates an RFC 822 date */
867fed37 508 $date = date('D, j M Y H:i:s ', time()) . $this->timezone();
49c7f411 509
78f39e78 510 /* Create a message-id */
10adeb76 511 $message_id = 'MESSAGE ID GENERATION ERROR! PLEASE CONTACT SQUIRRELMAIL DEVELOPERS';
512 if (empty($rfc822_header->message_id)) {
513 $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 .'>';
522 }
b67d61ee 523
78f39e78 524 /* Make an RFC822 Received: line */
525 if (isset($REMOTE_HOST)) {
526 $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
527 } else {
528 $received_from = $REMOTE_ADDR;
529 }
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';
533 }
534 $received_from .= " (proxying for $HTTP_X_FORWARDED_FOR)";
535 }
536 $header = array();
432db2fc 537
538 /**
539 * SquirrelMail header
540 *
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.
bb08da84 546 *
547 * Add $hide_squirrelmail_header as a candidate for config_local.php
548 * to allow completely hiding SquirrelMail participation in message
539a323f 549 * processing; This is dangerous, especially if users can modify their
e930c9fe 550 * account information, as it makes mapping a sent message back to the
551 * original sender almost impossible.
432db2fc 552 */
e930c9fe 553 $show_sm_header = ( defined('hide_squirrelmail_header') ? ! hide_squirrelmail_header : 1 );
bb08da84 554
10adeb76 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
bb08da84 556 if ( $show_sm_header ) {
557 if (isset($encode_header_key) &&
432db2fc 558 trim($encode_header_key)!='') {
559 // use encoded headers, if encryption key is set and not empty
b81efcb4 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;
432db2fc 562 if (isset($HTTP_X_FORWARDED_FOR))
b81efcb4 563 $header[] = 'X-Squirrel-ProxyHash:'.OneTimePadEncrypt($this->ip2hex($HTTP_X_FORWARDED_FOR),base64_encode($encode_header_key)).$rn;
bb08da84 564 } else {
432db2fc 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;
bb08da84 571 }
35aaf666 572 }
432db2fc 573
59387d1c 574 /* Insert the rest of the header fields */
10adeb76 575
576 if (!empty($rfc822_header->message_id)) {
577 $header[] = 'Message-ID: '. $rfc822_header->message_id . $rn;
578 } else {
579 $header[] = 'Message-ID: '. $message_id . $rn;
580 $rfc822_header->message_id = $message_id;
581 }
582
ea87de81 583 if (is_object($reply_rfc822_header) &&
b4316b34 584 isset($reply_rfc822_header->message_id) &&
585 $reply_rfc822_header->message_id) {
78f39e78 586 $rep_message_id = $reply_rfc822_header->message_id;
78f39e78 587 $header[] = 'In-Reply-To: '.$rep_message_id . $rn;
10adeb76 588 $rfc822_header->in_reply_to = $rep_message_id;
78f39e78 589 $references = $this->calculate_references($reply_rfc822_header);
590 $header[] = 'References: '.$references . $rn;
10adeb76 591 $rfc822_header->references = $references;
78f39e78 592 }
10adeb76 593
594 if (!empty($rfc822_header->date) && $rfc822_header->date != -1) {
595 $header[] = 'Date: '. $rfc822_header->date . $rn;
596 } else {
597 $header[] = "Date: $date" . $rn;
598 $rfc822_header->date = $date;
599 }
600
59387d1c 601 $header[] = 'Subject: '.encodeHeader($rfc822_header->subject) . $rn;
ac47827c 602 $header[] = 'From: '. $rfc822_header->getAddr_s('from',",$rn ",true) . $rn;
603
ea87de81 604 // folding address list [From|To|Cc|Bcc] happens by using ",$rn<space>"
605 // as delimiter
ac47827c 606 // Do not use foldLine for that.
607
608 // RFC2822 if from contains more then 1 address
59387d1c 609 if (count($rfc822_header->from) > 1) {
215de78c 610 $header[] = 'Sender: '. $rfc822_header->getAddr_s('sender',',',true) . $rn;
78f39e78 611 }
612 if (count($rfc822_header->to)) {
ac47827c 613 $header[] = 'To: '. $rfc822_header->getAddr_s('to',",$rn ",true) . $rn;
78f39e78 614 }
615 if (count($rfc822_header->cc)) {
ac47827c 616 $header[] = 'Cc: '. $rfc822_header->getAddr_s('cc',",$rn ",true) . $rn;
78f39e78 617 }
618 if (count($rfc822_header->reply_to)) {
215de78c 619 $header[] = 'Reply-To: '. $rfc822_header->getAddr_s('reply_to',',',true) . $rn;
78f39e78 620 }
621 /* Sendmail should return true. Default = false */
622 $bcc = $this->getBcc();
623 if (count($rfc822_header->bcc)) {
ac47827c 624 $s = 'Bcc: '. $rfc822_header->getAddr_s('bcc',",$rn ",true) . $rn;
78f39e78 625 if (!$bcc) {
78f39e78 626 $raw_length += strlen($s);
627 } else {
628 $header[] = $s;
629 }
630 }
72b004e6 631 /* Identify SquirrelMail */
b37e457f 632 $header[] = 'User-Agent: SquirrelMail/' . SM_VERSION . $rn;
78f39e78 633 /* Do the MIME-stuff */
634 $header[] = 'MIME-Version: 1.0' . $rn;
635 $contenttype = 'Content-Type: '. $rfc822_header->content_type->type0 .'/'.
59387d1c 636 $rfc822_header->content_type->type1;
78f39e78 637 if (count($rfc822_header->content_type->properties)) {
638 foreach ($rfc822_header->content_type->properties as $k => $v) {
639 if ($k && $v) {
72b004e6 640 $contenttype .= ';' .$k.'='.$v;
78f39e78 641 }
642 }
643 }
59387d1c 644 $header[] = $contenttype . $rn;
78f39e78 645 if ($encoding = $rfc822_header->encoding) {
23301b33 646 $header[] = 'Content-Transfer-Encoding: ' . $encoding . $rn;
72b004e6 647 }
539a323f 648 if (isset($rfc822_header->dnt) && $rfc822_header->dnt) {
72b004e6 649 $dnt = $rfc822_header->getAddr_s('dnt');
78f39e78 650 /* Pegasus Mail */
651 $header[] = 'X-Confirm-Reading-To: '.$dnt. $rn;
652 /* RFC 2298 */
653 $header[] = 'Disposition-Notification-To: '.$dnt. $rn;
654 }
655 if ($rfc822_header->priority) {
d1db3699 656 switch($rfc822_header->priority)
78f39e78 657 {
d1db3699 658 case 1:
91e0dccc 659 $header[] = 'X-Priority: 1 (Highest)'.$rn;
660 $header[] = 'Importance: High'. $rn; break;
d1db3699 661 case 5:
91e0dccc 662 $header[] = 'X-Priority: 5 (Lowest)'.$rn;
663 $header[] = 'Importance: Low'. $rn; break;
78f39e78 664 default: break;
665 }
666 }
72b004e6 667 /* Insert headers from the $more_headers array */
78f39e78 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;
672 }
673 }
674 $cnt = count($header);
675 $hdr_s = '';
5d2a7b15 676
78f39e78 677 for ($i = 0 ; $i < $cnt ; $i++) {
5d2a7b15 678 $sKey = substr($header[$i],0,strpos($header[$i],':'));
679 switch ($sKey)
680 {
681 case 'Message-ID':
682 case 'In-Reply_To':
683 $hdr_s .= $header[$i];
684 break;
685 case 'References':
686 $sRefs = substr($header[$i],12);
687 $aRefs = explode(' ',$sRefs);
688 $sLine = 'References:';
689 foreach ($aRefs as $sReference) {
cc201b46 690 if ( trim($sReference) == '' ) {
691 /* Don't add spaces. */
692 } elseif (strlen($sLine)+strlen($sReference) >76) {
5d2a7b15 693 $hdr_s .= $sLine;
694 $sLine = $rn . ' ' . $sReference;
695 } else {
696 $sLine .= ' '. $sReference;
697 }
698 }
699 $hdr_s .= $sLine;
700 break;
ac47827c 701 case 'To':
702 case 'Cc':
703 case 'Bcc':
704 case 'From':
705 $hdr_s .= $header[$i];
706 break;
5d2a7b15 707 default: $hdr_s .= $this->foldLine($header[$i], 78, str_pad('',4)); break;
708 }
78f39e78 709 }
78f39e78 710 $header = $hdr_s;
711 $header .= $rn; /* One blank line to separate header and body */
712 $raw_length += strlen($header);
713 return $header;
4960ec8e 714 }
715
72b004e6 716 /**
717 * function foldLine - for cleanly folding of headerlines
718 *
719 * @param string $line
720 * @param integer $length length to fold the line at
721 * @param string $pre prefix the line with...
722 *
3a70ee56 723 * @return string $line folded line with trailing CRLF
72b004e6 724 */
1274f430 725 function foldLine($line, $length, $pre='') {
e4f9307a 726 $line = substr($line,0, -2);
fb1d6cb6 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;
731 $bFirstFold = false;
732 $aFoldLine = array();
733 while (strlen($line) > $length) {
734 $fold = false;
735 /* handle encoded parts */
7060c7f0 736 if (preg_match('/(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(\s+|.*)/Ui',$line,$regs)) {
fb1d6cb6 737 $fold_tmp = $regs[1];
7060c7f0 738 if (!trim($regs[5])) {
739 $fold_tmp .= $regs[5];
740 }
fb1d6cb6 741 $iPosEnc = strpos($line,$fold_tmp);
742 $iLengthEnc = strlen($fold_tmp);
c422e824 743 $iPosEncEnd = $iPosEnc+$iLengthEnc;
744 if ($iPosEnc < $length && (($iPosEncEnd) > $length)) {
fb1d6cb6 745 $fold = true;
746 /* fold just before the start of the encoded string */
7060c7f0 747 if ($iPosEnc) {
748 $aFoldLine[] = substr($line,0,$iPosEnc);
749 }
fb1d6cb6 750 $line = substr($line,$iPosEnc);
751 if (!$bFirstFold) {
752 $bFirstFold = true;
753 $length -= strlen($fold_string);
754 }
755 if ($iLengthEnc > $length) { /* place the encoded
756 string on a separate line and do not fold inside it*/
223872cb 757 /* minimize foldstring */
758 $fold_string = "\r\n ";
759 $aFoldLine[] = substr($line,0,$iLengthEnc);
760 $line = substr($line,$iLengthEnc);
72b004e6 761 }
c422e824 762 } else if ($iPosEnc < $length) { /* the encoded string fits into the foldlength */
fb1d6cb6 763 /*remainder */
fb1d6cb6 764 $sLineRem = substr($line,$iPosEncEnd,$length - $iPosEncEnd);
c422e824 765 if (preg_match('/^(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)(.*)/Ui',$sLineRem) || !preg_match('/[=,;\s]/',$sLineRem)) {
fb1d6cb6 766 /*impossible to fold clean in the next part -> fold after the enc string */
7060c7f0 767 $aFoldLine[] = substr($line,0,$iPosEncEnd);
768 $line = substr($line,$iPosEncEnd);
fb1d6cb6 769 $fold = true;
770 if (!$bFirstFold) {
771 $bFirstFold = true;
772 $length -= strlen($fold_string);
773 }
72b004e6 774 }
fb1d6cb6 775 }
776 }
777 if (!$fold) {
778 $line_tmp = substr($line,0,$length);
779 $iFoldPos = false;
780 /* try to fold at logical places */
781 switch (true)
782 {
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;
787 default: break;
788 }
72b004e6 789
fb1d6cb6 790 if (!$iFoldPos) { /* clean folding didn't work */
791 $iFoldPos = $length;
792 }
793 $aFoldLine[] = substr($line,0,$iFoldPos+1);
794 $line = substr($line,$iFoldPos+1);
795 if (!$bFirstFold) {
796 $bFirstFold = true;
797 $length -= strlen($fold_string);
798 }
799 }
800 }
801 /*$reconstruct the line */
802 if ($line) {
803 $aFoldLine[] = $line;
804 }
805 $line = implode($fold_string,$aFoldLine);
806 }
807 return $line."\r\n";
808 }
3b53d87f 809
72b004e6 810 /**
811 * function mimeBoundary - calculates the mime boundary to use
812 *
813 * This function will generate a random mime boundary base part
814 * for the message if the boundary has not already been set.
815 *
3a70ee56 816 * @return string $mimeBoundaryString random mime boundary string
72b004e6 817 */
59387d1c 818 function mimeBoundary () {
78f39e78 819 static $mimeBoundaryString;
3b53d87f 820
78f39e78 821 if ( !isset( $mimeBoundaryString ) ||
822 $mimeBoundaryString == '') {
823 $mimeBoundaryString = '----=_' . date( 'YmdHis' ) . '_' .
824 mt_rand( 10000, 99999 );
825 }
826 return $mimeBoundaryString;
3b53d87f 827 }
828
72b004e6 829 /**
830 * function timezone - Time offset for correct timezone
831 *
3a70ee56 832 * @return string $result with timezone and offset
72b004e6 833 */
59387d1c 834 function timezone () {
78f39e78 835 global $invert_time;
836
837 $diff_second = date('Z');
838 if ($invert_time) {
839 $diff_second = - $diff_second;
840 }
841 if ($diff_second > 0) {
842 $sign = '+';
843 } else {
844 $sign = '-';
845 }
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').')';
72b004e6 850 $result = sprintf ("%s%02d%02d %s", $sign, $diff_hour, $diff_minute,
3b53d87f 851 $zonename);
78f39e78 852 return ($result);
59387d1c 853 }
3b53d87f 854
72b004e6 855 /**
f2ac3325 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.
72b004e6 861 *
862 * @param Rfc822Header $hdr message header to calculate from
863 *
f2ac3325 864 * @return string $refer concatenated and trimmed References string
72b004e6 865 */
1274f430 866 function calculate_references($hdr) {
f2ac3325 867 $aReferences = preg_split('/\s+/', $hdr->references);
78f39e78 868 $message_id = $hdr->message_id;
869 $in_reply_to = $hdr->in_reply_to;
539a323f 870
f2ac3325 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;
875 }
876 $aReferences[] = $message_id;
877
878 // sanitize the array: trim whitespace, remove dupes
86e6a9eb 879 array_walk($aReferences, 'sq_trim_value');
f2ac3325 880 $aReferences = array_unique($aReferences);
881
882 while ( count($aReferences) > 4 && strlen(implode(' ', $aReferences)) >= 986 ) {
883 $aReferences = array_merge(array_slice($aReferences,0,1),array_slice($aReferences,2));
78f39e78 884 }
f2ac3325 885 return implode(' ', $aReferences);
886 }
887
432db2fc 888 /**
889 * Converts ip address to hexadecimal string
890 *
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
ea87de81 894 * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses
432db2fc 895 * are represented as 32 byte string.
896 *
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
94511d23 900 * @since 1.5.1 and 1.4.5
432db2fc 901 */
902 function ip2hex($string) {
903 if (preg_match("/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/",$string,$match)) {
904 // ipv4 address
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)) {
910 // full ipv6 address
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]);
922 $ret='';
923 foreach ($aAddr as $addr) {
924 $ret.=str_pad($addr,4,'0',STR_PAD_LEFT);
925 }
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]);
930 $sStart='';
931 foreach($aStart as $addr) {
932 $sStart.=str_pad($addr,4,'0',STR_PAD_LEFT);
933 }
934 $aEnd = explode(':',$match[2]);
935 $sEnd='';
936 foreach($aEnd as $addr) {
937 $sEnd.=str_pad($addr,4,'0',STR_PAD_LEFT);
938 }
939 $ret = $sStart
940 . str_pad('',(32 - strlen($sStart . $sEnd)),'0',STR_PAD_LEFT)
941 . $sEnd;
942 } else {
943 // unknown addressing
944 $ret = $string;
945 }
946 return $ret;
947 }
59387d1c 948}