/src/applications/metamta/replyhandler/base/PhabricatorMailReplyHandler.php

https://github.com/juneym/phabricator · PHP · 202 lines · 134 code · 39 blank · 29 comment · 10 complexity · 0702930880f5111f5b9b5aeb03f9bef5 MD5 · raw file

  1. <?php
  2. /*
  3. * Copyright 2011 Facebook, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. abstract class PhabricatorMailReplyHandler {
  18. private $mailReceiver;
  19. private $actor;
  20. final public function setMailReceiver($mail_receiver) {
  21. $this->validateMailReceiver($mail_receiver);
  22. $this->mailReceiver = $mail_receiver;
  23. return $this;
  24. }
  25. final public function getMailReceiver() {
  26. return $this->mailReceiver;
  27. }
  28. final public function setActor(PhabricatorUser $actor) {
  29. $this->actor = $actor;
  30. return $this;
  31. }
  32. final public function getActor() {
  33. return $this->actor;
  34. }
  35. abstract public function validateMailReceiver($mail_receiver);
  36. abstract public function getPrivateReplyHandlerEmailAddress(
  37. PhabricatorObjectHandle $handle);
  38. abstract public function getReplyHandlerDomain();
  39. abstract public function getReplyHandlerInstructions();
  40. abstract public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail);
  41. public function supportsPrivateReplies() {
  42. return (bool)$this->getReplyHandlerDomain() &&
  43. !$this->supportsPublicReplies();
  44. }
  45. public function supportsPublicReplies() {
  46. if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
  47. return false;
  48. }
  49. return (bool)$this->getPublicReplyHandlerEmailAddress();
  50. }
  51. final public function supportsReplies() {
  52. return $this->supportsPrivateReplies() ||
  53. $this->supportsPublicReplies();
  54. }
  55. public function getPublicReplyHandlerEmailAddress() {
  56. return null;
  57. }
  58. final public function multiplexMail(
  59. PhabricatorMetaMTAMail $mail_template,
  60. array $to_handles,
  61. array $cc_handles) {
  62. $result = array();
  63. // If private replies are not supported, simply send one email to all
  64. // recipients and CCs. This covers cases where we have no reply handler,
  65. // or we have a public reply handler.
  66. if (!$this->supportsPrivateReplies()) {
  67. $mail = clone $mail_template;
  68. $mail->addTos(mpull($to_handles, 'getPHID'));
  69. $mail->addCCs(mpull($cc_handles, 'getPHID'));
  70. if ($this->supportsPublicReplies()) {
  71. $reply_to = $this->getPublicReplyHandlerEmailAddress();
  72. $mail->setReplyTo($reply_to);
  73. }
  74. $result[] = $mail;
  75. return $result;
  76. }
  77. // Merge all the recipients together. TODO: We could keep the CCs as real
  78. // CCs and send to a "noreply@domain.com" type address, but keep it simple
  79. // for now.
  80. $recipients = mpull($to_handles, null, 'getPHID') +
  81. mpull($cc_handles, null, 'getPHID');
  82. // This grouping is just so we can use the public reply-to for any
  83. // recipients without a private reply-to, e.g. mailing lists.
  84. $groups = array();
  85. foreach ($recipients as $recipient) {
  86. $private = $this->getPrivateReplyHandlerEmailAddress($recipient);
  87. $groups[$private][] = $recipient;
  88. }
  89. // When multiplexing mail, explicitly include To/Cc information in the
  90. // message body and headers.
  91. $add_headers = array();
  92. $body = $mail_template->getBody();
  93. $body .= "\n";
  94. if ($to_handles) {
  95. $body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
  96. $add_headers['X-Phabricator-To'] = $this->formatPHIDList($to_handles);
  97. }
  98. if ($cc_handles) {
  99. $body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
  100. $add_headers['X-Phabricator-Cc'] = $this->formatPHIDList($cc_handles);
  101. }
  102. foreach ($groups as $reply_to => $group) {
  103. $mail = clone $mail_template;
  104. $mail->addTos(mpull($group, 'getPHID'));
  105. $mail->setBody($body);
  106. foreach ($add_headers as $header => $value) {
  107. $mail->addHeader($header, $value);
  108. }
  109. if (!$reply_to && $this->supportsPublicReplies()) {
  110. $reply_to = $this->getPublicReplyHandlerEmailAddress();
  111. }
  112. if ($reply_to) {
  113. $mail->setReplyTo($reply_to);
  114. }
  115. $result[] = $mail;
  116. }
  117. return $result;
  118. }
  119. protected function formatPHIDList(array $handles) {
  120. $list = array();
  121. foreach ($handles as $handle) {
  122. $list[] = '<'.$handle->getPHID().'>';
  123. }
  124. return implode(', ', $list);
  125. }
  126. protected function getDefaultPublicReplyHandlerEmailAddress($prefix) {
  127. $receiver = $this->getMailReceiver();
  128. $receiver_id = $receiver->getID();
  129. $domain = $this->getReplyHandlerDomain();
  130. // We compute a hash using the object's own PHID to prevent an attacker
  131. // from blindly interacting with objects that they haven't ever received
  132. // mail about by just sending to D1@, D2@, etc...
  133. $hash = PhabricatorMetaMTAReceivedMail::computeMailHash(
  134. $receiver->getMailKey(),
  135. $receiver->getPHID());
  136. $address = "{$prefix}{$receiver_id}+public+{$hash}@{$domain}";
  137. return $this->getSingleReplyHandlerPrefix($address);
  138. }
  139. protected function getSingleReplyHandlerPrefix($address) {
  140. $single_handle_prefix = PhabricatorEnv::getEnvConfig(
  141. 'metamta.single-reply-handler-prefix');
  142. return ($single_handle_prefix)
  143. ? $single_handle_prefix . '+' . $address
  144. : $address;
  145. }
  146. protected function getDefaultPrivateReplyHandlerEmailAddress(
  147. PhabricatorObjectHandle $handle,
  148. $prefix) {
  149. if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
  150. // You must be a real user to get a private reply handler address.
  151. return null;
  152. }
  153. $receiver = $this->getMailReceiver();
  154. $receiver_id = $receiver->getID();
  155. $user_id = $handle->getAlternateID();
  156. $hash = PhabricatorMetaMTAReceivedMail::computeMailHash(
  157. $receiver->getMailKey(),
  158. $handle->getPHID());
  159. $domain = $this->getReplyHandlerDomain();
  160. $address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}";
  161. return $this->getSingleReplyHandlerPrefix($address);
  162. }
  163. }