fsf changes, meant to be rebased on upstream
[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
77a1e3d1 10 * @copyright 1999-2022 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)
69bb88aa 55 * @param resource $imap_stream If there is an open IMAP stream in
56 * the caller's context, it should be
57 * passed in here. This is OPTIONAL,
58 * as one will be created if not given,
59 * but as some IMAP servers may baulk
60 * at opening more than one connection
61 * at a time, the caller should always
62 * abide if possible. Currently, this
63 * stream is only used when $reply_id
64 * is also non-zero, but that is subject
65 * to change.
33f0da43 66 * @param mixed $extra Any implementation-specific variables
67 * can be passed in here and used in
68 * an overloaded version of this method
69 * if needed.
72b004e6 70 *
10adeb76 71 * @return integer The number of bytes written (or that would have been
72 * written) to the output stream.
b67d61ee 73 *
72b004e6 74 */
10adeb76 75 function mail(&$message, $stream=false, $reply_id=0, $reply_ent_id=0,
33f0da43 76 $imap_stream=NULL, $extra=NULL) {
e2ccf284 77
10adeb76 78 $rfc822_header = &$message->rfc822_header;
79
78f39e78 80 if (count($message->entities)) {
81 $boundary = $this->mimeBoundary();
82 $rfc822_header->content_type->properties['boundary']='"'.$boundary.'"';
83 } else {
84 $boundary='';
85 }
86 $raw_length = 0;
0fdb0aa1 87
88
89 // calculate reply header if needed
90 //
91 if ($reply_id) {
92 global $imapConnection, $username, $imapServerAddress,
a9805897 93 $imapPort, $imap_stream_options, $mailbox;
e2ccf284 94
69bb88aa 95 // try our best to use an existing IMAP handle
96 //
97 $close_imap_stream = FALSE;
98 if (is_resource($imap_stream)) {
99 $my_imap_stream = $imap_stream;
100
101 } else if (is_resource($imapConnection)) {
102 $my_imap_stream = $imapConnection;
103
104 } else {
105 $close_imap_stream = TRUE;
9aaa9ae2 106 $my_imap_stream = sqimap_login($username, FALSE, $imapServerAddress,
a9805897 107 $imapPort, 0, $imap_stream_options);
69bb88aa 108 }
109
110 sqimap_mailbox_select($my_imap_stream, $mailbox);
111 $reply_message = sqimap_get_message($my_imap_stream, $reply_id, $mailbox);
0fdb0aa1 112
69bb88aa 113 if ($close_imap_stream) {
114 sqimap_logout($my_imap_stream);
115 }
0fdb0aa1 116
117 if ($reply_ent_id) {
118 /* redefine the messsage in case of message/rfc822 */
119 $reply_message = $message->getEntity($reply_ent_id);
120 /* message is an entity which contains the envelope and type0=message
121 * and type1=rfc822. The actual entities are childs from
122 * $reply_message->entities[0]. That's where the encoding and is located
123 */
124
125 $orig_header = $reply_message->rfc822_header; /* here is the envelope located */
126
127 } else {
128 $orig_header = $reply_message->rfc822_header;
129 }
08e33475 130 $message->reply_rfc822_header = $orig_header;
0fdb0aa1 131 }
0fdb0aa1 132
133
72b004e6 134 $reply_rfc822_header = (isset($message->reply_rfc822_header)
78f39e78 135 ? $message->reply_rfc822_header : '');
136 $header = $this->prepareRFC822_Header($rfc822_header, $reply_rfc822_header, $raw_length);
75b14a70 137
e2ccf284 138 $this->send_mail($message, $header, $boundary, $stream, $raw_length, $extra);
139
10adeb76 140 return $raw_length;
e2ccf284 141 }
142
143 /**
144 * function send_mail - send the message parts to the IMAP stream
145 *
146 * @param Message $message Message object to send
147 * @param string $header Headers ready to send
148 * @param string $boundary Message parts boundary
149 * @param resource $stream Handle to the SMTP stream
150 * (when FALSE, nothing will be
151 * written to the stream; this can
152 * be used to determine the actual
153 * number of bytes that will be
154 * written to the stream)
155 * @param int &$raw_length The number of bytes written (or that
156 * would have been written) to the
157 * output stream - NOTE that this is
158 * passed by reference
159 * @param mixed $extra Any implementation-specific variables
160 * can be passed in here and used in
161 * an overloaded version of this method
162 * if needed.
163 *
164 * @return void
165 *
166 */
167 function send_mail($message, $header, $boundary, $stream=false,
5e1d5b0b 168 &$raw_length=0, $extra=NULL) {
e2ccf284 169
170
78f39e78 171 if ($stream) {
59387d1c 172 $this->preWriteToStream($header);
173 $this->writeToStream($stream, $header);
78f39e78 174 }
175 $this->writeBody($message, $stream, $raw_length, $boundary);
3b53d87f 176 }
78f39e78 177
72b004e6 178 /**
179 * function writeBody - generate and write the mime boundaries around each part to the stream
180 *
181 * Recursively formats and writes the MIME boundaries of the $message
182 * to the output stream.
183 *
184 * @param Message $message Message object to transform
185 * @param resource $stream SMTP output stream
e2ccf284 186 * (when FALSE, nothing will be
187 * written to the stream; this can
188 * be used to determine the actual
189 * number of bytes that will be
190 * written to the stream)
72b004e6 191 * @param integer &$length_raw raw length of the message (part)
192 * as returned by mail fn
193 * @param string $boundary custom boundary to call, usually for subparts
194 *
3a70ee56 195 * @return void
72b004e6 196 */
3b53d87f 197 function writeBody($message, $stream, &$length_raw, $boundary='') {
caf6a9a4 198 // calculate boundary in case of multidimensional mime structures
199 if ($boundary && $message->entity_id && count($message->entities)) {
200 if (strpos($boundary,'_part_')) {
201 $boundary = substr($boundary,0,strpos($boundary,'_part_'));
ba86230e 202
593d083d 203 // the next four lines use strrev to reverse any nested boundaries
204 // because RFC 2046 (5.1.1) says that if a line starts with the outer
205 // boundary string (doesn't matter what the line ends with), that
206 // can be considered a match for the outer boundary; thus the nested
207 // boundary needs to be unique from the outer one
ba86230e 208 //
209 } else if (strpos($boundary,'_trap_')) {
210 $boundary = substr(strrev($boundary),0,strpos(strrev($boundary),'_part_'));
caf6a9a4 211 }
ba86230e 212 $boundary_new = strrev($boundary . '_part_'.$message->entity_id);
caf6a9a4 213 } else {
214 $boundary_new = $boundary;
215 }
1274f430 216 if ($boundary && !$message->rfc822_header) {
78f39e78 217 $s = '--'.$boundary."\r\n";
caf6a9a4 218 $s .= $this->prepareMIME_Header($message, $boundary_new);
78f39e78 219 $length_raw += strlen($s);
220 if ($stream) {
3b53d87f 221 $this->preWriteToStream($s);
78f39e78 222 $this->writeToStream($stream, $s);
223 }
224 }
225 $this->writeBodyPart($message, $stream, $length_raw);
72b004e6 226
78f39e78 227 $last = false;
228 for ($i=0, $entCount=count($message->entities);$i<$entCount;$i++) {
caf6a9a4 229 $msg = $this->writeBody($message->entities[$i], $stream, $length_raw, $boundary_new);
78f39e78 230 if ($i == $entCount-1) $last = true;
231 }
1274f430 232 if ($boundary && $last) {
dcc5c913 233 $s = "--".$boundary_new."--\r\n\r\n";
78f39e78 234 $length_raw += strlen($s);
235 if ($stream) {
3b53d87f 236 $this->preWriteToStream($s);
78f39e78 237 $this->writeToStream($stream, $s);
238 }
239 }
3b53d87f 240 }
241
72b004e6 242 /**
243 * function writeBodyPart - write each individual mimepart
244 *
245 * Recursively called by WriteBody to write each mime part to the SMTP stream
246 *
247 * @param Message $message Message object to transform
248 * @param resource $stream SMTP output stream
e2ccf284 249 * (when FALSE, nothing will be
250 * written to the stream; this can
251 * be used to determine the actual
252 * number of bytes that will be
253 * written to the stream)
72b004e6 254 * @param integer &$length length of the message part
255 * as returned by mail fn
256 *
3a70ee56 257 * @return void
72b004e6 258 */
3b53d87f 259 function writeBodyPart($message, $stream, &$length) {
1274f430 260 if ($message->mime_header) {
78f39e78 261 $type0 = $message->mime_header->type0;
262 } else {
263 $type0 = $message->rfc822_header->content_type->type0;
264 }
265
266 $body_part_trailing = $last = '';
267 switch ($type0)
268 {
269 case 'text':
270 case 'message':
271 if ($message->body_part) {
272 $body_part = $message->body_part;
4cf85ddd 273 // remove NUL characters
274 $body_part = str_replace("\0",'',$body_part);
78f39e78 275 $length += $this->clean_crlf($body_part);
276 if ($stream) {
72b004e6 277 $this->preWriteToStream($body_part);
78f39e78 278 $this->writeToStream($stream, $body_part);
279 }
280 $last = $body_part;
281 } elseif ($message->att_local_name) {
1f270d3c 282 global $username, $attachment_dir;
283 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
78f39e78 284 $filename = $message->att_local_name;
89f19004 285
286 // inspect attached file for lines longer than allowed by RFC,
287 // in which case we'll be using base64 encoding (so we can split
288 // the lines up without corrupting them) instead of 8bit unencoded...
289 // (see RFC 2822/2.1.1)
290 //
291 // using 990 because someone somewhere is folding lines at
292 // 990 instead of 998 and I'm too lazy to find who it is
293 //
294 $file_has_long_lines = file_has_long_lines($hashed_attachment_dir
295 . '/' . $filename, 990);
296
1f270d3c 297 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
89f19004 298
299 // long lines were found, need to use base64 encoding
300 //
301 if ($file_has_long_lines) {
302 while ($tmp = fread($file, 570)) {
303 $body_part = chunk_split(base64_encode($tmp));
304 // Up to 4.3.10 chunk_split always appends a newline,
305 // while in 4.3.11 it doesn't if the string to split
306 // is shorter than the chunk length.
307 if( substr($body_part, -1 , 1 ) != "\n" )
308 $body_part .= "\n";
309 $length += $this->clean_crlf($body_part);
310 if ($stream) {
311 $this->writeToStream($stream, $body_part);
312 }
313 }
314 }
315
316 // no excessively long lines - normal 8bit
317 //
318 else {
319 while ($body_part = fgets($file, 4096)) {
320 // remove NUL characters
321 $body_part = str_replace("\0",'',$body_part);
322 $length += $this->clean_crlf($body_part);
323 if ($stream) {
324 $this->preWriteToStream($body_part);
325 $this->writeToStream($stream, $body_part);
326 }
327 $last = $body_part;
78f39e78 328 }
78f39e78 329 }
89f19004 330
78f39e78 331 fclose($file);
332 }
333 break;
334 default:
335 if ($message->body_part) {
336 $body_part = $message->body_part;
257dea3f 337 // remove NUL characters
338 $body_part = str_replace("\0",'',$body_part);
78f39e78 339 $length += $this->clean_crlf($body_part);
340 if ($stream) {
341 $this->writeToStream($stream, $body_part);
342 }
343 } elseif ($message->att_local_name) {
1f270d3c 344 global $username, $attachment_dir;
345 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
78f39e78 346 $filename = $message->att_local_name;
1f270d3c 347 $file = fopen ($hashed_attachment_dir . '/' . $filename, 'rb');
78f39e78 348 while ($tmp = fread($file, 570)) {
27ff4efb 349 $body_part = chunk_split(base64_encode($tmp));
350 // Up to 4.3.10 chunk_split always appends a newline,
351 // while in 4.3.11 it doesn't if the string to split
352 // is shorter than the chunk length.
353 if( substr($body_part, -1 , 1 ) != "\n" )
354 $body_part .= "\n";
78f39e78 355 $length += $this->clean_crlf($body_part);
356 if ($stream) {
357 $this->writeToStream($stream, $body_part);
358 }
359 }
360 fclose($file);
3b53d87f 361 }
78f39e78 362 break;
363 }
364 $body_part_trailing = '';
365 if ($last && substr($last,-1) != "\n") {
366 $body_part_trailing = "\r\n";
367 }
368 if ($body_part_trailing) {
369 $length += strlen($body_part_trailing);
370 if ($stream) {
371 $this->preWriteToStream($body_part_trailing);
372 $this->writeToStream($stream, $body_part_trailing);
373 }
374 }
3b53d87f 375 }
78f39e78 376
72b004e6 377 /**
378 * function clean_crlf - change linefeeds and newlines to legal characters
379 *
380 * The SMTP format only allows CRLF as line terminators.
381 * This function replaces illegal teminators with the correct terminator.
382 *
383 * @param string &$s string to clean linefeeds on
384 *
3a70ee56 385 * @return void
72b004e6 386 */
69298c28 387 function clean_crlf(&$s) {
3b53d87f 388 $s = str_replace("\r\n", "\n", $s);
389 $s = str_replace("\r", "\n", $s);
390 $s = str_replace("\n", "\r\n", $s);
78f39e78 391 return strlen($s);
3b53d87f 392 }
78f39e78 393
72b004e6 394 /**
395 * function strip_crlf - strip linefeeds and newlines from a string
396 *
397 * The SMTP format only allows CRLF as line terminators.
398 * This function strips all line terminators from the string.
399 *
400 * @param string &$s string to clean linefeeds on
401 *
3a70ee56 402 * @return void
72b004e6 403 */
e4f9307a 404 function strip_crlf(&$s) {
405 $s = str_replace("\r\n ", '', $s);
78f39e78 406 $s = str_replace("\r", '', $s);
407 $s = str_replace("\n", '', $s);
e4f9307a 408 }
3b53d87f 409
72b004e6 410 /**
411 * function preWriteToStream - reserved for extended functionality
412 *
413 * This function is not yet implemented.
414 * Reserved for extended functionality.
415 *
416 * @param string &$s string to operate on
417 *
3a70ee56 418 * @return void
72b004e6 419 */
69298c28 420 function preWriteToStream(&$s) {
3b53d87f 421 }
78f39e78 422
72b004e6 423 /**
424 * function writeToStream - write data to the SMTP stream
425 *
426 * @param resource $stream SMTP output stream
427 * @param string $data string with data to send to the SMTP stream
428 *
3a70ee56 429 * @return void
72b004e6 430 */
3b53d87f 431 function writeToStream($stream, $data) {
78f39e78 432 fputs($stream, $data);
3b53d87f 433 }
78f39e78 434
72b004e6 435 /**
436 * function initStream - reserved for extended functionality
437 *
438 * This function is not yet implemented.
439 * Reserved for extended functionality.
653a7e87 440 * UPDATE: It is implemented in Deliver_SMTP and Deliver_SendMail classes,
441 * but it remains unimplemented in this base class (and thus not
442 * in Deliver_IMAP or other child classes that don't define it)
443 *
444 * NOTE: some parameters are specific to the child class
445 * that is implementing this method
72b004e6 446 *
447 * @param Message $message Message object
653a7e87 448 * @param string $domain
449 * @param integer $length
72b004e6 450 * @param string $host host name or IP to connect to
653a7e87 451 * @param integer $port
72b004e6 452 * @param string $user username to log into the SMTP server with
453 * @param string $pass password to log into the SMTP server with
653a7e87 454 * @param boolean $authpop whether or not to use POP-before-SMTP authorization
455 * @param string $pop_host host name or IP to connect to for POP-before-SMTP authorization
1c5dd6f2 456 * @param array $stream_options Stream context options, see config_local.example.php for more details (OPTIONAL)
72b004e6 457 *
3a70ee56 458 * @return handle $stream file handle resource to SMTP stream
72b004e6 459 */
a9805897 460 function initStream($message, $domain, $length=0, $host='', $port='', $user='', $pass='', $authpop=false, $pop_host='', $stream_options=array()) {
78f39e78 461 return $stream;
3b53d87f 462 }
72b004e6 463
464 /**
465 * function getBCC - reserved for extended functionality
466 *
467 * This function is not yet implemented.
468 * Reserved for extended functionality.
469 *
470 */
471 function getBCC() {
78f39e78 472 return false;
59387d1c 473 }
3b53d87f 474
72b004e6 475 /**
476 * function prepareMIME_Header - creates the mime header
477 *
478 * @param Message $message Message object to act on
479 * @param string $boundary mime boundary from fn MimeBoundary
480 *
3a70ee56 481 * @return string $header properly formatted mime header
72b004e6 482 */
3b53d87f 483 function prepareMIME_Header($message, $boundary) {
78f39e78 484 $mime_header = $message->mime_header;
485 $rn="\r\n";
486 $header = array();
1274f430 487
78f39e78 488 $contenttype = 'Content-Type: '. $mime_header->type0 .'/'.
489 $mime_header->type1;
490 if (count($message->entities)) {
fcce1b3c 491 $contenttype .= ';' . 'boundary="'.$boundary.'"';
78f39e78 492 }
493 if (isset($mime_header->parameters['name'])) {
494 $contenttype .= '; name="'.
495 encodeHeader($mime_header->parameters['name']). '"';
496 }
497 if (isset($mime_header->parameters['charset'])) {
498 $charset = $mime_header->parameters['charset'];
499 $contenttype .= '; charset="'.
500 encodeHeader($charset). '"';
501 }
1274f430 502
78f39e78 503 $header[] = $contenttype . $rn;
504 if ($mime_header->description) {
eb72e151 505 $header[] = 'Content-Description: ' . $mime_header->description . $rn;
78f39e78 506 }
507 if ($mime_header->encoding) {
eb72e151 508 $header[] = 'Content-Transfer-Encoding: ' . $mime_header->encoding . $rn;
78f39e78 509 } else {
89f19004 510
511 // inspect attached file for lines longer than allowed by RFC,
512 // in which case we'll be using base64 encoding (so we can split
513 // the lines up without corrupting them) instead of 8bit unencoded...
514 // (see RFC 2822/2.1.1)
515 //
516 if (!empty($message->att_local_name)) { // is this redundant? I have no idea
517 global $username, $attachment_dir;
518 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
519 $filename = $hashed_attachment_dir . '/' . $message->att_local_name;
520
521 // using 990 because someone somewhere is folding lines at
522 // 990 instead of 998 and I'm too lazy to find who it is
523 //
524 $file_has_long_lines = file_has_long_lines($filename, 990);
525 } else
526 $file_has_long_lines = FALSE;
527
528 if ($mime_header->type0 == 'multipart' || $mime_header->type0 == 'alternative') {
c6dd29ba 529 /* no-op; no encoding needed */
89f19004 530 } else if (($mime_header->type0 == 'text' || $mime_header->type0 == 'message')
531 && !$file_has_long_lines) {
532 $header[] = 'Content-Transfer-Encoding: 8bit' . $rn;
78f39e78 533 } else {
eb72e151 534 $header[] = 'Content-Transfer-Encoding: base64' . $rn;
78f39e78 535 }
536 }
537 if ($mime_header->id) {
eb72e151 538 $header[] = 'Content-ID: ' . $mime_header->id . $rn;
78f39e78 539 }
540 if ($mime_header->disposition) {
541 $disposition = $mime_header->disposition;
542 $contentdisp = 'Content-Disposition: ' . $disposition->name;
543 if ($disposition->getProperty('filename')) {
544 $contentdisp .= '; filename="'.
545 encodeHeader($disposition->getProperty('filename')). '"';
546 }
72b004e6 547 $header[] = $contentdisp . $rn;
78f39e78 548 }
549 if ($mime_header->md5) {
eb72e151 550 $header[] = 'Content-MD5: ' . $mime_header->md5 . $rn;
78f39e78 551 }
552 if ($mime_header->language) {
eb72e151 553 $header[] = 'Content-Language: ' . $mime_header->language . $rn;
78f39e78 554 }
4960ec8e 555
78f39e78 556 $cnt = count($header);
557 $hdr_s = '';
558 for ($i = 0 ; $i < $cnt ; $i++) {
b1fbb25f 559 $hdr_s .= $this->foldLine($header[$i], 78);
78f39e78 560 }
561 $header = $hdr_s;
562 $header .= $rn; /* One blank line to separate mimeheader and body-entity */
563 return $header;
564 }
4960ec8e 565
72b004e6 566 /**
567 * function prepareRFC822_Header - prepares the RFC822 header string from Rfc822Header object(s)
568 *
569 * This function takes the Rfc822Header object(s) and formats them
570 * into the RFC822Header string to send to the SMTP server as part
571 * of the SMTP message.
572 *
573 * @param Rfc822Header $rfc822_header
574 * @param Rfc822Header $reply_rfc822_header
575 * @param integer &$raw_length length of the message
576 *
3a70ee56 577 * @return string $header
72b004e6 578 */
10adeb76 579 function prepareRFC822_Header(&$rfc822_header, $reply_rfc822_header, &$raw_length) {
b37e457f 580 global $domain, $username, $encode_header_key,
e930c9fe 581 $edit_identity, $hide_auth_header;
60a46e65 582
49c7f411 583 /* if server var SERVER_NAME not available, or contains
584 ":" (e.g. IPv6) which is illegal in a Message-ID, use $domain */
585 if(!sqGetGlobalVar('SERVER_NAME', $SERVER_NAME, SQ_SERVER) ||
586 strpos($SERVER_NAME,':') !== FALSE) {
60a46e65 587 $SERVER_NAME = $domain;
78f39e78 588 }
60a46e65 589
78f39e78 590 sqGetGlobalVar('REMOTE_ADDR', $REMOTE_ADDR, SQ_SERVER);
591 sqGetGlobalVar('REMOTE_PORT', $REMOTE_PORT, SQ_SERVER);
592 sqGetGlobalVar('REMOTE_HOST', $REMOTE_HOST, SQ_SERVER);
593 sqGetGlobalVar('HTTP_VIA', $HTTP_VIA, SQ_SERVER);
594 sqGetGlobalVar('HTTP_X_FORWARDED_FOR', $HTTP_X_FORWARDED_FOR, SQ_SERVER);
60a46e65 595
78f39e78 596 $rn = "\r\n";
223872cb 597
78f39e78 598 /* This creates an RFC 822 date */
42190655 599 $now = time();
600 $now_date = date('D, j M Y H:i:s ', $now) . $this->timezone();
601 // TODO: Do we really want to preserve possibly old date? Date header should always have "now"... but here is not where this decision should be made -- the caller really should blank out $rfc822_header->date even for drafts being re-edited or sent
602 if (!empty($rfc822_header->date) && $rfc822_header->date != -1)
603 $message_date = date('D, j M Y H:i:s ', $rfc822_header->date) . $this->timezone();
604 else {
605 $message_date = $now_date;
606 $rfc822_header->date = $now;
607 }
49c7f411 608
78f39e78 609 /* Create a message-id */
10adeb76 610 $message_id = 'MESSAGE ID GENERATION ERROR! PLEASE CONTACT SQUIRRELMAIL DEVELOPERS';
611 if (empty($rfc822_header->message_id)) {
dda811a1 612 $message_id = '<'
613 . md5(GenerateRandomString(16, '', 7) . uniqid(mt_rand(),true))
614 . '.squirrel@' . $SERVER_NAME .'>';
10adeb76 615 }
b67d61ee 616
78f39e78 617 /* Make an RFC822 Received: line */
618 if (isset($REMOTE_HOST)) {
619 $received_from = "$REMOTE_HOST ([$REMOTE_ADDR])";
620 } else {
621 $received_from = $REMOTE_ADDR;
622 }
623 if (isset($HTTP_VIA) || isset ($HTTP_X_FORWARDED_FOR)) {
624 if (!isset($HTTP_X_FORWARDED_FOR) || $HTTP_X_FORWARDED_FOR == '') {
625 $HTTP_X_FORWARDED_FOR = 'unknown';
626 }
627 $received_from .= " (proxying for $HTTP_X_FORWARDED_FOR)";
628 }
629 $header = array();
432db2fc 630
631 /**
632 * SquirrelMail header
633 *
634 * This Received: header provides information that allows to track
635 * user and machine that was used to send email. Don't remove it
636 * unless you understand all possible forging issues or your
637 * webmail installation does not prevent changes in user's email address.
638 * See SquirrelMail bug tracker #847107 for more details about it.
bb08da84 639 *
904849cc 640 * Add hide_squirrelmail_header as a candidate for config_local.php
641 * (must be defined as a constant: define('hide_squirrelmail_header', 1);
bb08da84 642 * to allow completely hiding SquirrelMail participation in message
539a323f 643 * processing; This is dangerous, especially if users can modify their
e930c9fe 644 * account information, as it makes mapping a sent message back to the
645 * original sender almost impossible.
432db2fc 646 */
e930c9fe 647 $show_sm_header = ( defined('hide_squirrelmail_header') ? ! hide_squirrelmail_header : 1 );
bb08da84 648
10adeb76 649 // 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 650 if ( $show_sm_header ) {
651 if (isset($encode_header_key) &&
432db2fc 652 trim($encode_header_key)!='') {
653 // use encoded headers, if encryption key is set and not empty
b81efcb4 654 $header[] = 'X-Squirrel-UserHash: '.OneTimePadEncrypt($username,base64_encode($encode_header_key)).$rn;
655 $header[] = 'X-Squirrel-FromHash: '.OneTimePadEncrypt($this->ip2hex($REMOTE_ADDR),base64_encode($encode_header_key)).$rn;
432db2fc 656 if (isset($HTTP_X_FORWARDED_FOR))
b81efcb4 657 $header[] = 'X-Squirrel-ProxyHash:'.OneTimePadEncrypt($this->ip2hex($HTTP_X_FORWARDED_FOR),base64_encode($encode_header_key)).$rn;
bb08da84 658 } else {
432db2fc 659 // use default received headers
660 $header[] = "Received: from $received_from" . $rn;
2405c716 661 if (!isset($hide_auth_header) || !$hide_auth_header)
432db2fc 662 $header[] = " (SquirrelMail authenticated user $username)" . $rn;
663 $header[] = " by $SERVER_NAME with HTTP;" . $rn;
42190655 664 $header[] = " $now_date" . $rn;
bb08da84 665 }
35aaf666 666 }
432db2fc 667
59387d1c 668 /* Insert the rest of the header fields */
10adeb76 669
670 if (!empty($rfc822_header->message_id)) {
671 $header[] = 'Message-ID: '. $rfc822_header->message_id . $rn;
672 } else {
673 $header[] = 'Message-ID: '. $message_id . $rn;
674 $rfc822_header->message_id = $message_id;
675 }
676
ea87de81 677 if (is_object($reply_rfc822_header) &&
b4316b34 678 isset($reply_rfc822_header->message_id) &&
679 $reply_rfc822_header->message_id) {
78f39e78 680 $rep_message_id = $reply_rfc822_header->message_id;
78f39e78 681 $header[] = 'In-Reply-To: '.$rep_message_id . $rn;
10adeb76 682 $rfc822_header->in_reply_to = $rep_message_id;
78f39e78 683 $references = $this->calculate_references($reply_rfc822_header);
684 $header[] = 'References: '.$references . $rn;
10adeb76 685 $rfc822_header->references = $references;
78f39e78 686 }
10adeb76 687
42190655 688 $header[] = "Date: $message_date" . $rn;
10adeb76 689
59387d1c 690 $header[] = 'Subject: '.encodeHeader($rfc822_header->subject) . $rn;
ac47827c 691 $header[] = 'From: '. $rfc822_header->getAddr_s('from',",$rn ",true) . $rn;
692
ea87de81 693 // folding address list [From|To|Cc|Bcc] happens by using ",$rn<space>"
694 // as delimiter
ac47827c 695 // Do not use foldLine for that.
696
697 // RFC2822 if from contains more then 1 address
59387d1c 698 if (count($rfc822_header->from) > 1) {
215de78c 699 $header[] = 'Sender: '. $rfc822_header->getAddr_s('sender',',',true) . $rn;
78f39e78 700 }
701 if (count($rfc822_header->to)) {
ac47827c 702 $header[] = 'To: '. $rfc822_header->getAddr_s('to',",$rn ",true) . $rn;
78f39e78 703 }
704 if (count($rfc822_header->cc)) {
ac47827c 705 $header[] = 'Cc: '. $rfc822_header->getAddr_s('cc',",$rn ",true) . $rn;
78f39e78 706 }
707 if (count($rfc822_header->reply_to)) {
215de78c 708 $header[] = 'Reply-To: '. $rfc822_header->getAddr_s('reply_to',',',true) . $rn;
78f39e78 709 }
710 /* Sendmail should return true. Default = false */
711 $bcc = $this->getBcc();
712 if (count($rfc822_header->bcc)) {
ac47827c 713 $s = 'Bcc: '. $rfc822_header->getAddr_s('bcc',",$rn ",true) . $rn;
78f39e78 714 if (!$bcc) {
78f39e78 715 $raw_length += strlen($s);
716 } else {
717 $header[] = $s;
718 }
719 }
72b004e6 720 /* Identify SquirrelMail */
b37e457f 721 $header[] = 'User-Agent: SquirrelMail/' . SM_VERSION . $rn;
78f39e78 722 /* Do the MIME-stuff */
723 $header[] = 'MIME-Version: 1.0' . $rn;
724 $contenttype = 'Content-Type: '. $rfc822_header->content_type->type0 .'/'.
59387d1c 725 $rfc822_header->content_type->type1;
78f39e78 726 if (count($rfc822_header->content_type->properties)) {
727 foreach ($rfc822_header->content_type->properties as $k => $v) {
728 if ($k && $v) {
72b004e6 729 $contenttype .= ';' .$k.'='.$v;
78f39e78 730 }
731 }
732 }
59387d1c 733 $header[] = $contenttype . $rn;
78f39e78 734 if ($encoding = $rfc822_header->encoding) {
23301b33 735 $header[] = 'Content-Transfer-Encoding: ' . $encoding . $rn;
72b004e6 736 }
539a323f 737 if (isset($rfc822_header->dnt) && $rfc822_header->dnt) {
72b004e6 738 $dnt = $rfc822_header->getAddr_s('dnt');
78f39e78 739 /* Pegasus Mail */
740 $header[] = 'X-Confirm-Reading-To: '.$dnt. $rn;
741 /* RFC 2298 */
742 $header[] = 'Disposition-Notification-To: '.$dnt. $rn;
743 }
9feb3cb9 744 if (isset($rfc822_header->dsn) && $rfc822_header->dsn) {
6a8deff5 745 $dsn = $rfc822_header->getAddr_s('dsn');
746 $header[] = 'Return-Receipt-To: '.$dsn. $rn;
747 }
78f39e78 748 if ($rfc822_header->priority) {
d1db3699 749 switch($rfc822_header->priority)
78f39e78 750 {
d1db3699 751 case 1:
91e0dccc 752 $header[] = 'X-Priority: 1 (Highest)'.$rn;
753 $header[] = 'Importance: High'. $rn; break;
d1db3699 754 case 5:
91e0dccc 755 $header[] = 'X-Priority: 5 (Lowest)'.$rn;
756 $header[] = 'Importance: Low'. $rn; break;
78f39e78 757 default: break;
758 }
759 }
72b004e6 760 /* Insert headers from the $more_headers array */
78f39e78 761 if(count($rfc822_header->more_headers)) {
762 reset($rfc822_header->more_headers);
763 foreach ($rfc822_header->more_headers as $k => $v) {
764 $header[] = $k.': '.$v .$rn;
765 }
766 }
767 $cnt = count($header);
768 $hdr_s = '';
5d2a7b15 769
78f39e78 770 for ($i = 0 ; $i < $cnt ; $i++) {
5d2a7b15 771 $sKey = substr($header[$i],0,strpos($header[$i],':'));
772 switch ($sKey)
773 {
774 case 'Message-ID':
775 case 'In-Reply_To':
776 $hdr_s .= $header[$i];
777 break;
778 case 'References':
779 $sRefs = substr($header[$i],12);
780 $aRefs = explode(' ',$sRefs);
781 $sLine = 'References:';
782 foreach ($aRefs as $sReference) {
cc201b46 783 if ( trim($sReference) == '' ) {
784 /* Don't add spaces. */
785 } elseif (strlen($sLine)+strlen($sReference) >76) {
5d2a7b15 786 $hdr_s .= $sLine;
787 $sLine = $rn . ' ' . $sReference;
788 } else {
789 $sLine .= ' '. $sReference;
790 }
791 }
792 $hdr_s .= $sLine;
793 break;
ac47827c 794 case 'To':
795 case 'Cc':
796 case 'Bcc':
797 case 'From':
798 $hdr_s .= $header[$i];
799 break;
b1fbb25f 800 default: $hdr_s .= $this->foldLine($header[$i], 78); break;
5d2a7b15 801 }
78f39e78 802 }
78f39e78 803 $header = $hdr_s;
804 $header .= $rn; /* One blank line to separate header and body */
805 $raw_length += strlen($header);
806 return $header;
4960ec8e 807 }
808
72b004e6 809 /**
9bcf508d 810 * Fold header lines per RFC 2822/2.2.3 and RFC 822/3.1.1
811 *
812 * Herein "soft" folding/wrapping (with whitespace tokens) is
813 * what we refer to as the preferred method of wrapping - that
814 * which we'd like to do within the $soft_wrap limit, but if
815 * not possible, we will try to do as soon as possible after
816 * $soft_wrap up to the $hard_wrap limit. Encoded words don't
817 * need to be detected in this phase, since they cannot contain
818 * spaces.
819 *
820 * "Hard" folding/wrapping (with "hard" tokens) is what we refer
821 * to as less ideal wrapping that will be done to keep within
822 * the $hard_wrap limit. This adds other syntactical breaking
823 * elements such as commas and encoded words.
824 *
825 * @param string $header The header content being folded
826 * @param integer $soft_wrap The desirable maximum line length
827 * (OPTIONAL; default is 78, per RFC)
828 * @param string $indent Wrapped lines will already have
829 * whitespace following the CRLF wrap,
830 * but you can add more indentation (or
831 * whatever) with this. The use of this
832 * parameter is DISCOURAGED, since it
833 * can corrupt the redisplay (unfolding)
834 * of headers whose content is space-
835 * sensitive, like subjects, etc.
836 * (OPTIONAL; default is an empty string)
837 * @param string $hard_wrap The absolute maximum line length
838 * (OPTIONAL; default is 998, per RFC)
839 *
840 * @return string The folded header content, with a trailing CRLF.
841 *
842 */
843 function foldLine($header, $soft_wrap=78, $indent='', $hard_wrap=998) {
844
6ffe7358 845 // allow folding after the initial colon and space?
846 // (only supported if the header name is within the $soft_wrap limit)
847 //
848 $allow_fold_after_header_name = FALSE;
849
9bcf508d 850 // the "hard" token list can be altered if desired,
851 // for example, by adding ":"
852 // (in the future, we can take optional arguments
853 // for overriding or adding elements to the "hard"
854 // token list if we want to get fancy)
855 //
856 // the order of these is significant - preferred
857 // fold points should be listed first
858 //
859 // it is advised that the "=" always come first
860 // since it also finds encoded words, thus if it
861 // comes after some other token that happens to
862 // fall within the encoded word, the encoded word
863 // could be inadvertently broken in half, which
864 // is not allowable per RFC
865 //
866 $hard_break_tokens = array(
867 '=', // includes encoded word detection
868 ',',
869 ';',
870 );
871
872 // the order of these is significant too
873 //
874 $whitespace = array(
875 ' ',
876 "\t",
877 );
878
879 $CRLF = "\r\n";
880
6ffe7358 881 // switch that helps compact the last line, pasting it at the
882 // end of the one before if the one before is already over the
883 // soft limit and it wouldn't go over the hard limit
884 //
885 $pull_last_line_up_if_second_to_last_is_already_over_soft_limit = FALSE;
886
887
888 // ----- end configurable behaviors -----
889
890
9bcf508d 891 $folded_header = '';
892
6ffe7358 893 // if we want to prevent a wrap right after the
894 // header name, make note of the position here
895 //
896 if (!$allow_fold_after_header_name
897 && ($header_name_end_pos = strpos($header, ':'))
898 && strlen($header) > $header_name_end_pos + 1
4663e9db 899 && in_array($header[$header_name_end_pos + 1], $whitespace))
6ffe7358 900 $header_name_end_pos++;
901
9bcf508d 902 // if using an indent string, reduce wrap limits by its size
903 //
904 if (!empty($indent)) {
905 $soft_wrap -= strlen($indent);
906 $hard_wrap -= strlen($indent);
907 }
908
909 while (strlen($header) > $soft_wrap) {
910
911 $soft_wrapped_line = substr($header, 0, $soft_wrap);
912
913 // look for a token as close to the end of the soft wrap limit as possible
914 //
915 foreach ($whitespace as $token) {
916
917 // note that this if statement also fails when $pos === 0,
918 // which is intended, since blank lines are not allowed
919 //
920 if ($pos = strrpos($soft_wrapped_line, $token))
921 {
6ffe7358 922
923 // make sure proposed fold isn't forbidden
924 //
925 if (!$allow_fold_after_header_name
926 && $pos === $header_name_end_pos)
927 continue;
928
9bcf508d 929 $new_fold = substr($header, 0, $pos);
930
931 // make sure proposed fold doesn't create a blank line
932 //
933 if (!trim($new_fold)) continue;
934
935 // with whitespace breaks, we fold BEFORE the token
936 //
937 $folded_header .= $new_fold . $CRLF . $indent;
938 $header = substr($header, $pos);
939
940 // ready for next while() iteration
941 //
942 continue 2;
943
944 }
945
946 }
947
948 // we were unable to find a wrapping point within the soft
949 // wrap limit, so now we'll try to find the first possible
950 // soft wrap point within the hard wrap limit
951 //
952 $hard_wrapped_line = substr($header, 0, $hard_wrap);
953
954 // look for a *SOFT* token as close to the
955 // beginning of the hard wrap limit as possible
956 //
957 foreach ($whitespace as $token) {
958
959 // use while loop instead of if block because it
960 // is possible we don't want the first one we find
961 //
962 $pos = $soft_wrap - 1; // -1 is corrected by +1 on next line
963 while ($pos = strpos($hard_wrapped_line, $token, $pos + 1))
964 {
965
966 $new_fold = substr($header, 0, $pos);
967
968 // make sure proposed fold doesn't create a blank line
969 //
970 if (!trim($new_fold)) continue;
971
972 // with whitespace breaks, we fold BEFORE the token
973 //
974 $folded_header .= $new_fold . $CRLF . $indent;
975 $header = substr($header, $pos);
976
977 // ready for next outter while() iteration
978 //
979 continue 3;
980
981 }
982
983 }
984
985 // we were still unable to find a soft wrapping point within
986 // both the soft and hard wrap limits, so if the length of
987 // what is left is no more than the hard wrap limit, we'll
988 // simply take the whole thing
989 //
6ffe7358 990 if (strlen($header) <= $hard_wrap) {
991
992 // if the header has been folded at least once before now,
993 // let's see if we can add the remaining chunk to the last
994 // fold (this is mainly just aesthetic)
995 //
996 if ($pull_last_line_up_if_second_to_last_is_already_over_soft_limit
997 && strlen($folded_header)
998 // last fold is conveniently in $new_fold
999 && strlen($new_fold) + strlen($header) <= $hard_wrap) {
1000 // $last_fold = substr(substr($folded_header, 0, -(strlen($CRLF) + strlen($indent))),
1001 // remove CRLF and indentation and paste the rest of the header on
1002 $folded_header = substr($folded_header, 0, -(strlen($CRLF) + strlen($indent))) . $header;
1003 $header = '';
1004 }
1005
9bcf508d 1006 break;
6ffe7358 1007 }
9bcf508d 1008
1009 // otherwise, we can't quit yet - look for a "hard" token
1010 // as close to the end of the hard wrap limit as possible
1011 //
1012 foreach ($hard_break_tokens as $token) {
1013
1014 // note that this if statement also fails when $pos === 0,
1015 // which is intended, since blank lines are not allowed
1016 //
1017 if ($pos = strrpos($hard_wrapped_line, $token))
1018 {
1019
1020 // if we found a "=" token, we must determine whether,
1021 // if it is part of an encoded word, it is the beginning
1022 // or middle of one, where we need to readjust $pos a bit
1023 //
1024 if ($token == '=') {
1025
1026 // if we found the beginning of an encoded word,
1027 // we want to break BEFORE the token
1028 //
b1fbb25f 1029 if (preg_match('/^(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)/Ui',
9bcf508d 1030 substr($header, $pos))) {
1031 $pos--;
72b004e6 1032 }
9bcf508d 1033
1034 // check if we found this token in the *middle*
1035 // of an encoded word, in which case we have to
1036 // ignore it, pushing back to the token that
1037 // starts the encoded word instead
1038 //
1039 // of course, this is only possible if there is
1040 // more content after the next hard wrap
1041 //
1042 // then look for the end of an encoded word in
1043 // the next part (past the next hard wrap)
1044 //
1045 // then see if it is in fact part of a legitimate
1046 // encoded word
1047 //
1048 else if (strlen($header) > $hard_wrap
1049 && ($end_pos = strpos(substr($header, $hard_wrap), '?=')) !== FALSE
b1fbb25f 1050 && preg_match('/(=\?([^?]*)\?(Q|B)\?([^?]*)\?=)$/Ui',
9bcf508d 1051 substr($header, 0, $hard_wrap + $end_pos + 2),
1052 $matches)) {
1053
1054 $pos = $hard_wrap + $end_pos + 2 - strlen($matches[1]) - 1;
1055
72b004e6 1056 }
72b004e6 1057
fb1d6cb6 1058 }
9bcf508d 1059
1060 // $pos could have been changed; make sure it's
1061 // not at the beginning of the line, as blank
1062 // lines are not allowed
1063 //
1064 if ($pos === 0) continue;
1065
1066 // we are dealing with a simple token break...
1067 //
1068 // for non-whitespace breaks, we fold AFTER the token
1069 // and add a space after the fold if not immediately
1070 // followed by a whitespace character in the next part
1071 //
6ffe7358 1072 // $new_fold is used above, it's assumed we update it upon every fold action
1073 $new_fold = substr($header, 0, $pos + 1);
1074 $folded_header .= $new_fold . $CRLF;
9bcf508d 1075
1076 // don't go beyond end of $header, though
1077 //
1078 if (strlen($header) > $pos + 1) {
1079 $header = substr($header, $pos + 1);
4663e9db 1080 if (!in_array($header[0], $whitespace))
9bcf508d 1081 $header = ' ' . $indent . $header;
1082 } else {
1083 $header = '';
fb1d6cb6 1084 }
9bcf508d 1085
1086 // ready for next while() iteration
1087 //
1088 continue 2;
1089
fb1d6cb6 1090 }
9bcf508d 1091
fb1d6cb6 1092 }
9bcf508d 1093
1094 // finally, we just couldn't find anything to fold on, so we
1095 // have to just cut it off at the hard limit
1096 //
6ffe7358 1097 // $new_fold is used above, it's assumed we update it upon every fold action
1098 $new_fold = $hard_wrapped_line;
1099 $folded_header .= $new_fold . $CRLF;
9bcf508d 1100
1101 // is there more?
1102 //
1103 if (strlen($header) > strlen($hard_wrapped_line)) {
1104 $header = substr($header, strlen($hard_wrapped_line));
4663e9db 1105 if (!in_array($header[0], $whitespace))
9bcf508d 1106 $header = ' ' . $indent . $header;
1107 } else {
1108 $header = '';
fb1d6cb6 1109 }
9bcf508d 1110
fb1d6cb6 1111 }
9bcf508d 1112
1113
1114 // add any left-overs
1115 //
1116 $folded_header .= $header;
1117
1118
1119 // make sure it ends with a CRLF
1120 //
1121 if (substr($folded_header, -2) != $CRLF) $folded_header .= $CRLF;
1122
1123
1124 return $folded_header;
fb1d6cb6 1125 }
3b53d87f 1126
72b004e6 1127 /**
1128 * function mimeBoundary - calculates the mime boundary to use
1129 *
1130 * This function will generate a random mime boundary base part
1131 * for the message if the boundary has not already been set.
1132 *
3a70ee56 1133 * @return string $mimeBoundaryString random mime boundary string
72b004e6 1134 */
59387d1c 1135 function mimeBoundary () {
78f39e78 1136 static $mimeBoundaryString;
3b53d87f 1137
78f39e78 1138 if ( !isset( $mimeBoundaryString ) ||
1139 $mimeBoundaryString == '') {
1140 $mimeBoundaryString = '----=_' . date( 'YmdHis' ) . '_' .
1141 mt_rand( 10000, 99999 );
1142 }
1143 return $mimeBoundaryString;
3b53d87f 1144 }
1145
72b004e6 1146 /**
1147 * function timezone - Time offset for correct timezone
1148 *
3a70ee56 1149 * @return string $result with timezone and offset
72b004e6 1150 */
59387d1c 1151 function timezone () {
0bbf8622 1152 global $invert_time, $show_timezone_name;
78f39e78 1153
1154 $diff_second = date('Z');
1155 if ($invert_time) {
1156 $diff_second = - $diff_second;
1157 }
1158 if ($diff_second > 0) {
1159 $sign = '+';
1160 } else {
1161 $sign = '-';
1162 }
1163 $diff_second = abs($diff_second);
1164 $diff_hour = floor ($diff_second / 3600);
1165 $diff_minute = floor (($diff_second-3600*$diff_hour) / 60);
0bbf8622 1166
1167 // If an administrator wants to add the timezone name to the
1168 // end of the date header, they can set $show_timezone_name
1169 // to boolean TRUE in config/config_local.php, but that is
1170 // NOT RFC-822 compliant (see section 5.1). Moreover, some
1171 // Windows users reported that strftime('%Z') was returning
1172 // the full zone name (not the abbreviation) which in some
1173 // cases included 8-bit characters (not allowed as is in headers).
1174 // The PHP manual actually does NOT promise what %Z will return
1175 // for strftime!: "The time zone offset/abbreviation option NOT
1176 // given by %z (depends on operating system)"
1177 //
1178 if ($show_timezone_name) {
1179 $zonename = '('.strftime('%Z').')';
1180 $result = sprintf ("%s%02d%02d %s", $sign, $diff_hour, $diff_minute, $zonename);
1181 } else {
1182 $result = sprintf ("%s%02d%02d", $sign, $diff_hour, $diff_minute);
1183 }
78f39e78 1184 return ($result);
59387d1c 1185 }
3b53d87f 1186
72b004e6 1187 /**
f2ac3325 1188 * function calculate_references - calculate correct References string
1189 * Adds the current message ID, and makes sure it doesn't grow forever,
1190 * to that extent it drops message-ID's in a smart way until the string
1191 * length is under the recommended value of 1000 ("References: <986>\r\n").
1192 * It always keeps the first and the last three ID's.
72b004e6 1193 *
1194 * @param Rfc822Header $hdr message header to calculate from
1195 *
f2ac3325 1196 * @return string $refer concatenated and trimmed References string
72b004e6 1197 */
1274f430 1198 function calculate_references($hdr) {
f2ac3325 1199 $aReferences = preg_split('/\s+/', $hdr->references);
78f39e78 1200 $message_id = $hdr->message_id;
1201 $in_reply_to = $hdr->in_reply_to;
539a323f 1202
f2ac3325 1203 // if References already exists, add the current message ID at the end.
1204 // no References exists; if we know a IRT, add that aswell
1205 if (count($aReferences) == 0 && $in_reply_to) {
1206 $aReferences[] = $in_reply_to;
1207 }
1208 $aReferences[] = $message_id;
1209
1210 // sanitize the array: trim whitespace, remove dupes
86e6a9eb 1211 array_walk($aReferences, 'sq_trim_value');
f2ac3325 1212 $aReferences = array_unique($aReferences);
1213
1214 while ( count($aReferences) > 4 && strlen(implode(' ', $aReferences)) >= 986 ) {
1215 $aReferences = array_merge(array_slice($aReferences,0,1),array_slice($aReferences,2));
78f39e78 1216 }
f2ac3325 1217 return implode(' ', $aReferences);
1218 }
1219
432db2fc 1220 /**
1221 * Converts ip address to hexadecimal string
1222 *
1223 * Function is used to convert ipv4 and ipv6 addresses to hex strings.
1224 * It removes all delimiter symbols from ip addresses, converts decimal
1225 * ipv4 numbers to hex and pads strings in order to present full length
ea87de81 1226 * address. ipv4 addresses are represented as 8 byte strings, ipv6 addresses
432db2fc 1227 * are represented as 32 byte string.
1228 *
1229 * If function fails to detect address format, it returns unprocessed string.
1230 * @param string $string ip address string
1231 * @return string processed ip address string
94511d23 1232 * @since 1.5.1 and 1.4.5
432db2fc 1233 */
1234 function ip2hex($string) {
1235 if (preg_match("/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/",$string,$match)) {
1236 // ipv4 address
1237 $ret = str_pad(dechex($match[1]),2,'0',STR_PAD_LEFT)
1238 . str_pad(dechex($match[2]),2,'0',STR_PAD_LEFT)
1239 . str_pad(dechex($match[3]),2,'0',STR_PAD_LEFT)
1240 . str_pad(dechex($match[4]),2,'0',STR_PAD_LEFT);
1241 } 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)) {
1242 // full ipv6 address
1243 $ret = str_pad($match[1],4,'0',STR_PAD_LEFT)
1244 . str_pad($match[2],4,'0',STR_PAD_LEFT)
1245 . str_pad($match[3],4,'0',STR_PAD_LEFT)
1246 . str_pad($match[4],4,'0',STR_PAD_LEFT)
1247 . str_pad($match[5],4,'0',STR_PAD_LEFT)
1248 . str_pad($match[6],4,'0',STR_PAD_LEFT)
1249 . str_pad($match[7],4,'0',STR_PAD_LEFT)
1250 . str_pad($match[8],4,'0',STR_PAD_LEFT);
1251 } elseif (preg_match("/^\:\:([0-9a-h\:]+)$/i",$string,$match)) {
1252 // short ipv6 with all starting symbols nulled
1253 $aAddr=explode(':',$match[1]);
1254 $ret='';
1255 foreach ($aAddr as $addr) {
1256 $ret.=str_pad($addr,4,'0',STR_PAD_LEFT);
1257 }
1258 $ret=str_pad($ret,32,'0',STR_PAD_LEFT);
1259 } elseif (preg_match("/^([0-9a-h\:]+)::([0-9a-h\:]+)$/i",$string,$match)) {
1260 // short ipv6 with middle part nulled
1261 $aStart=explode(':',$match[1]);
1262 $sStart='';
1263 foreach($aStart as $addr) {
1264 $sStart.=str_pad($addr,4,'0',STR_PAD_LEFT);
1265 }
1266 $aEnd = explode(':',$match[2]);
1267 $sEnd='';
1268 foreach($aEnd as $addr) {
1269 $sEnd.=str_pad($addr,4,'0',STR_PAD_LEFT);
1270 }
1271 $ret = $sStart
1272 . str_pad('',(32 - strlen($sStart . $sEnd)),'0',STR_PAD_LEFT)
1273 . $sEnd;
1274 } else {
1275 // unknown addressing
1276 $ret = $string;
1277 }
1278 return $ret;
1279 }
59387d1c 1280}