Aleksander Machniak
2016-05-02 9796cd2063770a8562d58d6492fd6904cdeb4627
commit | author | age
4e17e6 1 <?php
T 2
a95874 3 /**
4e17e6 4  +-----------------------------------------------------------------------+
T 5  | program/steps/mail/sendmail.inc                                       |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
0f16a0 8  | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
7fe381 9  |                                                                       |
T 10  | Licensed under the GNU General Public License version 3 or            |
11  | any later version with exceptions for skins & plugins.                |
12  | See the README file for a full license statement.                     |
4e17e6 13  |                                                                       |
T 14  | PURPOSE:                                                              |
15  |   Compose a new mail message with all headers and attachments         |
e8f8fe 16  |   and send it using the PEAR::Net_SMTP class or with PHP mail()       |
4e17e6 17  |                                                                       |
T 18  +-----------------------------------------------------------------------+
19  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
20  +-----------------------------------------------------------------------+
21 */
22
0b6c1c 23 // remove all scripts and act as called in frame
T 24 $OUTPUT->reset();
25 $OUTPUT->framed = TRUE;
26
c5c8e7 27 $saveonly       = !empty($_GET['_saveonly']);
AM 28 $savedraft      = !empty($_POST['_draft']) && !$saveonly;
0f16a0 29 $sendmail_delay = (int) $RCMAIL->config->get('sendmail_delay');
AM 30 $drafts_mbox    = $RCMAIL->config->get('drafts_mbox');
acb08f 31
6b2b2e 32 $COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
72ff6a 33 $COMPOSE    =& $_SESSION['compose_data_'.$COMPOSE_ID];
4591de 34
acb08f 35 /****** checks ********/
0b6c1c 36
72ff6a 37 if (!isset($COMPOSE['id'])) {
0f16a0 38     rcube::raise_error(array('code' => 500, 'type' => 'php',
AM 39         'file' => __FILE__, 'line' => __LINE__,
40         'message' => "Invalid compose ID"), true, false);
10eedb 41
0f16a0 42     $OUTPUT->show_message('internalerror', 'error');
AM 43     $OUTPUT->send('iframe');
0b6c1c 44 }
4e17e6 45
4b60fa 46 if (!$savedraft) {
0f16a0 47     if (empty($_POST['_to']) && empty($_POST['_cc']) && empty($_POST['_bcc'])
AM 48         && empty($_POST['_subject']) && $_POST['_message']
49     ) {
50         $OUTPUT->show_message('sendingfailed', 'error');
51         $OUTPUT->send('iframe');
acb08f 52     }
0f16a0 53
AM 54     if ($sendmail_delay) {
55         $wait_sec = time() - $sendmail_delay - intval($RCMAIL->config->get('last_message_time'));
56         if ($wait_sec < 0) {
57             $OUTPUT->show_message('senttooquickly', 'error', array('sec' => $wait_sec * -1));
58             $OUTPUT->send('iframe');
59         }
60     }
acb08f 61 }
A 62
4e17e6 63
acb08f 64 /****** compose message ********/
A 65
10936f 66 if (empty($COMPOSE['param']['message-id'])) {
0f16a0 67     $COMPOSE['param']['message-id'] = $RCMAIL->gen_message_id();
10936f 68 }
AM 69 $message_id = $COMPOSE['param']['message-id'];
4e17e6 70
5bc8cb 71 // set default charset
27be4e 72 $message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $OUTPUT->get_charset();
c03095 73
e4acbb 74 $EMAIL_FORMAT_ERROR = NULL;
0f16a0 75 $RECIPIENT_COUNT    = 0;
e4acbb 76
0f16a0 77 $mailto  = rcmail_email_input_format(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
AM 78 $mailcc  = rcmail_email_input_format(rcube_utils::get_input_value('_cc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
6b2b2e 79 $mailbcc = rcmail_email_input_format(rcube_utils::get_input_value('_bcc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
4e17e6 80
10bf6b 81 if ($EMAIL_FORMAT_ERROR && !$savedraft) {
0f16a0 82     $OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR));
AM 83     $OUTPUT->send('iframe');
e4acbb 84 }
A 85
e8f8fe 86 if (empty($mailto) && !empty($mailcc)) {
0f16a0 87     $mailto = $mailcc;
AM 88     $mailcc = null;
e8f8fe 89 }
0f16a0 90 else if (empty($mailto)) {
AM 91     $mailto = 'undisclosed-recipients:;';
92 }
e8f8fe 93
d2b884 94 // Get sender name and address...
6b2b2e 95 $from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);
d2b884 96 // ... from identity...
A 97 if (is_numeric($from)) {
0f16a0 98     if (is_array($identity_arr = rcmail_get_identity($from))) {
AM 99         if ($identity_arr['mailto'])
100             $from = $identity_arr['mailto'];
101         if ($identity_arr['string'])
102             $from_string = $identity_arr['string'];
103     }
104     else {
105         $from = null;
106     }
d2b884 107 }
A 108 // ... if there is no identity record, this might be a custom from
109 else if ($from_string = rcmail_email_input_format($from)) {
0f16a0 110     if (preg_match('/(\S+@\S+)/', $from_string, $m))
AM 111         $from = trim($m[1], '<>');
112     else
113         $from = null;
d2b884 114 }
fd51e0 115
0a4e09 116 // check 'From' address (identity may be incomplete)
AM 117 if (!$savedraft && !$saveonly && empty($from)) {
118     $OUTPUT->show_message('nofromaddress', 'error');
119     $OUTPUT->send('iframe');
120 }
121
0f16a0 122 if (!$from_string && $from) {
AM 123     $from_string = $from;
124 }
4e17e6 125
T 126 // compose headers array
2471d3 127 $headers = array();
A 128
129 // if configured, the Received headers goes to top, for good measure
0f16a0 130 if ($RCMAIL->config->get('http_received_header')) {
fdbb1c 131     $nldlm       = "\r\n\t";
0f16a0 132     $http_header = 'from ';
AM 133
fdbb1c 134     // FROM/VIA
0f16a0 135     if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
fdbb1c 136         $hosts        = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2);
AM 137         $http_header .= rcmail_received_host($hosts[0]) . $nldlm . ' via ';
ddc891 138     }
A 139
fdbb1c 140     $http_header .= rcmail_received_host($_SERVER['REMOTE_ADDR']);
0f16a0 141
AM 142     // BY
143     $http_header .= $nldlm . 'by ' . $_SERVER['HTTP_HOST'];
144
145     // WITH
fdbb1c 146     $http_header .= $nldlm . 'with HTTP (' . $_SERVER['SERVER_PROTOCOL']
AM 147         . ' ' . $_SERVER['REQUEST_METHOD'] . '); ' . date('r');
0f16a0 148
fdbb1c 149     $headers['Received'] = wordwrap($http_header, 69, $nldlm);
2471d3 150 }
A 151
6b2b2e 152 $headers['Date'] = $RCMAIL->user_date();
AM 153 $headers['From'] = rcube_charset::convert($from_string, RCUBE_CHARSET, $message_charset);
f65021 154 $headers['To']   = $mailto;
4e17e6 155
T 156 // additional recipients
14f87f 157 if (!empty($mailcc)) {
0f16a0 158     $headers['Cc'] = $mailcc;
14f87f 159 }
A 160 if (!empty($mailbcc)) {
0f16a0 161     $headers['Bcc'] = $mailbcc;
14f87f 162 }
751b22 163
A 164 if (($max_recipients = (int) $RCMAIL->config->get('max_recipients')) > 0) {
0f16a0 165     if ($RECIPIENT_COUNT > $max_recipients) {
AM 166         $OUTPUT->show_message('toomanyrecipients', 'error', array('max' => $max_recipients));
167         $OUTPUT->send('iframe');
168     }
751b22 169 }
f65021 170
AM 171 $dont_override = (array) $RCMAIL->config->get('dont_override');
172 $mdn_enabled   = in_array('mdn_default', $dont_override) ? $RCMAIL->config->get('mdn_default') : !empty($_POST['_mdn']);
173 $dsn_enabled   = in_array('dsn_default', $dont_override) ? $RCMAIL->config->get('dsn_default') : !empty($_POST['_dsn']);
4e17e6 174
T 175 // add subject
6b2b2e 176 $headers['Subject'] = trim(rcube_utils::get_input_value('_subject', rcube_utils::INPUT_POST, TRUE, $message_charset));
4e17e6 177
14f87f 178 if (!empty($identity_arr['organization'])) {
0f16a0 179     $headers['Organization'] = $identity_arr['organization'];
3ee5a7 180 }
0f16a0 181 if ($hdr = rcube_utils::get_input_value('_replyto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
AM 182     $headers['Reply-To'] = rcmail_email_input_format($hdr);
14f87f 183 }
A 184 if (!empty($headers['Reply-To'])) {
0f16a0 185     $headers['Mail-Reply-To'] = $headers['Reply-To'];
14f87f 186 }
0f16a0 187 if ($hdr = rcube_utils::get_input_value('_followupto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
65ac83 188     $headers['Mail-Followup-To'] = rcmail_email_input_format($hdr);
14f87f 189 }
e99991 190
bbc856 191 // remember reply/forward UIDs in special headers
ce3105 192 if ($savedraft) {
AM 193     // Note: We ignore <UID>.<PART> forwards/replies here
194     if (($uid = $COMPOSE['reply_uid']) && !preg_match('/^\d+[0-9.]+$/', $uid)) {
195         $headers['X-Draft-Info'] = array('type' => 'reply', 'uid' => $uid);
196     }
197     else if (!empty($COMPOSE['forward_uid'])
198         && ($uid = rcube_imap_generic::compressMessageSet($COMPOSE['forward_uid']))
199         && !preg_match('/^\d+[0-9.]+$/', $uid)
200     ) {
201         $headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => $uid);
202     }
14f87f 203 }
bbc856 204
eafd5b 205 if (!empty($COMPOSE['reply_msgid'])) {
0f16a0 206     $headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
eafd5b 207 }
72ff6a 208 if (!empty($COMPOSE['references'])) {
0f16a0 209     $headers['References'] = $COMPOSE['references'];
14f87f 210 }
4e17e6 211
d2b884 212 if (!empty($_POST['_priority'])) {
0f16a0 213     $priority     = intval($_POST['_priority']);
AM 214     $a_priorities = array(1 => 'highest', 2 => 'high', 4 => 'low', 5 => 'lowest');
215
216     if ($str_priority = $a_priorities[$priority]) {
217         $headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority));
218     }
d2b884 219 }
4e17e6 220
f65021 221 if ($mdn_enabled) {
0f16a0 222     $headers['Return-Receipt-To']           = $from_string;
AM 223     $headers['Disposition-Notification-To'] = $from_string;
d2b884 224 }
4e17e6 225
T 226 // additional headers
53e79d 227 $headers['Message-ID'] = $message_id;
0f16a0 228 $headers['X-Sender']   = $from;
53e79d 229
14f87f 230 if (is_array($headers['X-Draft-Info'])) {
0f16a0 231     $headers['X-Draft-Info'] = rcmail_draftinfo_encode($headers['X-Draft-Info'] + array('folder' => $COMPOSE['mailbox']));
14f87f 232 }
0f16a0 233 if ($hdr = $RCMAIL->config->get('useragent')) {
AM 234     $headers['User-Agent'] = $hdr;
14f87f 235 }
4e17e6 236
b44b4d 237 // exec hook for header checking and manipulation
6efadf 238 // Depracated: use message_before_send hook instead
e6ce00 239 $data = $RCMAIL->plugins->exec_hook('message_outgoing_headers', array('headers' => $headers));
b44b4d 240
T 241 // sending aborted by plugin
242 if ($data['abort'] && !$savedraft) {
827159 243     $OUTPUT->show_message($data['message'] ?: 'sendingfailed');
0f16a0 244     $OUTPUT->send('iframe');
b44b4d 245 }
0f16a0 246 else {
AM 247     $headers = $data['headers'];
248 }
b44b4d 249
6b2b2e 250 $isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
940fc1 251
c03095 252 // fetch message body
6b2b2e 253 $message_body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, TRUE, $message_charset);
940fc1 254
40d152 255 if (isset($_POST['_pgpmime'])) {
4cd087 256     $pgp_mime     = rcube_utils::get_input_value('_pgpmime', rcube_utils::INPUT_POST);
AM 257     $isHtml       = false;
258     $message_body = '';
40d152 259
TB 260     // clear unencrypted attachments
e250f0 261     foreach ((array) $COMPOSE['attachments'] as $attach) {
40d152 262         $RCMAIL->plugins->exec_hook('attachment_delete', $attach);
TB 263     }
e250f0 264
40d152 265     $COMPOSE['attachments'] = array();
TB 266 }
267
7e263e 268 if ($isHtml) {
0f16a0 269     $bstyle = array();
f7b2bf 270
0f16a0 271     if ($font_size = $RCMAIL->config->get('default_font_size')) {
AM 272         $bstyle[] = 'font-size: ' . $font_size;
273     }
274     if ($font_family = $RCMAIL->config->get('default_font')) {
275         $bstyle[] = 'font-family: ' . rcmail::font_defs($font_family);
276     }
7e263e 277
0f16a0 278     // append doctype and html/body wrappers
89d6ce 279     $bstyle       = !empty($bstyle) ? (" style='" . implode($bstyle, '; ') . "'") : '';
AM 280     $message_body = '<html><head>'
281         . '<meta http-equiv="Content-Type" content="text/html; charset=' . $message_charset . '" /></head>'
282         . "<body" . $bstyle . ">\r\n" . $message_body;
7e263e 283 }
A 284
65605c 285 if (!$savedraft) {
0f16a0 286     if ($isHtml) {
eda92e 287         $b_style   = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
AM 288         $pre_style = 'margin: 0; padding: 0; font-family: monospace';
4e17e6 289
eda92e 290         $message_body = preg_replace(
AM 291             array(
d5f6d6 292                 // remove empty signature div
AM 293                 '/<div id="_rc_sig">(&nbsp;)?<\/div>[\s\r\n]*$/',
eda92e 294                 // remove signature's div ID
AM 295                 '/\s*id="_rc_sig"/',
296                 // add inline css for blockquotes and container
297                 '/<blockquote>/',
d5f6d6 298                 '/<div class="pre">/',
AM 299                 // convert TinyMCE's new-line sequences (#1490463)
300                 '/<p>&nbsp;<\/p>/',
eda92e 301             ),
AM 302             array(
303                 '',
d5f6d6 304                 '',
eda92e 305                 '<blockquote type="cite" style="'.$b_style.'">',
d5f6d6 306                 '<div class="pre" style="'.$pre_style.'">',
AM 307                 '<p><br /></p>',
eda92e 308             ),
AM 309             $message_body);
1d5779 310     }
A 311
0f16a0 312     // Check spelling before send
AM 313     if ($RCMAIL->config->get('spellcheck_before_send') && $RCMAIL->config->get('enable_spellcheck')
314         && empty($COMPOSE['spell_checked']) && !empty($message_body)
315     ) {
316         $message_body = str_replace("\r\n", "\n", $message_body);
317         $spellchecker = new rcube_spellchecker(rcube_utils::get_input_value('_lang', rcube_utils::INPUT_GPC));
318         $spell_result = $spellchecker->check($message_body, $isHtml);
319
320         $COMPOSE['spell_checked'] = true;
321
322         if (!$spell_result) {
f56e70 323             if ($isHtml) {
AM 324                 $result['words']      = $spellchecker->get();
325                 $result['dictionary'] = (bool) $RCMAIL->config->get('spellcheck_dictionary');
326             }
327             else {
328                 $result = $spellchecker->get_xml();
329             }
0f16a0 330
AM 331             $OUTPUT->show_message('mispellingsfound', 'error');
646b64 332             $OUTPUT->command('spellcheck_resume', $result);
0f16a0 333             $OUTPUT->send('iframe');
AM 334         }
335     }
336
337     // generic footer for all messages
338     if ($footer = rcmail_generic_message_footer($isHtml)) {
339         $footer = rcube_charset::convert($footer, RCUBE_CHARSET, $message_charset);
340         $message_body .= "\r\n" . $footer;
341     }
7e263e 342 }
A 343
344 if ($isHtml) {
0f16a0 345     $message_body .= "\r\n</body></html>\r\n";
65605c 346 }
a0109c 347
b169de 348 // sort attachments to make sure the order is the same as in the UI (#1488423)
0f16a0 349 if ($files = rcube_utils::get_input_value('_attachments', rcube_utils::INPUT_POST)) {
AM 350     $files = explode(',', $files);
351     $files = array_flip($files);
352     foreach ($files as $idx => $val) {
353         $files[$idx] = $COMPOSE['attachments'][$idx];
354         unset($COMPOSE['attachments'][$idx]);
355     }
b169de 356
0f16a0 357     $COMPOSE['attachments'] = array_merge(array_filter($files), $COMPOSE['attachments']);
b169de 358 }
AM 359
b62049 360 // set line length for body wrapping
c769c6 361 $LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
b62049 362
91790e 363 // Since we can handle big messages with disk usage, we need more time to work
A 364 @set_time_limit(0);
365
366 // create PEAR::Mail_mime instance
ac8edb 367 $MAIL_MIME = new Mail_mime("\r\n");
91790e 368
A 369 // Check if we have enough memory to handle the message in it
370 // It's faster than using files, so we'll do this if we only can
0f16a0 371 if (is_array($COMPOSE['attachments']) && $RCMAIL->config->get('smtp_server')
AM 372   && ($mem_limit = parse_bytes(ini_get('memory_limit')))
373 ) {
374     $memory = 0;
375     foreach ($COMPOSE['attachments'] as $id => $attachment) {
376         $memory += $attachment['size'];
377     }
91790e 378
0f16a0 379     // Yeah, Net_SMTP needs up to 12x more memory, 1.33 is for base64
AM 380     if (!rcube_utils::mem_check($memory * 1.33 * 12)) {
381         $MAIL_MIME->setParam('delay_file_io', true);
382     }
91790e 383 }
a0109c 384
S 385 // For HTML-formatted messages, construct the MIME message with both
386 // the HTML part and the plain-text part
cc97ea 387 if ($isHtml) {
0f16a0 388     $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
AM 389         array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME));
ac8edb 390
0f16a0 391     $MAIL_MIME->setHTMLBody($plugin['body']);
a0109c 392
a63f14 393     $plainTextPart = $RCMAIL->html2text($plugin['body'], array('width' => 0, 'charset' => $message_charset));
AM 394     $plainTextPart = rcube_mime::wordwrap($plainTextPart, $LINE_LENGTH, "\r\n", false, $message_charset);
0f16a0 395     $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
21d463 396
0f16a0 397     // make sure all line endings are CRLF (#1486712)
AM 398     $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
ac8edb 399
0f16a0 400     $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
AM 401         array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
ac8edb 402
a63f14 403     // add a plain text version of the e-mail as an alternative part.
0f16a0 404     $MAIL_MIME->setTXTBody($plugin['body']);
9287ed 405
0f16a0 406     // Extract image Data URIs into message attachments (#1488502)
AM 407     rcmail_extract_inline_images($MAIL_MIME, $from);
cc97ea 408 }
6b6f2e 409 else {
0f16a0 410     $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
AM 411         array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME));
5852c1 412
0f16a0 413     $message_body = $plugin['body'];
5852c1 414
0f16a0 415     // compose format=flowed content if enabled
AM 416     if ($flowed = ($savedraft || $RCMAIL->config->get('send_format_flowed', true)))
417         $message_body = rcube_mime::format_flowed($message_body, min($LINE_LENGTH+2, 79), $message_charset);
418     else
419         $message_body = rcube_mime::wordwrap($message_body, $LINE_LENGTH, "\r\n", false, $message_charset);
5852c1 420
0f16a0 421     $message_body = wordwrap($message_body, 998, "\r\n", true);
ac8edb 422
0f16a0 423     $MAIL_MIME->setTXTBody($message_body, false, true);
cc97ea 424 }
4e17e6 425
T 426 // add stored attachments, if any
e28b12 427 if (is_array($COMPOSE['attachments'])) {
0f16a0 428     foreach ($COMPOSE['attachments'] as $id => $attachment) {
AM 429         // This hook retrieves the attachment contents from the file storage backend
430         $attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment);
cc97ea 431
0f16a0 432         if ($isHtml) {
a16cf3 433             $dispurl      = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
0f16a0 434             $message_body = $MAIL_MIME->getHTMLBody();
AM 435             $is_inline    = preg_match($dispurl, $message_body);
436         }
437         else {
438             $is_inline = false;
439         }
440
441         // inline image
442         if ($is_inline) {
443             // Mail_Mime does not support many inline attachments with the same name (#1489406)
444             // we'll generate cid: urls here to workaround this
445             $cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
446             if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $from, $matches)) {
447                 $cid .= $matches[1];
448             }
449             else {
450                 $cid .= '@localhost';
451             }
452
a16cf3 453             $message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
0f16a0 454
AM 455             $MAIL_MIME->setHTMLBody($message_body);
456
457             if ($attachment['data'])
458                 $MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false, $cid);
459             else
460                 $MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true, $cid);
461         }
462         else {
463             $ctype   = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
827159 464             $file    = $attachment['data'] ?: $attachment['path'];
0f16a0 465             $folding = (int) $RCMAIL->config->get('mime_param_folding');
AM 466
467             $MAIL_MIME->addAttachment($file,
468                 $ctype,
469                 $attachment['name'],
470                 $attachment['data'] ? false : true,
471                 $ctype == 'message/rfc822' ? '8bit' : 'base64',
472                 'attachment',
d165d1 473                 $attachment['charset'],
AM 474                 '', '',
0f16a0 475                 $folding ? 'quoted-printable' : NULL,
AM 476                 $folding == 2 ? 'quoted-printable' : NULL,
477                 '', RCUBE_CHARSET
478             );
479         }
e28b12 480     }
cc97ea 481 }
4e17e6 482
3d0ec7 483 // choose transfer encoding for plain/text body
ac3cdd 484 if (preg_match('/[^\x00-\x7F]/', $MAIL_MIME->getTXTBody())) {
0f16a0 485     $text_charset      = $message_charset;
AM 486     $transfer_encoding = $RCMAIL->config->get('force_7bit') ? 'quoted-printable' : '8bit';
ac3cdd 487 }
AM 488 else {
e1b8f4 489     $text_charset      = 'US-ASCII';
0f16a0 490     $transfer_encoding = '7bit';
ac3cdd 491 }
AM 492
493 if ($flowed) {
0f16a0 494     $text_charset .= ";\r\n format=flowed";
ac3cdd 495 }
3d0ec7 496
40d152 497 // compose PGP/Mime message
TB 498 if ($pgp_mime) {
4cd087 499     $MAIL_MIME->addAttachment(new Mail_mimePart('Version: 1', array(
AM 500             'content_type' => 'application/pgp-encrypted',
501             'description'  => 'PGP/MIME version identification',
502     )));
40d152 503
4cd087 504     $MAIL_MIME->addAttachment(new Mail_mimePart($pgp_mime, array(
AM 505             'content_type' => 'application/octet-stream',
506             'filename'     => 'encrypted.asc',
507             'disposition'  => 'inline',
508     )));
40d152 509
4cd087 510     $MAIL_MIME->setContentType('multipart/encrypted', array('protocol' => 'application/pgp-encrypted'));
AM 511     $MAIL_MIME->setParam('preamble', 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)');
40d152 512 }
TB 513
a95e0e 514 // encoding settings for mail composing
91790e 515 $MAIL_MIME->setParam('text_encoding', $transfer_encoding);
A 516 $MAIL_MIME->setParam('html_encoding', 'quoted-printable');
517 $MAIL_MIME->setParam('head_encoding', 'quoted-printable');
518 $MAIL_MIME->setParam('head_charset', $message_charset);
519 $MAIL_MIME->setParam('html_charset', $message_charset);
ac3cdd 520 $MAIL_MIME->setParam('text_charset', $text_charset);
cc97ea 521
fba1f5 522 // pass headers to message object
T 523 $MAIL_MIME->headers($headers);
4e17e6 524
0a4e09 525 // This hook allows to modify the message before send or save action
AM 526 $plugin    = $RCMAIL->plugins->exec_hook('message_ready', array('message' => $MAIL_MIME));
527 $MAIL_MIME = $plugin['message'];
528
d2b884 529 // Begin SMTP Delivery Block
97ee4c 530 if (!$savedraft && !$saveonly) {
0f16a0 531     // Handle Delivery Status Notification request
f65021 532     $smtp_opts['dsn'] = $dsn_enabled;
acb08f 533
0f16a0 534     $sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto,
AM 535         $smtp_error, $mailbody_file, $smtp_opts);
765fde 536
0f16a0 537     // return to compose page if sending failed
AM 538     if (!$sent) {
539         // remove temp file
540         if ($mailbody_file) {
541             unlink($mailbody_file);
542         }
c03095 543
0f16a0 544         if ($smtp_error)
AM 545             $OUTPUT->show_message($smtp_error['label'], 'error', $smtp_error['vars']); 
546         else
547             $OUTPUT->show_message('sendingfailed', 'error'); 
548         $OUTPUT->send('iframe');
549     }
41fa0b 550
0f16a0 551     // save message sent time
AM 552     if ($sendmail_delay) {
553         $RCMAIL->user->save_prefs(array('last_message_time' => time()));
554     }
555
556     // set replied/forwarded flag
e8cb51 557     if ($COMPOSE['reply_uid']) {
0456f7 558         foreach (rcmail::get_uids($COMPOSE['reply_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
ce3105 559             // skip <UID>.<PART> replies
AM 560             if (!preg_match('/^\d+[0-9.]+$/', implode(',', (array) $uids))) {
561                 $RCMAIL->storage->set_flag($uids, 'ANSWERED', $mbox);
562             }
e8cb51 563         }
TB 564     }
565     else if ($COMPOSE['forward_uid']) {
0456f7 566         foreach (rcmail::get_uids($COMPOSE['forward_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
ce3105 567             // skip <UID>.<PART> forwards
AM 568             if (!preg_match('/^\d+[0-9.]+$/', implode(',', (array) $uids))) {
569                 $RCMAIL->storage->set_flag($uids, 'FORWARDED', $mbox);
570             }
e8cb51 571         }
TB 572     }
0f16a0 573 }
41fa0b 574
1966c5 575 // Determine which folder to save message
0f16a0 576 if ($savedraft) {
AM 577     $store_target = $drafts_mbox;
578 }
579 else if (!$RCMAIL->config->get('no_save_sent_messages')) {
10f133 580     if (isset($_POST['_store_target'])) {
AM 581         $store_target = rcube_utils::get_input_value('_store_target', rcube_utils::INPUT_POST);
582     }
583     else {
0d9672 584         $store_target = $RCMAIL->config->get('sent_mbox');
0f16a0 585     }
AM 586 }
c03095 587
56ec81 588 if ($store_target) {
0f16a0 589     // check if folder is subscribed
AM 590     if ($RCMAIL->storage->folder_exists($store_target, true)) {
591         $store_folder = true;
56ec81 592     }
0f16a0 593     // folder may be existing but not subscribed (#1485241)
AM 594     else if (!$RCMAIL->storage->folder_exists($store_target)) {
595         $store_folder = $RCMAIL->storage->create_folder($store_target, true);
596     }
597     else if ($RCMAIL->storage->subscribe($store_target)) {
598         $store_folder = true;
56ec81 599     }
91790e 600
0f16a0 601     // append message to sent box
AM 602     if ($store_folder) {
603         // message body in file
604         if ($mailbody_file || $MAIL_MIME->getParam('delay_file_io')) {
605             $headers = $MAIL_MIME->txtHeaders();
606
607             // file already created
608             if ($mailbody_file) {
609                 $msg = $mailbody_file;
610             }
611             else {
612                 $temp_dir      = $RCMAIL->config->get('temp_dir');
613                 $mailbody_file = tempnam($temp_dir, 'rcmMsg');
b59b72 614                 $msg           = $MAIL_MIME->saveMessageBody($mailbody_file);
0f16a0 615
b59b72 616                 if (!is_a($msg, 'PEAR_Error')) {
0f16a0 617                     $msg = $mailbody_file;
AM 618                 }
619             }
620         }
621         else {
622             $msg     = $MAIL_MIME->getMessage();
623             $headers = '';
624         }
625
b59b72 626         if (is_a($msg, 'PEAR_Error')) {
0f16a0 627             rcube::raise_error(array('code' => 650, 'type' => 'php',
AM 628                 'file' => __FILE__, 'line' => __LINE__,
629                 'message' => "Could not create message: ".$msg->getMessage()),
630                 true, false);
631         }
632         else {
633             $saved = $RCMAIL->storage->save_message($store_target, $msg, $headers,
634                 $mailbody_file ? true : false, array('SEEN'));
635         }
636
637         if ($mailbody_file) {
638             unlink($mailbody_file);
639             $mailbody_file = null;
640         }
56ec81 641     }
91790e 642
0f16a0 643     // raise error if saving failed
AM 644     if (!$saved) {
645         rcube::raise_error(array('code' => 800, 'type' => 'imap',
646             'file' => __FILE__, 'line' => __LINE__,
647             'message' => "Could not save message in $store_target"), true, false);
648
649         if ($savedraft) {
216b31 650             $RCMAIL->display_server_error('errorsaving');
AM 651
0f16a0 652             // start the auto-save timer again
AM 653             $OUTPUT->command('auto_save_start');
654             $OUTPUT->send('iframe');
655         }
56ec81 656     }
A 657 }
91790e 658 // remove temp file
A 659 else if ($mailbody_file) {
0f16a0 660     unlink($mailbody_file);
56ec81 661 }
91790e 662
8b0d81 663 // delete previous saved draft
AM 664 $old_id = rcube_utils::get_input_value('_draft_saveid', rcube_utils::INPUT_POST);
665 if ($old_id && ($sent || $saved)) {
666     $deleted = $RCMAIL->storage->delete_message($old_id, $drafts_mbox);
667
668     // raise error if deletion of old draft failed
669     if (!$deleted) {
670         rcube::raise_error(array('code' => 800, 'type' => 'imap',
671             'file' => __FILE__, 'line' => __LINE__,
672             'message' => "Could not delete message from $drafts_mbox"), true, false);
673     }
674 }
4e17e6 675
56ec81 676 if ($savedraft) {
0f16a0 677     // remember new draft-uid ($saved could be an UID or true/false here)
AM 678     if ($saved && is_bool($saved)) {
679         $index = $RCMAIL->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
680         $saved = @max($index->get());
681     }
c719f3 682
0f16a0 683     if ($saved) {
AM 684         $plugin = $RCMAIL->plugins->exec_hook('message_draftsaved',
685             array('msgid' => $message_id, 'uid' => $saved, 'folder' => $store_target));
f0f98f 686
0f16a0 687         // display success
827159 688         $OUTPUT->show_message($plugin['message'] ?: 'messagesaved', 'confirmation');
10936f 689
0f16a0 690         // update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
AM 691         $COMPOSE['param']['draft_uid'] = $plugin['uid'];
692         $OUTPUT->command('set_draft_id', $plugin['uid']);
693         $OUTPUT->command('compose_field_hash', true);
694     }
41fa0b 695
0f16a0 696     // start the auto-save timer again
AM 697     $OUTPUT->command('auto_save_start');
56ec81 698 }
A 699 else {
9ed6d4 700     // Collect folders which could contain the composed message,
AM 701     // we'll refresh the list if currently opened folder is one of them (#1490238)
0f16a0 702     $folders = array();
66a549 703
c5c8e7 704     if (!$saveonly) {
AM 705         if (in_array($COMPOSE['mode'], array('reply', 'forward', 'draft'))) {
706             $folders[] = $COMPOSE['mailbox'];
707         }
708         if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
709             $folders[] = $drafts_mbox;
710         }
0f16a0 711     }
9a5762 712
0f16a0 713     if ($store_folder && !$saved) {
c5c8e7 714         $params = $saveonly ? null : array('prefix' => true);
AM 715         $RCMAIL->display_server_error('errorsavingsent', null, null, $params);
716         if ($saveonly) {
717             $OUTPUT->send('iframe');
718         }
719
720         $save_error = true;
0f16a0 721     }
c5c8e7 722     else {
AM 723         rcmail_compose_cleanup($COMPOSE_ID);
724         $OUTPUT->command('remove_compose_data', $COMPOSE_ID);
725
726         if ($store_folder) {
727             $folders[] = $store_target;
728         }
66a549 729     }
AM 730
c5c8e7 731     $msg = $RCMAIL->gettext($saveonly ? 'successfullysaved' : 'messagesent');
AM 732
733     $OUTPUT->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
56ec81 734 }
66a549 735
AM 736 $OUTPUT->send('iframe');
f5d2ee 737
AM 738
739 /****** message sending functions ********/
740
fdbb1c 741 function rcmail_received_host($host)
AM 742 {
743     $hostname = gethostbyaddr($host);
744
745     $result = rcmail_encrypt_host($hostname);
746
747     if ($host != $hostname) {
748         $result .= ' (' . rcmail_encrypt_host($host) . ')';
749     }
750
751     return $result;
752 }
753
754 // encrypt host IP or hostname for Received header
755 function rcmail_encrypt_host($host)
f5d2ee 756 {
AM 757     global $RCMAIL;
758
fdbb1c 759     if ($RCMAIL->config->get('http_received_header_encrypt')) {
AM 760         return $RCMAIL->encrypt($host);
f5d2ee 761     }
AM 762
fdbb1c 763     if (!preg_match('/[^0-9:.]/', $host)) {
AM 764         return "[$host]";
765     }
766
767     return $host;
f5d2ee 768 }
AM 769
770 // get identity record
771 function rcmail_get_identity($id)
772 {
773     global $RCMAIL, $message_charset;
774
775     if ($sql_arr = $RCMAIL->user->get_identity($id)) {
776         $out = $sql_arr;
777
778         if ($message_charset != RCUBE_CHARSET) {
779             foreach ($out as $k => $v) {
780                 $out[$k] = rcube_charset::convert($v, RCUBE_CHARSET, $message_charset);
781             }
782         }
783
784         $out['mailto'] = $sql_arr['email'];
785         $out['string'] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
786
787         return $out;
788     }
789
790     return false;
791 }
792
793 /**
794  * Extract image attachments from HTML content (data URIs)
795  */
796 function rcmail_extract_inline_images($mime_message, $from)
797 {
798     $body   = $mime_message->getHTMLBody();
799     $offset = 0;
800     $list   = array();
14bd92 801     $domain = 'localhost';
AM 802     $regexp = '#img[^>]+src=[\'"](data:([^;]*);base64,([a-z0-9+/=\r\n]+))([\'"])#i';
f5d2ee 803
AM 804     if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) {
14bd92 805         // get domain for the Content-ID, must be the same as in Mail_Mime::get()
AM 806         if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $m)) {
807             $domain = $m[1];
808         }
809
f5d2ee 810         foreach ($matches[1] as $idx => $m) {
AM 811             $data = preg_replace('/\r\n/', '', $matches[3][$idx][0]);
812             $data = base64_decode($data);
813
814             if (empty($data)) {
815                 continue;
816             }
817
818             $hash      = md5($data) . '@' . $domain;
819             $mime_type = $matches[2][$idx][0];
820             $name      = $list[$hash];
821
14bd92 822             if (empty($mime_type)) {
AM 823                 $mime_type = rcube_mime::image_content_type($data);
824             }
825
f5d2ee 826             // add the image to the MIME message
AM 827             if (!$name) {
828                 $ext         = preg_replace('#^[^/]+/#', '', $mime_type);
829                 $name        = substr($hash, 0, 8) . '.' . $ext;
830                 $list[$hash] = $name;
831
832                 $mime_message->addHTMLImage($data, $mime_type, $name, false, $hash);
833             }
834
835             $body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0]));
836             $offset += strlen($name) - strlen($m[0]);
837         }
838     }
839
840     $mime_message->setHTMLBody($body);
841 }
842
843 /**
844  * Parse and cleanup email address input (and count addresses)
845  *
846  * @param string  Address input
847  * @param boolean Do count recipients (saved in global $RECIPIENT_COUNT)
848  * @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR)
849  * @return string Canonical recipients string separated by comma
850  */
851 function rcmail_email_input_format($mailto, $count=false, $check=true)
852 {
853     global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
854
855     // simplified email regexp, supporting quoted local part
856     $email_regexp = '(\S+|("[^"]+"))@\S+';
857
858     $delim   = trim($RCMAIL->config->get('recipients_separator', ','));
859     $regexp  = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
860     $replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');
861
862     // replace new lines and strip ending ', ', make address input more valid
863     $mailto = trim(preg_replace($regexp, $replace, $mailto));
864     $items  = rcube_utils::explode_quoted_string($delim, $mailto);
865     $result = array();
866
867     foreach ($items as $item) {
868         $item = trim($item);
869         // address in brackets without name (do nothing)
870         if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
871             $item     = rcube_utils::idn_to_ascii(trim($item, '<>'));
872             $result[] = $item;
873         }
874         // address without brackets and without name (add brackets)
875         else if (preg_match('/^'.$email_regexp.'$/', $item)) {
876             $item     = rcube_utils::idn_to_ascii($item);
877             $result[] = $item;
878         }
879         // address with name (handle name)
880         else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {
881             $address = $matches[0];
882             $name    = trim(str_replace($address, '', $item));
883             if ($name[0] == '"' && $name[count($name)-1] == '"') {
884                 $name = substr($name, 1, -1);
885             }
886             $name     = stripcslashes($name);
887             $address  = rcube_utils::idn_to_ascii(trim($address, '<>'));
888             $result[] = format_email_recipient($address, $name);
889             $item     = $address;
890         }
891         else if (trim($item)) {
892             continue;
893         }
894
895         // check address format
896         $item = trim($item, '<>');
897         if ($item && $check && !rcube_utils::check_email($item)) {
898             $EMAIL_FORMAT_ERROR = $item;
899             return;
900         }
901     }
902
903     if ($count) {
904         $RECIPIENT_COUNT += count($result);
905     }
906
907     return implode(', ', $result);
908 }
909
910
911 function rcmail_generic_message_footer($isHtml)
912 {
913     global $RCMAIL;
914
915     if ($isHtml && ($file = $RCMAIL->config->get('generic_message_footer_html'))) {
916         $html_footer = true;
917     }
918     else {
919         $file = $RCMAIL->config->get('generic_message_footer');
920         $html_footer = false;
921     }
922
923     if ($file && realpath($file)) {
924         // sanity check
925         if (!preg_match('/\.(php|ini|conf)$/', $file) && strpos($file, '/etc/') === false) {
926             $footer = file_get_contents($file);
927             if ($isHtml && !$html_footer) {
eda92e 928                 $t2h    = new rcube_text2html($footer, false);
AM 929                 $footer = $t2h->get_html();
f5d2ee 930             }
AM 931             return $footer;
932         }
933     }
934
935     return false;
936 }
4b72a1 937
AM 938 /**
939  * clear message composing settings
940  */
941 function rcmail_compose_cleanup($id)
942 {
943     if (!isset($_SESSION['compose_data_'.$id])) {
944         return;
945     }
946
947     $rcmail = rcmail::get_instance();
948     $rcmail->plugins->exec_hook('attachments_cleanup', array('group' => $id));
949     $rcmail->session->remove('compose_data_'.$id);
950
951     $_SESSION['last_compose_session'] = $id;
952 }