Mercurial > hg > rc2
comparison program/steps/mail/sendmail.inc @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children | aff04b06b685 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4681f974d28b |
---|---|
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 | |
722 if ($store_folder && !$saved) { | |
723 $params = $saveonly ? null : array('prefix' => true); | |
724 $RCMAIL->display_server_error('errorsavingsent', null, null, $params); | |
725 if ($saveonly) { | |
726 $OUTPUT->send('iframe'); | |
727 } | |
728 | |
729 $save_error = true; | |
730 } | |
731 else { | |
732 rcmail_compose_cleanup($COMPOSE_ID); | |
733 $OUTPUT->command('remove_compose_data', $COMPOSE_ID); | |
734 | |
735 if ($store_folder) { | |
736 $folders[] = $store_target; | |
737 } | |
738 } | |
739 | |
740 $msg = $RCMAIL->gettext($saveonly ? 'successfullysaved' : 'messagesent'); | |
741 | |
742 $OUTPUT->command('sent_successfully', 'confirmation', $msg, $folders, $save_error); | |
743 } | |
744 | |
745 $OUTPUT->send('iframe'); | |
746 | |
747 | |
748 /****** message sending functions ********/ | |
749 | |
750 function rcmail_received_host($host) | |
751 { | |
752 $hostname = gethostbyaddr($host); | |
753 | |
754 $result = rcmail_encrypt_host($hostname); | |
755 | |
756 if ($host != $hostname) { | |
757 $result .= ' (' . rcmail_encrypt_host($host) . ')'; | |
758 } | |
759 | |
760 return $result; | |
761 } | |
762 | |
763 // encrypt host IP or hostname for Received header | |
764 function rcmail_encrypt_host($host) | |
765 { | |
766 global $RCMAIL; | |
767 | |
768 if ($RCMAIL->config->get('http_received_header_encrypt')) { | |
769 return $RCMAIL->encrypt($host); | |
770 } | |
771 | |
772 if (!preg_match('/[^0-9:.]/', $host)) { | |
773 return "[$host]"; | |
774 } | |
775 | |
776 return $host; | |
777 } | |
778 | |
779 // get identity record | |
780 function rcmail_get_identity($id) | |
781 { | |
782 global $RCMAIL, $message_charset; | |
783 | |
784 if ($sql_arr = $RCMAIL->user->get_identity($id)) { | |
785 $out = $sql_arr; | |
786 | |
787 if ($message_charset != RCUBE_CHARSET) { | |
788 foreach ($out as $k => $v) { | |
789 $out[$k] = rcube_charset::convert($v, RCUBE_CHARSET, $message_charset); | |
790 } | |
791 } | |
792 | |
793 $out['mailto'] = $sql_arr['email']; | |
794 $out['string'] = format_email_recipient($sql_arr['email'], $sql_arr['name']); | |
795 | |
796 return $out; | |
797 } | |
798 | |
799 return false; | |
800 } | |
801 | |
802 /** | |
803 * Extract image attachments from HTML content (data URIs) | |
804 */ | |
805 function rcmail_extract_inline_images($mime_message, $from) | |
806 { | |
807 $body = $mime_message->getHTMLBody(); | |
808 $offset = 0; | |
809 $list = array(); | |
810 $domain = 'localhost'; | |
811 $regexp = '#img[^>]+src=[\'"](data:([^;]*);base64,([a-z0-9+/=\r\n]+))([\'"])#i'; | |
812 | |
813 if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) { | |
814 // get domain for the Content-ID, must be the same as in Mail_Mime::get() | |
815 if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $m)) { | |
816 $domain = $m[1]; | |
817 } | |
818 | |
819 foreach ($matches[1] as $idx => $m) { | |
820 $data = preg_replace('/\r\n/', '', $matches[3][$idx][0]); | |
821 $data = base64_decode($data); | |
822 | |
823 if (empty($data)) { | |
824 continue; | |
825 } | |
826 | |
827 $hash = md5($data) . '@' . $domain; | |
828 $mime_type = $matches[2][$idx][0]; | |
829 $name = $list[$hash]; | |
830 | |
831 if (empty($mime_type)) { | |
832 $mime_type = rcube_mime::image_content_type($data); | |
833 } | |
834 | |
835 // add the image to the MIME message | |
836 if (!$name) { | |
837 $ext = preg_replace('#^[^/]+/#', '', $mime_type); | |
838 $name = substr($hash, 0, 8) . '.' . $ext; | |
839 $list[$hash] = $name; | |
840 | |
841 $mime_message->addHTMLImage($data, $mime_type, $name, false, $hash); | |
842 } | |
843 | |
844 $body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0])); | |
845 $offset += strlen($name) - strlen($m[0]); | |
846 } | |
847 } | |
848 | |
849 $mime_message->setHTMLBody($body); | |
850 } | |
851 | |
852 /** | |
853 * Parse and cleanup email address input (and count addresses) | |
854 * | |
855 * @param string Address input | |
856 * @param boolean Do count recipients (saved in global $RECIPIENT_COUNT) | |
857 * @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR) | |
858 * @return string Canonical recipients string separated by comma | |
859 */ | |
860 function rcmail_email_input_format($mailto, $count=false, $check=true) | |
861 { | |
862 global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT; | |
863 | |
864 // simplified email regexp, supporting quoted local part | |
865 $email_regexp = '(\S+|("[^"]+"))@\S+'; | |
866 | |
867 $delim = trim($RCMAIL->config->get('recipients_separator', ',')); | |
868 $regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U'); | |
869 $replace = array($delim.' ', ', ', '', $delim, '\\1 \\2'); | |
870 | |
871 // replace new lines and strip ending ', ', make address input more valid | |
872 $mailto = trim(preg_replace($regexp, $replace, $mailto)); | |
873 $items = rcube_utils::explode_quoted_string($delim, $mailto); | |
874 $result = array(); | |
875 | |
876 foreach ($items as $item) { | |
877 $item = trim($item); | |
878 // address in brackets without name (do nothing) | |
879 if (preg_match('/^<'.$email_regexp.'>$/', $item)) { | |
880 $item = rcube_utils::idn_to_ascii(trim($item, '<>')); | |
881 $result[] = $item; | |
882 } | |
883 // address without brackets and without name (add brackets) | |
884 else if (preg_match('/^'.$email_regexp.'$/', $item)) { | |
885 $item = rcube_utils::idn_to_ascii($item); | |
886 $result[] = $item; | |
887 } | |
888 // address with name (handle name) | |
889 else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) { | |
890 $address = $matches[0]; | |
891 $name = trim(str_replace($address, '', $item)); | |
892 if ($name[0] == '"' && $name[count($name)-1] == '"') { | |
893 $name = substr($name, 1, -1); | |
894 } | |
895 $name = stripcslashes($name); | |
896 $address = rcube_utils::idn_to_ascii(trim($address, '<>')); | |
897 $result[] = format_email_recipient($address, $name); | |
898 $item = $address; | |
899 } | |
900 | |
901 // check address format | |
902 $item = trim($item, '<>'); | |
903 if ($item && $check && !rcube_utils::check_email($item)) { | |
904 $EMAIL_FORMAT_ERROR = $item; | |
905 return; | |
906 } | |
907 } | |
908 | |
909 if ($count) { | |
910 $RECIPIENT_COUNT += count($result); | |
911 } | |
912 | |
913 return implode(', ', $result); | |
914 } | |
915 | |
916 | |
917 function rcmail_generic_message_footer($isHtml) | |
918 { | |
919 global $RCMAIL; | |
920 | |
921 if ($isHtml && ($file = $RCMAIL->config->get('generic_message_footer_html'))) { | |
922 $html_footer = true; | |
923 } | |
924 else { | |
925 $file = $RCMAIL->config->get('generic_message_footer'); | |
926 $html_footer = false; | |
927 } | |
928 | |
929 if ($file && realpath($file)) { | |
930 // sanity check | |
931 if (!preg_match('/\.(php|ini|conf)$/', $file) && strpos($file, '/etc/') === false) { | |
932 $footer = file_get_contents($file); | |
933 if ($isHtml && !$html_footer) { | |
934 $t2h = new rcube_text2html($footer, false); | |
935 $footer = $t2h->get_html(); | |
936 } | |
937 return $footer; | |
938 } | |
939 } | |
940 | |
941 return false; | |
942 } | |
943 | |
944 /** | |
945 * clear message composing settings | |
946 */ | |
947 function rcmail_compose_cleanup($id) | |
948 { | |
949 if (!isset($_SESSION['compose_data_'.$id])) { | |
950 return; | |
951 } | |
952 | |
953 $rcmail = rcmail::get_instance(); | |
954 $rcmail->plugins->exec_hook('attachments_cleanup', array('group' => $id)); | |
955 $rcmail->session->remove('compose_data_'.$id); | |
956 | |
957 $_SESSION['last_compose_session'] = $id; | |
958 } |