RoundCube Webmail
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

963 lines
33 KiB

20 years ago
12 years ago
20 years ago
20 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
20 years ago
20 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
17 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
11 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
20 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. <?php
  2. /*
  3. +-----------------------------------------------------------------------+
  4. | program/steps/mail/sendmail.inc |
  5. | |
  6. | This file is part of the Roundcube Webmail client |
  7. | Copyright (C) 2005-2013, The Roundcube Dev Team |
  8. | |
  9. | Licensed under the GNU General Public License version 3 or |
  10. | any later version with exceptions for skins & plugins. |
  11. | See the README file for a full license statement. |
  12. | |
  13. | PURPOSE: |
  14. | Compose a new mail message with all headers and attachments |
  15. | and send it using the PEAR::Net_SMTP class or with PHP mail() |
  16. | |
  17. +-----------------------------------------------------------------------+
  18. | Author: Thomas Bruederli <roundcube@gmail.com> |
  19. +-----------------------------------------------------------------------+
  20. */
  21. // remove all scripts and act as called in frame
  22. $OUTPUT->reset();
  23. $OUTPUT->framed = TRUE;
  24. $saveonly = !empty($_GET['_saveonly']);
  25. $savedraft = !empty($_POST['_draft']) && !$saveonly;
  26. $sendmail_delay = (int) $RCMAIL->config->get('sendmail_delay');
  27. $drafts_mbox = $RCMAIL->config->get('drafts_mbox');
  28. $COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
  29. $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
  30. /****** checks ********/
  31. if (!isset($COMPOSE['id'])) {
  32. rcube::raise_error(array('code' => 500, 'type' => 'php',
  33. 'file' => __FILE__, 'line' => __LINE__,
  34. 'message' => "Invalid compose ID"), true, false);
  35. $OUTPUT->show_message('internalerror', 'error');
  36. $OUTPUT->send('iframe');
  37. }
  38. if (!$savedraft) {
  39. if (empty($_POST['_to']) && empty($_POST['_cc']) && empty($_POST['_bcc'])
  40. && empty($_POST['_subject']) && $_POST['_message']
  41. ) {
  42. $OUTPUT->show_message('sendingfailed', 'error');
  43. $OUTPUT->send('iframe');
  44. }
  45. if ($sendmail_delay) {
  46. $wait_sec = time() - $sendmail_delay - intval($RCMAIL->config->get('last_message_time'));
  47. if ($wait_sec < 0) {
  48. $OUTPUT->show_message('senttooquickly', 'error', array('sec' => $wait_sec * -1));
  49. $OUTPUT->send('iframe');
  50. }
  51. }
  52. }
  53. /****** compose message ********/
  54. if (empty($COMPOSE['param']['message-id'])) {
  55. $COMPOSE['param']['message-id'] = $RCMAIL->gen_message_id();
  56. }
  57. $message_id = $COMPOSE['param']['message-id'];
  58. // set default charset
  59. $message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $OUTPUT->get_charset();
  60. $EMAIL_FORMAT_ERROR = NULL;
  61. $RECIPIENT_COUNT = 0;
  62. $mailto = rcmail_email_input_format(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
  63. $mailcc = rcmail_email_input_format(rcube_utils::get_input_value('_cc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
  64. $mailbcc = rcmail_email_input_format(rcube_utils::get_input_value('_bcc', rcube_utils::INPUT_POST, TRUE, $message_charset), true);
  65. if ($EMAIL_FORMAT_ERROR && !$savedraft) {
  66. $OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR));
  67. $OUTPUT->send('iframe');
  68. }
  69. if (empty($mailto) && !empty($mailcc)) {
  70. $mailto = $mailcc;
  71. $mailcc = null;
  72. }
  73. else if (empty($mailto)) {
  74. $mailto = 'undisclosed-recipients:;';
  75. }
  76. // Get sender name and address...
  77. $from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST, true, $message_charset);
  78. // ... from identity...
  79. if (is_numeric($from)) {
  80. if (is_array($identity_arr = rcmail_get_identity($from))) {
  81. if ($identity_arr['mailto'])
  82. $from = $identity_arr['mailto'];
  83. if ($identity_arr['string'])
  84. $from_string = $identity_arr['string'];
  85. }
  86. else {
  87. $from = null;
  88. }
  89. }
  90. // ... if there is no identity record, this might be a custom from
  91. else if ($from_string = rcmail_email_input_format($from)) {
  92. if (preg_match('/(\S+@\S+)/', $from_string, $m))
  93. $from = trim($m[1], '<>');
  94. else
  95. $from = null;
  96. }
  97. if (!$from_string && $from) {
  98. $from_string = $from;
  99. }
  100. // compose headers array
  101. $headers = array();
  102. // if configured, the Received headers goes to top, for good measure
  103. if ($RCMAIL->config->get('http_received_header')) {
  104. $nldlm = "\r\n\t";
  105. $encrypt = $RCMAIL->config->get('http_received_header_encrypt');
  106. // FROM/VIA
  107. $http_header = 'from ';
  108. if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  109. $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2);
  110. $hostname = gethostbyaddr($hosts[0]);
  111. if ($encrypt) {
  112. $http_header .= rcmail_encrypt_header($hostname);
  113. if ($host != $hostname)
  114. $http_header .= ' ('. rcmail_encrypt_header($host) . ')';
  115. }
  116. else {
  117. $http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
  118. if ($host != $hostname)
  119. $http_header .= ' (['. $host .'])';
  120. }
  121. $http_header .= $nldlm . ' via ';
  122. }
  123. $host = $_SERVER['REMOTE_ADDR'];
  124. $hostname = gethostbyaddr($host);
  125. if ($encrypt) {
  126. $http_header .= rcmail_encrypt_header($hostname);
  127. if ($host != $hostname)
  128. $http_header .= ' ('. rcmail_encrypt_header($host) . ')';
  129. }
  130. else {
  131. $http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
  132. if ($host != $hostname)
  133. $http_header .= ' (['. $host .'])';
  134. }
  135. // BY
  136. $http_header .= $nldlm . 'by ' . $_SERVER['HTTP_HOST'];
  137. // WITH
  138. $http_header .= $nldlm . 'with HTTP (' . $_SERVER['SERVER_PROTOCOL'] .
  139. ' '.$_SERVER['REQUEST_METHOD'] . '); ' . date('r');
  140. $http_header = wordwrap($http_header, 69, $nldlm);
  141. $headers['Received'] = $http_header;
  142. }
  143. $headers['Date'] = $RCMAIL->user_date();
  144. $headers['From'] = rcube_charset::convert($from_string, RCUBE_CHARSET, $message_charset);
  145. $headers['To'] = $mailto;
  146. // additional recipients
  147. if (!empty($mailcc)) {
  148. $headers['Cc'] = $mailcc;
  149. }
  150. if (!empty($mailbcc)) {
  151. $headers['Bcc'] = $mailbcc;
  152. }
  153. if (($max_recipients = (int) $RCMAIL->config->get('max_recipients')) > 0) {
  154. if ($RECIPIENT_COUNT > $max_recipients) {
  155. $OUTPUT->show_message('toomanyrecipients', 'error', array('max' => $max_recipients));
  156. $OUTPUT->send('iframe');
  157. }
  158. }
  159. $dont_override = (array) $RCMAIL->config->get('dont_override');
  160. $mdn_enabled = in_array('mdn_default', $dont_override) ? $RCMAIL->config->get('mdn_default') : !empty($_POST['_mdn']);
  161. $dsn_enabled = in_array('dsn_default', $dont_override) ? $RCMAIL->config->get('dsn_default') : !empty($_POST['_dsn']);
  162. // add subject
  163. $headers['Subject'] = trim(rcube_utils::get_input_value('_subject', rcube_utils::INPUT_POST, TRUE, $message_charset));
  164. if (!empty($identity_arr['organization'])) {
  165. $headers['Organization'] = $identity_arr['organization'];
  166. }
  167. if ($hdr = rcube_utils::get_input_value('_replyto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
  168. $headers['Reply-To'] = rcmail_email_input_format($hdr);
  169. }
  170. if (!empty($headers['Reply-To'])) {
  171. $headers['Mail-Reply-To'] = $headers['Reply-To'];
  172. }
  173. if ($hdr = rcube_utils::get_input_value('_followupto', rcube_utils::INPUT_POST, TRUE, $message_charset)) {
  174. $headers['Mail-Followup-To'] = rcmail_email_input_format($hdr);
  175. }
  176. // remember reply/forward UIDs in special headers
  177. if (!empty($COMPOSE['reply_uid']) && $savedraft) {
  178. $headers['X-Draft-Info'] = array('type' => 'reply', 'uid' => $COMPOSE['reply_uid']);
  179. }
  180. else if (!empty($COMPOSE['forward_uid']) && $savedraft) {
  181. $headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => rcube_imap_generic::compressMessageSet($COMPOSE['forward_uid']));
  182. }
  183. if (!empty($COMPOSE['reply_msgid'])) {
  184. $headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
  185. }
  186. if (!empty($COMPOSE['references'])) {
  187. $headers['References'] = $COMPOSE['references'];
  188. }
  189. if (!empty($_POST['_priority'])) {
  190. $priority = intval($_POST['_priority']);
  191. $a_priorities = array(1 => 'highest', 2 => 'high', 4 => 'low', 5 => 'lowest');
  192. if ($str_priority = $a_priorities[$priority]) {
  193. $headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority));
  194. }
  195. }
  196. if ($mdn_enabled) {
  197. $headers['Return-Receipt-To'] = $from_string;
  198. $headers['Disposition-Notification-To'] = $from_string;
  199. }
  200. // additional headers
  201. $headers['Message-ID'] = $message_id;
  202. $headers['X-Sender'] = $from;
  203. if (is_array($headers['X-Draft-Info'])) {
  204. $headers['X-Draft-Info'] = rcmail_draftinfo_encode($headers['X-Draft-Info'] + array('folder' => $COMPOSE['mailbox']));
  205. }
  206. if ($hdr = $RCMAIL->config->get('useragent')) {
  207. $headers['User-Agent'] = $hdr;
  208. }
  209. // exec hook for header checking and manipulation
  210. // Depracated: use message_before_send hook instead
  211. $data = $RCMAIL->plugins->exec_hook('message_outgoing_headers', array('headers' => $headers));
  212. // sending aborted by plugin
  213. if ($data['abort'] && !$savedraft) {
  214. $OUTPUT->show_message($data['message'] ? $data['message'] : 'sendingfailed');
  215. $OUTPUT->send('iframe');
  216. }
  217. else {
  218. $headers = $data['headers'];
  219. }
  220. $isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
  221. // fetch message body
  222. $message_body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, TRUE, $message_charset);
  223. if ($isHtml) {
  224. $bstyle = array();
  225. if ($font_size = $RCMAIL->config->get('default_font_size')) {
  226. $bstyle[] = 'font-size: ' . $font_size;
  227. }
  228. if ($font_family = $RCMAIL->config->get('default_font')) {
  229. $bstyle[] = 'font-family: ' . rcmail::font_defs($font_family);
  230. }
  231. // append doctype and html/body wrappers
  232. $bstyle = !empty($bstyle) ? (" style='" . implode($bstyle, '; ') . "'") : '';
  233. $message_body = '<html><head>'
  234. . '<meta http-equiv="Content-Type" content="text/html; charset=' . $message_charset . '" /></head>'
  235. . "<body" . $bstyle . ">\r\n" . $message_body;
  236. }
  237. if (!$savedraft) {
  238. if ($isHtml) {
  239. $b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
  240. $pre_style = 'margin: 0; padding: 0; font-family: monospace';
  241. $message_body = preg_replace(
  242. array(
  243. // remove signature's div ID
  244. '/\s*id="_rc_sig"/',
  245. // add inline css for blockquotes and container
  246. '/<blockquote>/',
  247. '/<div class="pre">/'
  248. ),
  249. array(
  250. '',
  251. '<blockquote type="cite" style="'.$b_style.'">',
  252. '<div class="pre" style="'.$pre_style.'">'
  253. ),
  254. $message_body);
  255. }
  256. // Check spelling before send
  257. if ($RCMAIL->config->get('spellcheck_before_send') && $RCMAIL->config->get('enable_spellcheck')
  258. && empty($COMPOSE['spell_checked']) && !empty($message_body)
  259. ) {
  260. $message_body = str_replace("\r\n", "\n", $message_body);
  261. $spellchecker = new rcube_spellchecker(rcube_utils::get_input_value('_lang', rcube_utils::INPUT_GPC));
  262. $spell_result = $spellchecker->check($message_body, $isHtml);
  263. $COMPOSE['spell_checked'] = true;
  264. if (!$spell_result) {
  265. if ($isHtml) {
  266. $result['words'] = $spellchecker->get();
  267. $result['dictionary'] = (bool) $RCMAIL->config->get('spellcheck_dictionary');
  268. }
  269. else {
  270. $result = $spellchecker->get_xml();
  271. }
  272. $OUTPUT->show_message('mispellingsfound', 'error');
  273. $OUTPUT->command('spellcheck_resume', $result);
  274. $OUTPUT->send('iframe');
  275. }
  276. }
  277. // generic footer for all messages
  278. if ($footer = rcmail_generic_message_footer($isHtml)) {
  279. $footer = rcube_charset::convert($footer, RCUBE_CHARSET, $message_charset);
  280. $message_body .= "\r\n" . $footer;
  281. }
  282. }
  283. if ($isHtml) {
  284. $message_body .= "\r\n</body></html>\r\n";
  285. }
  286. // sort attachments to make sure the order is the same as in the UI (#1488423)
  287. if ($files = rcube_utils::get_input_value('_attachments', rcube_utils::INPUT_POST)) {
  288. $files = explode(',', $files);
  289. $files = array_flip($files);
  290. foreach ($files as $idx => $val) {
  291. $files[$idx] = $COMPOSE['attachments'][$idx];
  292. unset($COMPOSE['attachments'][$idx]);
  293. }
  294. $COMPOSE['attachments'] = array_merge(array_filter($files), $COMPOSE['attachments']);
  295. }
  296. // set line length for body wrapping
  297. $LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
  298. // Since we can handle big messages with disk usage, we need more time to work
  299. @set_time_limit(0);
  300. // create PEAR::Mail_mime instance
  301. $MAIL_MIME = new Mail_mime("\r\n");
  302. // Check if we have enough memory to handle the message in it
  303. // It's faster than using files, so we'll do this if we only can
  304. if (is_array($COMPOSE['attachments']) && $RCMAIL->config->get('smtp_server')
  305. && ($mem_limit = parse_bytes(ini_get('memory_limit')))
  306. ) {
  307. $memory = 0;
  308. foreach ($COMPOSE['attachments'] as $id => $attachment) {
  309. $memory += $attachment['size'];
  310. }
  311. // Yeah, Net_SMTP needs up to 12x more memory, 1.33 is for base64
  312. if (!rcube_utils::mem_check($memory * 1.33 * 12)) {
  313. $MAIL_MIME->setParam('delay_file_io', true);
  314. }
  315. }
  316. // For HTML-formatted messages, construct the MIME message with both
  317. // the HTML part and the plain-text part
  318. if ($isHtml) {
  319. $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
  320. array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME));
  321. $MAIL_MIME->setHTMLBody($plugin['body']);
  322. // replace emoticons
  323. $plugin['body'] = $RCMAIL->replace_emoticons($plugin['body']);
  324. // add a plain text version of the e-mail as an alternative part.
  325. $h2t = new rcube_html2text($plugin['body'], false, true, 0, $message_charset);
  326. $plainTextPart = rcube_mime::wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n", false, $message_charset);
  327. $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
  328. // make sure all line endings are CRLF (#1486712)
  329. $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
  330. $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
  331. array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
  332. $MAIL_MIME->setTXTBody($plugin['body']);
  333. // look for "emoticon" images from TinyMCE and change their src paths to
  334. // be file paths on the server instead of URL paths.
  335. rcmail_fix_emoticon_paths($MAIL_MIME);
  336. // Extract image Data URIs into message attachments (#1488502)
  337. rcmail_extract_inline_images($MAIL_MIME, $from);
  338. }
  339. else {
  340. $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
  341. array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME));
  342. $message_body = $plugin['body'];
  343. // compose format=flowed content if enabled
  344. if ($flowed = ($savedraft || $RCMAIL->config->get('send_format_flowed', true)))
  345. $message_body = rcube_mime::format_flowed($message_body, min($LINE_LENGTH+2, 79), $message_charset);
  346. else
  347. $message_body = rcube_mime::wordwrap($message_body, $LINE_LENGTH, "\r\n", false, $message_charset);
  348. $message_body = wordwrap($message_body, 998, "\r\n", true);
  349. $MAIL_MIME->setTXTBody($message_body, false, true);
  350. }
  351. // add stored attachments, if any
  352. if (is_array($COMPOSE['attachments'])) {
  353. foreach ($COMPOSE['attachments'] as $id => $attachment) {
  354. // This hook retrieves the attachment contents from the file storage backend
  355. $attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment);
  356. if ($isHtml) {
  357. $dispurl = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
  358. $message_body = $MAIL_MIME->getHTMLBody();
  359. $is_inline = preg_match($dispurl, $message_body);
  360. }
  361. else {
  362. $is_inline = false;
  363. }
  364. // inline image
  365. if ($is_inline) {
  366. // Mail_Mime does not support many inline attachments with the same name (#1489406)
  367. // we'll generate cid: urls here to workaround this
  368. $cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
  369. if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $from, $matches)) {
  370. $cid .= $matches[1];
  371. }
  372. else {
  373. $cid .= '@localhost';
  374. }
  375. $message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
  376. $MAIL_MIME->setHTMLBody($message_body);
  377. if ($attachment['data'])
  378. $MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false, $cid);
  379. else
  380. $MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true, $cid);
  381. }
  382. else {
  383. $ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
  384. $file = $attachment['data'] ? $attachment['data'] : $attachment['path'];
  385. $folding = (int) $RCMAIL->config->get('mime_param_folding');
  386. $MAIL_MIME->addAttachment($file,
  387. $ctype,
  388. $attachment['name'],
  389. $attachment['data'] ? false : true,
  390. $ctype == 'message/rfc822' ? '8bit' : 'base64',
  391. 'attachment',
  392. $attachment['charset'],
  393. '', '',
  394. $folding ? 'quoted-printable' : NULL,
  395. $folding == 2 ? 'quoted-printable' : NULL,
  396. '', RCUBE_CHARSET
  397. );
  398. }
  399. }
  400. }
  401. // choose transfer encoding for plain/text body
  402. if (preg_match('/[^\x00-\x7F]/', $MAIL_MIME->getTXTBody())) {
  403. $text_charset = $message_charset;
  404. $transfer_encoding = $RCMAIL->config->get('force_7bit') ? 'quoted-printable' : '8bit';
  405. }
  406. else {
  407. $text_charset = 'US-ASCII';
  408. $transfer_encoding = '7bit';
  409. }
  410. if ($flowed) {
  411. $text_charset .= ";\r\n format=flowed";
  412. }
  413. // encoding settings for mail composing
  414. $MAIL_MIME->setParam('text_encoding', $transfer_encoding);
  415. $MAIL_MIME->setParam('html_encoding', 'quoted-printable');
  416. $MAIL_MIME->setParam('head_encoding', 'quoted-printable');
  417. $MAIL_MIME->setParam('head_charset', $message_charset);
  418. $MAIL_MIME->setParam('html_charset', $message_charset);
  419. $MAIL_MIME->setParam('text_charset', $text_charset);
  420. // encoding subject header with mb_encode provides better results with asian characters
  421. if (function_exists('mb_encode_mimeheader')) {
  422. mb_internal_encoding($message_charset);
  423. $headers['Subject'] = mb_encode_mimeheader($headers['Subject'],
  424. $message_charset, 'Q', "\r\n", 8);
  425. mb_internal_encoding(RCUBE_CHARSET);
  426. }
  427. // pass headers to message object
  428. $MAIL_MIME->headers($headers);
  429. // Begin SMTP Delivery Block
  430. if (!$savedraft && !$saveonly) {
  431. // check 'From' address (identity may be incomplete)
  432. if (empty($from)) {
  433. $OUTPUT->show_message('nofromaddress', 'error');
  434. $OUTPUT->send('iframe');
  435. }
  436. // Handle Delivery Status Notification request
  437. $smtp_opts['dsn'] = $dsn_enabled;
  438. $sent = $RCMAIL->deliver_message($MAIL_MIME, $from, $mailto,
  439. $smtp_error, $mailbody_file, $smtp_opts);
  440. // return to compose page if sending failed
  441. if (!$sent) {
  442. // remove temp file
  443. if ($mailbody_file) {
  444. unlink($mailbody_file);
  445. }
  446. if ($smtp_error)
  447. $OUTPUT->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
  448. else
  449. $OUTPUT->show_message('sendingfailed', 'error');
  450. $OUTPUT->send('iframe');
  451. }
  452. // save message sent time
  453. if ($sendmail_delay) {
  454. $RCMAIL->user->save_prefs(array('last_message_time' => time()));
  455. }
  456. // set replied/forwarded flag
  457. if ($COMPOSE['reply_uid']) {
  458. foreach (rcmail::get_uids($COMPOSE['reply_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
  459. $RCMAIL->storage->set_flag($uids, 'ANSWERED', $mbox);
  460. }
  461. }
  462. else if ($COMPOSE['forward_uid']) {
  463. foreach (rcmail::get_uids($COMPOSE['forward_uid'], $COMPOSE['mailbox']) as $mbox => $uids) {
  464. $RCMAIL->storage->set_flag($uids, 'FORWARDED', $mbox);
  465. }
  466. }
  467. }
  468. // Determine which folder to save message
  469. if ($savedraft) {
  470. $store_target = $drafts_mbox;
  471. }
  472. else if (!$RCMAIL->config->get('no_save_sent_messages')) {
  473. if (isset($_POST['_store_target'])) {
  474. $store_target = rcube_utils::get_input_value('_store_target', rcube_utils::INPUT_POST);
  475. }
  476. else {
  477. $store_target = $RCMAIL->config->get('sent_mbox');
  478. }
  479. }
  480. if ($store_target) {
  481. // check if folder is subscribed
  482. if ($RCMAIL->storage->folder_exists($store_target, true)) {
  483. $store_folder = true;
  484. }
  485. // folder may be existing but not subscribed (#1485241)
  486. else if (!$RCMAIL->storage->folder_exists($store_target)) {
  487. $store_folder = $RCMAIL->storage->create_folder($store_target, true);
  488. }
  489. else if ($RCMAIL->storage->subscribe($store_target)) {
  490. $store_folder = true;
  491. }
  492. // append message to sent box
  493. if ($store_folder) {
  494. // message body in file
  495. if ($mailbody_file || $MAIL_MIME->getParam('delay_file_io')) {
  496. $headers = $MAIL_MIME->txtHeaders();
  497. // file already created
  498. if ($mailbody_file) {
  499. $msg = $mailbody_file;
  500. }
  501. else {
  502. $temp_dir = $RCMAIL->config->get('temp_dir');
  503. $mailbody_file = tempnam($temp_dir, 'rcmMsg');
  504. if (!PEAR::isError($msg = $MAIL_MIME->saveMessageBody($mailbody_file))) {
  505. $msg = $mailbody_file;
  506. }
  507. }
  508. }
  509. else {
  510. $msg = $MAIL_MIME->getMessage();
  511. $headers = '';
  512. }
  513. if (PEAR::isError($msg)) {
  514. rcube::raise_error(array('code' => 650, 'type' => 'php',
  515. 'file' => __FILE__, 'line' => __LINE__,
  516. 'message' => "Could not create message: ".$msg->getMessage()),
  517. true, false);
  518. }
  519. else {
  520. $saved = $RCMAIL->storage->save_message($store_target, $msg, $headers,
  521. $mailbody_file ? true : false, array('SEEN'));
  522. }
  523. if ($mailbody_file) {
  524. unlink($mailbody_file);
  525. $mailbody_file = null;
  526. }
  527. }
  528. // raise error if saving failed
  529. if (!$saved) {
  530. rcube::raise_error(array('code' => 800, 'type' => 'imap',
  531. 'file' => __FILE__, 'line' => __LINE__,
  532. 'message' => "Could not save message in $store_target"), true, false);
  533. if ($savedraft) {
  534. $RCMAIL->display_server_error('errorsaving');
  535. // start the auto-save timer again
  536. $OUTPUT->command('auto_save_start');
  537. $OUTPUT->send('iframe');
  538. }
  539. }
  540. // delete previous saved draft
  541. if ($saved && ($old_id = rcube_utils::get_input_value('_draft_saveid', rcube_utils::INPUT_POST))) {
  542. $deleted = $RCMAIL->storage->delete_message($old_id, $drafts_mbox);
  543. // raise error if deletion of old draft failed
  544. if (!$deleted) {
  545. rcube::raise_error(array('code' => 800, 'type' => 'imap',
  546. 'file' => __FILE__, 'line' => __LINE__,
  547. 'message' => "Could not delete message from $drafts_mbox"), true, false);
  548. }
  549. }
  550. }
  551. // remove temp file
  552. else if ($mailbody_file) {
  553. unlink($mailbody_file);
  554. }
  555. if ($savedraft) {
  556. // remember new draft-uid ($saved could be an UID or true/false here)
  557. if ($saved && is_bool($saved)) {
  558. $index = $RCMAIL->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
  559. $saved = @max($index->get());
  560. }
  561. if ($saved) {
  562. $plugin = $RCMAIL->plugins->exec_hook('message_draftsaved',
  563. array('msgid' => $message_id, 'uid' => $saved, 'folder' => $store_target));
  564. // display success
  565. $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'messagesaved', 'confirmation');
  566. // update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
  567. $COMPOSE['param']['draft_uid'] = $plugin['uid'];
  568. $OUTPUT->command('set_draft_id', $plugin['uid']);
  569. $OUTPUT->command('compose_field_hash', true);
  570. }
  571. // start the auto-save timer again
  572. $OUTPUT->command('auto_save_start');
  573. }
  574. else {
  575. // Collect folders which could contain the composed message,
  576. // we'll refresh the list if currently opened folder is one of them (#1490238)
  577. $folders = array();
  578. if (!$saveonly) {
  579. if (in_array($COMPOSE['mode'], array('reply', 'forward', 'draft'))) {
  580. $folders[] = $COMPOSE['mailbox'];
  581. }
  582. if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
  583. $folders[] = $drafts_mbox;
  584. }
  585. }
  586. if ($store_folder && !$saved) {
  587. $params = $saveonly ? null : array('prefix' => true);
  588. $RCMAIL->display_server_error('errorsavingsent', null, null, $params);
  589. if ($saveonly) {
  590. $OUTPUT->send('iframe');
  591. }
  592. $save_error = true;
  593. }
  594. else {
  595. rcmail_compose_cleanup($COMPOSE_ID);
  596. $OUTPUT->command('remove_compose_data', $COMPOSE_ID);
  597. if ($store_folder) {
  598. $folders[] = $store_target;
  599. }
  600. }
  601. $msg = $RCMAIL->gettext($saveonly ? 'successfullysaved' : 'messagesent');
  602. $OUTPUT->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
  603. }
  604. $OUTPUT->send('iframe');
  605. /****** message sending functions ********/
  606. // encrypt parts of the header
  607. function rcmail_encrypt_header($what)
  608. {
  609. global $RCMAIL;
  610. if (!$RCMAIL->config->get('http_received_header_encrypt')) {
  611. return $what;
  612. }
  613. return $RCMAIL->encrypt($what);
  614. }
  615. // get identity record
  616. function rcmail_get_identity($id)
  617. {
  618. global $RCMAIL, $message_charset;
  619. if ($sql_arr = $RCMAIL->user->get_identity($id)) {
  620. $out = $sql_arr;
  621. if ($message_charset != RCUBE_CHARSET) {
  622. foreach ($out as $k => $v) {
  623. $out[$k] = rcube_charset::convert($v, RCUBE_CHARSET, $message_charset);
  624. }
  625. }
  626. $out['mailto'] = $sql_arr['email'];
  627. $out['string'] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
  628. return $out;
  629. }
  630. return false;
  631. }
  632. /**
  633. * go from this:
  634. * <img src="http[s]://.../tinymce/plugins/emoticons/img/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
  635. *
  636. * to this:
  637. *
  638. * <img src="/path/on/server/.../tinymce/plugins/emoticons/img/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
  639. */
  640. function rcmail_fix_emoticon_paths($mime_message)
  641. {
  642. global $RCMAIL;
  643. $body = $mime_message->getHTMLBody();
  644. // remove any null-byte characters before parsing
  645. $body = preg_replace('/\x00/', '', $body);
  646. $searchstr = 'program/js/tinymce/plugins/emoticons/img/';
  647. $assets_dir = $RCMAIL->config->get('assets_dir');
  648. $path = ($assets_dir ?: INSTALL_PATH) . '/' . $searchstr;
  649. $offset = 0;
  650. // keep track of added images, so they're only added once
  651. $included_images = array();
  652. if (preg_match_all('# src=[\'"]([^\'"]+)#', $body, $matches, PREG_OFFSET_CAPTURE)) {
  653. foreach ($matches[1] as $m) {
  654. // find emoticon image tags
  655. if (preg_match('#'.$searchstr.'(.*)$#', $m[0], $imatches)) {
  656. $image_name = $imatches[1];
  657. // sanitize image name so resulting attachment doesn't leave images dir
  658. $image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
  659. $img_file = $path . $image_name;
  660. if (!in_array($image_name, $included_images)) {
  661. // add the image to the MIME message
  662. $res = $mime_message->addHTMLImage($img_file, 'image/gif', '', true, $image_name);
  663. if (PEAR::isError($res)) {
  664. $RCMAIL->output->show_message("emoticonerror", 'error');
  665. continue;
  666. }
  667. array_push($included_images, $image_name);
  668. }
  669. $body = substr_replace($body, $img_file, $m[1] + $offset, strlen($m[0]));
  670. $offset += strlen($img_file) - strlen($m[0]);
  671. }
  672. }
  673. }
  674. $mime_message->setHTMLBody($body);
  675. }
  676. /**
  677. * Extract image attachments from HTML content (data URIs)
  678. */
  679. function rcmail_extract_inline_images($mime_message, $from)
  680. {
  681. $body = $mime_message->getHTMLBody();
  682. $offset = 0;
  683. $list = array();
  684. $domain = 'localhost';
  685. $regexp = '#img[^>]+src=[\'"](data:([^;]*);base64,([a-z0-9+/=\r\n]+))([\'"])#i';
  686. if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) {
  687. // get domain for the Content-ID, must be the same as in Mail_Mime::get()
  688. if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $m)) {
  689. $domain = $m[1];
  690. }
  691. foreach ($matches[1] as $idx => $m) {
  692. $data = preg_replace('/\r\n/', '', $matches[3][$idx][0]);
  693. $data = base64_decode($data);
  694. if (empty($data)) {
  695. continue;
  696. }
  697. $hash = md5($data) . '@' . $domain;
  698. $mime_type = $matches[2][$idx][0];
  699. $name = $list[$hash];
  700. if (empty($mime_type)) {
  701. $mime_type = rcube_mime::image_content_type($data);
  702. }
  703. // add the image to the MIME message
  704. if (!$name) {
  705. $ext = preg_replace('#^[^/]+/#', '', $mime_type);
  706. $name = substr($hash, 0, 8) . '.' . $ext;
  707. $list[$hash] = $name;
  708. $mime_message->addHTMLImage($data, $mime_type, $name, false, $hash);
  709. }
  710. $body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0]));
  711. $offset += strlen($name) - strlen($m[0]);
  712. }
  713. }
  714. $mime_message->setHTMLBody($body);
  715. }
  716. /**
  717. * Parse and cleanup email address input (and count addresses)
  718. *
  719. * @param string Address input
  720. * @param boolean Do count recipients (saved in global $RECIPIENT_COUNT)
  721. * @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR)
  722. * @return string Canonical recipients string separated by comma
  723. */
  724. function rcmail_email_input_format($mailto, $count=false, $check=true)
  725. {
  726. global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
  727. // simplified email regexp, supporting quoted local part
  728. $email_regexp = '(\S+|("[^"]+"))@\S+';
  729. $delim = trim($RCMAIL->config->get('recipients_separator', ','));
  730. $regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
  731. $replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');
  732. // replace new lines and strip ending ', ', make address input more valid
  733. $mailto = trim(preg_replace($regexp, $replace, $mailto));
  734. $items = rcube_utils::explode_quoted_string($delim, $mailto);
  735. $result = array();
  736. foreach ($items as $item) {
  737. $item = trim($item);
  738. // address in brackets without name (do nothing)
  739. if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
  740. $item = rcube_utils::idn_to_ascii(trim($item, '<>'));
  741. $result[] = $item;
  742. }
  743. // address without brackets and without name (add brackets)
  744. else if (preg_match('/^'.$email_regexp.'$/', $item)) {
  745. $item = rcube_utils::idn_to_ascii($item);
  746. $result[] = $item;
  747. }
  748. // address with name (handle name)
  749. else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {
  750. $address = $matches[0];
  751. $name = trim(str_replace($address, '', $item));
  752. if ($name[0] == '"' && $name[count($name)-1] == '"') {
  753. $name = substr($name, 1, -1);
  754. }
  755. $name = stripcslashes($name);
  756. $address = rcube_utils::idn_to_ascii(trim($address, '<>'));
  757. $result[] = format_email_recipient($address, $name);
  758. $item = $address;
  759. }
  760. else if (trim($item)) {
  761. continue;
  762. }
  763. // check address format
  764. $item = trim($item, '<>');
  765. if ($item && $check && !rcube_utils::check_email($item)) {
  766. $EMAIL_FORMAT_ERROR = $item;
  767. return;
  768. }
  769. }
  770. if ($count) {
  771. $RECIPIENT_COUNT += count($result);
  772. }
  773. return implode(', ', $result);
  774. }
  775. function rcmail_generic_message_footer($isHtml)
  776. {
  777. global $RCMAIL;
  778. if ($isHtml && ($file = $RCMAIL->config->get('generic_message_footer_html'))) {
  779. $html_footer = true;
  780. }
  781. else {
  782. $file = $RCMAIL->config->get('generic_message_footer');
  783. $html_footer = false;
  784. }
  785. if ($file && realpath($file)) {
  786. // sanity check
  787. if (!preg_match('/\.(php|ini|conf)$/', $file) && strpos($file, '/etc/') === false) {
  788. $footer = file_get_contents($file);
  789. if ($isHtml && !$html_footer) {
  790. $t2h = new rcube_text2html($footer, false);
  791. $footer = $t2h->get_html();
  792. }
  793. return $footer;
  794. }
  795. }
  796. return false;
  797. }