mimemail.incoming.inc 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. /**
  3. * @file
  4. * Functions that handle inbound messages to mimemail.
  5. */
  6. /**
  7. * Receive messages POSTed from an external source.
  8. *
  9. * This function enables messages to be sent via POST or some other RFC822
  10. * source input (e.g. directly from a mail server).
  11. *
  12. * @return
  13. * The POSTed message.
  14. */
  15. function mimemail_post() {
  16. $message = $_POST['message'];
  17. $token = $_POST['token'];
  18. $hash = md5(variable_get('mimemail_key', '**') . $message);
  19. if ($hash != $token) {
  20. watchdog('access denied', 'Authentication error for POST e-mail', WATCHDOG_WARNING);
  21. return drupal_access_denied();
  22. }
  23. return mimemail_incoming($message);
  24. }
  25. /**
  26. * Parses an externally received message.
  27. *
  28. * @param $message
  29. * The message to parse.
  30. */
  31. function mimemail_incoming($message) {
  32. $mail = mimemail_parse($message);
  33. foreach (module_implements('mimemail_incoming_alter') as $module) {
  34. call_user_func_array($module . '_mimemail_incoming_alter', $mail);
  35. }
  36. module_invoke_all('mimemail_incoming', $mail);
  37. }
  38. /**
  39. * Parses a message into its parts.
  40. *
  41. * @param string $message
  42. * The message to parse.
  43. *
  44. * @return array
  45. * The parts of the message.
  46. */
  47. function mimemail_parse($message) {
  48. // Provides a "headers", "content-type" and "body" element.
  49. $mail = mimemail_parse_headers($message);
  50. // Get an address-only version of "From" (useful for user_load() and such).
  51. $mail['from'] = preg_replace('/.*\b([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4})\b.*/i', '\1', drupal_strtolower($mail['headers']['From']));
  52. // Get a subject line, which may be cleaned up/modified later.
  53. $mail['subject'] = $mail['headers']['Subject'];
  54. // Make an array to hold any non-content attachments.
  55. $mail['attachments'] = array();
  56. // We're dealing with a multi-part message.
  57. $mail['parts'] = mimemail_parse_boundary($mail);
  58. foreach ($mail['parts'] as $i => $part_body) {
  59. $part = mimemail_parse_headers($part_body);
  60. $sub_parts = mimemail_parse_boundary($part);
  61. // Content is encoded in a multipart/alternative section.
  62. if (count($sub_parts) > 1) {
  63. foreach ($sub_parts as $j => $sub_part_body) {
  64. $sub_part = mimemail_parse_headers($sub_part_body);
  65. if ($sub_part['content-type'] == 'text/plain') {
  66. $mail['text'] = mimemail_parse_content($sub_part);
  67. }
  68. if ($sub_part['content-type'] == 'text/html') {
  69. $mail['html'] = mimemail_parse_content($sub_part);
  70. }
  71. else {
  72. $mail['attachments'][] = mimemail_parse_attachment($sub_part);
  73. }
  74. }
  75. }
  76. if (($part['content-type'] == 'text/plain') && !isset($mail['text'])) {
  77. $mail['text'] = mimemail_parse_content($part);
  78. }
  79. elseif (($part['content-type'] == 'text/html') && !isset($mail['html'])) {
  80. $mail['html'] = mimemail_parse_content($part);
  81. }
  82. else {
  83. $mail['attachments'][] = mimemail_parse_attachment($part);
  84. }
  85. }
  86. // Make sure our text and html parts are accounted for.
  87. if (isset($mail['html']) && !isset($mail['text'])) {
  88. $mail['text'] = preg_replace('|<style.*</style>|mis', '', $mail['html']);
  89. $mail['text'] = drupal_html_to_text($mail['text']);
  90. }
  91. elseif (isset($mail['text']) && !isset($mail['html'])) {
  92. $mail['html'] = check_markup($mail['text'], variable_get('mimemail_format', filter_fallback_format()));
  93. }
  94. // Last ditch attempt - use the body as-is.
  95. if (!isset($mail['text'])) {
  96. $mail['text'] = mimemail_parse_content($mail);
  97. $mail['html'] = check_markup($mail['text'], variable_get('mimemail_format', filter_fallback_format()));
  98. }
  99. return $mail;
  100. }
  101. /**
  102. * Split a multi-part message using MIME boundaries.
  103. */
  104. function mimemail_parse_boundary($part) {
  105. $m = array();
  106. if (preg_match('/.*boundary="?([^";]+)"?.*/', $part['headers']['Content-Type'], $m)) {
  107. $boundary = "\n--" . $m[1];
  108. $body = str_replace("$boundary--", '', $part['body']);
  109. return array_slice(explode($boundary, $body), 1);
  110. }
  111. return array($part['body']);
  112. }
  113. /**
  114. * Split a message (or message part) into its headers and body section.
  115. */
  116. function mimemail_parse_headers($message) {
  117. // Split out body and headers.
  118. if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $message, $match)) {
  119. list($hdr, $body) = array($match[1], $match[2]);
  120. }
  121. // Un-fold the headers.
  122. $hdr = preg_replace(array("/\r/", "/\n(\t| )+/"), array('', ' '), $hdr);
  123. $headers = array();
  124. foreach (explode("\n", trim($hdr)) as $row) {
  125. $split = strpos($row, ':');
  126. $name = trim(drupal_substr($row, 0, $split));
  127. $val = trim(drupal_substr($row, $split+1));
  128. $headers[$name] = $val;
  129. }
  130. $type = (preg_replace('/\s*([^;]+).*/', '\1', $headers['Content-Type']));
  131. return array('headers' => $headers, 'body' => $body, 'content-type' => $type);
  132. }
  133. /**
  134. * Return a decoded MIME part in UTF-8.
  135. */
  136. function mimemail_parse_content($part) {
  137. $content = $part['body'];
  138. // Decode this part.
  139. if ($encoding = drupal_strtolower($part['headers']['Content-Transfer-Encoding'])) {
  140. switch ($encoding) {
  141. case 'base64':
  142. $content = base64_decode($content);
  143. break;
  144. case 'quoted-printable':
  145. $content = quoted_printable_decode($content);
  146. break;
  147. // 7bit is the RFC default.
  148. case '7bit':
  149. break;
  150. }
  151. }
  152. // Try to convert character set to UTF-8.
  153. if (preg_match('/.*charset="?([^";]+)"?.*/', $part['headers']['Content-Type'], $m)) {
  154. $content = drupal_convert_to_utf8($content, $m[1]);
  155. }
  156. return $content;
  157. }
  158. /**
  159. * Convert a MIME part into a file array.
  160. */
  161. function mimemail_parse_attachment($part) {
  162. $m = array();
  163. if (preg_match('/.*filename="?([^";])"?.*/', $part['headers']['Content-Disposition'], $m)) {
  164. $name = $m[1];
  165. }
  166. elseif (preg_match('/.*name="?([^";])"?.*/', $part['headers']['Content-Type'], $m)) {
  167. $name = $m[1];
  168. }
  169. return array(
  170. 'filename' => $name,
  171. 'filemime' => $part['content-type'],
  172. 'content' => mimemail_parse_content($part),
  173. );
  174. }