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