Path: blob/master/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
12256 views
<?php12abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver {34/**5* Return a regular expression fragment which matches the name of an6* object which can receive mail. For example, Differential uses:7*8* D[1-9]\d*9*10* ...to match `D123`, etc., identifying Differential Revisions.11*12* @return string Regular expression fragment.13*/14abstract protected function getObjectPattern();151617/**18* Load the object receiving mail, based on an identifying pattern. Normally19* this pattern is some sort of object ID.20*21* @param string A string matched by @{method:getObjectPattern}22* fragment.23* @param PhabricatorUser The viewing user.24* @return void25*/26abstract protected function loadObject($pattern, PhabricatorUser $viewer);272829final protected function processReceivedMail(30PhabricatorMetaMTAReceivedMail $mail,31PhutilEmailAddress $target) {3233$parts = $this->matchObjectAddress($target);34if (!$parts) {35// We should only make it here if we matched already in "canAcceptMail()",36// so this is a surprise.37throw new Exception(38pht(39'Failed to parse object address ("%s") during processing.',40(string)$target));41}4243$pattern = $parts['pattern'];44$sender = $this->getSender();4546try {47$object = $this->loadObject($pattern, $sender);48} catch (PhabricatorPolicyException $policy_exception) {49throw new PhabricatorMetaMTAReceivedMailProcessingException(50MetaMTAReceivedMailStatus::STATUS_POLICY_PROBLEM,51pht(52'This mail is addressed to an object ("%s") you do not have '.53'permission to see: %s',54$pattern,55$policy_exception->getMessage()));56}5758if (!$object) {59throw new PhabricatorMetaMTAReceivedMailProcessingException(60MetaMTAReceivedMailStatus::STATUS_NO_SUCH_OBJECT,61pht(62'This mail is addressed to an object ("%s"), but that object '.63'does not exist.',64$pattern));65}6667$sender_identifier = $parts['sender'];68if ($sender_identifier === 'public') {69if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {70throw new PhabricatorMetaMTAReceivedMailProcessingException(71MetaMTAReceivedMailStatus::STATUS_NO_PUBLIC_MAIL,72pht(73'This mail is addressed to the public email address of an object '.74'("%s"), but public replies are not enabled on this server. An '.75'administrator may have recently disabled this setting, or you '.76'may have replied to an old message. Try replying to a more '.77'recent message instead.',78$pattern));79}80$check_phid = $object->getPHID();81} else {82if ($sender_identifier != $sender->getID()) {83throw new PhabricatorMetaMTAReceivedMailProcessingException(84MetaMTAReceivedMailStatus::STATUS_USER_MISMATCH,85pht(86'This mail is addressed to the private email address of an object '.87'("%s"), but you are not the user who is authorized to use the '.88'address you sent mail to. Each private address is unique to the '.89'user who received the original mail. Try replying to a message '.90'which was sent directly to you instead.',91$pattern));92}93$check_phid = $sender->getPHID();94}9596$mail_key = PhabricatorMetaMTAMailProperties::loadMailKey($object);97$expect_hash = self::computeMailHash($mail_key, $check_phid);9899if (!phutil_hashes_are_identical($expect_hash, $parts['hash'])) {100throw new PhabricatorMetaMTAReceivedMailProcessingException(101MetaMTAReceivedMailStatus::STATUS_HASH_MISMATCH,102pht(103'This mail is addressed to an object ("%s"), but the address is '.104'not correct (the security hash is wrong). Check that the address '.105'is correct.',106$pattern));107}108109$mail->setRelatedPHID($object->getPHID());110$this->processReceivedObjectMail($mail, $object, $sender);111112return $this;113}114115protected function processReceivedObjectMail(116PhabricatorMetaMTAReceivedMail $mail,117PhabricatorLiskDAO $object,118PhabricatorUser $sender) {119120$handler = $this->getTransactionReplyHandler();121if ($handler) {122return $handler123->setMailReceiver($object)124->setActor($sender)125->setExcludeMailRecipientPHIDs($mail->loadAllRecipientPHIDs())126->processEmail($mail);127}128129throw new PhutilMethodNotImplementedException();130}131132protected function getTransactionReplyHandler() {133return null;134}135136public function loadMailReceiverObject($pattern, PhabricatorUser $viewer) {137return $this->loadObject($pattern, $viewer);138}139140final public function canAcceptMail(141PhabricatorMetaMTAReceivedMail $mail,142PhutilEmailAddress $target) {143144// If we don't have a valid sender user account, we can never accept145// mail to any object.146$sender = $this->getSender();147if (!$sender) {148return false;149}150151return (bool)$this->matchObjectAddress($target);152}153154private function matchObjectAddress(PhutilEmailAddress $address) {155$address = PhabricatorMailUtil::normalizeAddress($address);156$local = $address->getLocalPart();157158$regexp = $this->getAddressRegexp();159$matches = null;160if (!preg_match($regexp, $local, $matches)) {161return false;162}163164return $matches;165}166167private function getAddressRegexp() {168$pattern = $this->getObjectPattern();169170$regexp =171'(^'.172'(?P<pattern>'.$pattern.')'.173'\\+'.174'(?P<sender>\w+)'.175'\\+'.176'(?P<hash>[a-f0-9]{16})'.177'$)Ui';178179return $regexp;180}181182public static function computeMailHash($mail_key, $phid) {183$hash = PhabricatorHash::digestWithNamedKey(184$mail_key.$phid,185'mail.object-address-key');186return substr($hash, 0, 16);187}188189}190191192