Changeset View
Changeset View
Standalone View
Standalone View
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
Show First 20 Lines • Show All 430 Lines • ▼ Show 20 Lines | public function sendNow( | ||||
if (!$force_send) { | if (!$force_send) { | ||||
if ($this->getStatus() != self::STATUS_QUEUE) { | if ($this->getStatus() != self::STATUS_QUEUE) { | ||||
throw new Exception(pht('Trying to send an already-sent mail!')); | throw new Exception(pht('Trying to send an already-sent mail!')); | ||||
} | } | ||||
} | } | ||||
try { | try { | ||||
$headers = $this->generateHeaders(); | |||||
$params = $this->parameters; | $params = $this->parameters; | ||||
$actors = $this->loadAllActors(); | $actors = $this->loadAllActors(); | ||||
$deliverable_actors = $this->filterDeliverableActors($actors); | $deliverable_actors = $this->filterDeliverableActors($actors); | ||||
$default_from = PhabricatorEnv::getEnvConfig('metamta.default-address'); | $default_from = PhabricatorEnv::getEnvConfig('metamta.default-address'); | ||||
if (empty($params['from'])) { | if (empty($params['from'])) { | ||||
$mailer->setFrom($default_from); | $mailer->setFrom($default_from); | ||||
▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | try { | ||||
break; | break; | ||||
case 'cc': | case 'cc': | ||||
$cc_phids = $this->expandRecipients($value); | $cc_phids = $this->expandRecipients($value); | ||||
$cc_actors = array_select_keys($deliverable_actors, $cc_phids); | $cc_actors = array_select_keys($deliverable_actors, $cc_phids); | ||||
$add_cc = array_merge( | $add_cc = array_merge( | ||||
$add_cc, | $add_cc, | ||||
mpull($cc_actors, 'getEmailAddress')); | mpull($cc_actors, 'getEmailAddress')); | ||||
break; | break; | ||||
case 'headers': | |||||
foreach ($value as $pair) { | |||||
list($header_key, $header_value) = $pair; | |||||
// NOTE: If we have \n in a header, SES rejects the email. | |||||
$header_value = str_replace("\n", ' ', $header_value); | |||||
$mailer->addHeader($header_key, $header_value); | |||||
} | |||||
break; | |||||
case 'attachments': | case 'attachments': | ||||
$value = $this->getAttachments(); | $value = $this->getAttachments(); | ||||
foreach ($value as $attachment) { | foreach ($value as $attachment) { | ||||
$mailer->addAttachment( | $mailer->addAttachment( | ||||
$attachment->getData(), | $attachment->getData(), | ||||
$attachment->getFilename(), | $attachment->getFilename(), | ||||
$attachment->getMimeType()); | $attachment->getMimeType()); | ||||
} | } | ||||
Show All 32 Lines | try { | ||||
$subject[] = $vary_prefix; | $subject[] = $vary_prefix; | ||||
} | } | ||||
} | } | ||||
$subject[] = $value; | $subject[] = $value; | ||||
$mailer->setSubject(implode(' ', array_filter($subject))); | $mailer->setSubject(implode(' ', array_filter($subject))); | ||||
break; | break; | ||||
case 'is-bulk': | |||||
if ($value) { | |||||
$mailer->addHeader('Precedence', 'bulk'); | |||||
} | |||||
break; | |||||
case 'thread-id': | case 'thread-id': | ||||
// NOTE: Gmail freaks out about In-Reply-To and References which | // NOTE: Gmail freaks out about In-Reply-To and References which | ||||
// aren't in the form "<string@domain.tld>"; this is also required | // aren't in the form "<string@domain.tld>"; this is also required | ||||
// by RFC 2822, although some clients are more liberal in what they | // by RFC 2822, although some clients are more liberal in what they | ||||
// accept. | // accept. | ||||
$domain = PhabricatorEnv::getEnvConfig('metamta.domain'); | $domain = PhabricatorEnv::getEnvConfig('metamta.domain'); | ||||
$value = '<'.$value.'@'.$domain.'>'; | $value = '<'.$value.'@'.$domain.'>'; | ||||
if ($is_first && $mailer->supportsMessageIDHeader()) { | if ($is_first && $mailer->supportsMessageIDHeader()) { | ||||
$mailer->addHeader('Message-ID', $value); | $headers[] = array('Message-ID', $value); | ||||
} else { | } else { | ||||
$in_reply_to = $value; | $in_reply_to = $value; | ||||
$references = array($value); | $references = array($value); | ||||
$parent_id = $this->getParentMessageID(); | $parent_id = $this->getParentMessageID(); | ||||
if ($parent_id) { | if ($parent_id) { | ||||
$in_reply_to = $parent_id; | $in_reply_to = $parent_id; | ||||
// By RFC 2822, the most immediate parent should appear last | // By RFC 2822, the most immediate parent should appear last | ||||
// in the "References" header, so this order is intentional. | // in the "References" header, so this order is intentional. | ||||
$references[] = $parent_id; | $references[] = $parent_id; | ||||
} | } | ||||
$references = implode(' ', $references); | $references = implode(' ', $references); | ||||
$mailer->addHeader('In-Reply-To', $in_reply_to); | $headers[] = array('In-Reply-To', $in_reply_to); | ||||
$mailer->addHeader('References', $references); | $headers[] = array('References', $references); | ||||
} | } | ||||
$thread_index = $this->generateThreadIndex($value, $is_first); | $thread_index = $this->generateThreadIndex($value, $is_first); | ||||
$mailer->addHeader('Thread-Index', $thread_index); | $headers[] = array('Thread-Index', $thread_index); | ||||
break; | |||||
case 'mailtags': | |||||
// Handled below. | |||||
break; | |||||
case 'subject-prefix': | |||||
case 'vary-subject-prefix': | |||||
// Handled above. | |||||
break; | break; | ||||
default: | default: | ||||
// Just discard. | // Other parameters are handled elsewhere or are not relevant to | ||||
// constructing the message. | |||||
break; | |||||
} | } | ||||
} | } | ||||
$body = idx($params, 'body', ''); | $body = idx($params, 'body', ''); | ||||
$max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit'); | $max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit'); | ||||
if (strlen($body) > $max) { | if (strlen($body) > $max) { | ||||
$body = id(new PhutilUTF8StringTruncator()) | $body = id(new PhutilUTF8StringTruncator()) | ||||
->setMaximumBytes($max) | ->setMaximumBytes($max) | ||||
Show All 9 Lines | try { | ||||
PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS, | PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS, | ||||
$html_emails); | $html_emails); | ||||
} | } | ||||
if ($html_emails && isset($params['html-body'])) { | if ($html_emails && isset($params['html-body'])) { | ||||
$mailer->setHTMLBody($params['html-body']); | $mailer->setHTMLBody($params['html-body']); | ||||
} | } | ||||
// Pass the headers to the mailer, then save the state so we can show | |||||
// them in the web UI. | |||||
foreach ($headers as $header) { | |||||
list($header_key, $header_value) = $header; | |||||
$mailer->addHeader($header_key, $header_value); | |||||
} | |||||
$this->setParam('headers.sent', $headers); | |||||
// Save the final deliverability outcomes and reasoning so we can | |||||
// explain why things happened the way they did. | |||||
$actor_list = array(); | |||||
foreach ($actors as $actor) { | |||||
$actor_list[$actor->getPHID()] = array( | |||||
'deliverable' => $actor->isDeliverable(), | |||||
'reasons' => $actor->getDeliverabilityReasons(), | |||||
); | |||||
} | |||||
$this->setParam('actors.sent', $actor_list); | |||||
if (!$add_to && !$add_cc) { | if (!$add_to && !$add_cc) { | ||||
$this->setStatus(self::STATUS_VOID); | $this->setStatus(self::STATUS_VOID); | ||||
$this->setMessage( | $this->setMessage( | ||||
pht( | pht( | ||||
'Message has no valid recipients: all To/Cc are disabled, '. | 'Message has no valid recipients: all To/Cc are disabled, '. | ||||
'invalid, or configured not to receive this mail.')); | 'invalid, or configured not to receive this mail.')); | ||||
return $this->save(); | return $this->save(); | ||||
} | } | ||||
Show All 16 Lines | try { | ||||
$this->setMessage( | $this->setMessage( | ||||
pht( | pht( | ||||
'Phabricator is running in silent mode. See `%s` '. | 'Phabricator is running in silent mode. See `%s` '. | ||||
'in the configuration to change this setting.', | 'in the configuration to change this setting.', | ||||
'phabricator.silent')); | 'phabricator.silent')); | ||||
return $this->save(); | return $this->save(); | ||||
} | } | ||||
$mailer->addHeader('X-Phabricator-Sent-This-Message', 'Yes'); | |||||
$mailer->addHeader('X-Mail-Transport-Agent', 'MetaMTA'); | |||||
// Some clients respect this to suppress OOF and other auto-responses. | |||||
$mailer->addHeader('X-Auto-Response-Suppress', 'All'); | |||||
// If the message has mailtags, filter out any recipients who don't want | |||||
// to receive this type of mail. | |||||
$mailtags = $this->getParam('mailtags'); | |||||
if ($mailtags) { | |||||
$tag_header = array(); | |||||
foreach ($mailtags as $mailtag) { | |||||
$tag_header[] = '<'.$mailtag.'>'; | |||||
} | |||||
$tag_header = implode(', ', $tag_header); | |||||
$mailer->addHeader('X-Phabricator-Mail-Tags', $tag_header); | |||||
} | |||||
// Some mailers require a valid "To:" in order to deliver mail. If we | // Some mailers require a valid "To:" in order to deliver mail. If we | ||||
// don't have any "To:", try to fill it in with a placeholder "To:". | // don't have any "To:", try to fill it in with a placeholder "To:". | ||||
// If that also fails, move the "Cc:" line to "To:". | // If that also fails, move the "Cc:" line to "To:". | ||||
if (!$add_to) { | if (!$add_to) { | ||||
$placeholder_key = 'metamta.placeholder-to-recipient'; | $placeholder_key = 'metamta.placeholder-to-recipient'; | ||||
$placeholder = PhabricatorEnv::getEnvConfig($placeholder_key); | $placeholder = PhabricatorEnv::getEnvConfig($placeholder_key); | ||||
if ($placeholder !== null) { | if ($placeholder !== null) { | ||||
$add_to = array($placeholder); | $add_to = array($placeholder); | ||||
▲ Show 20 Lines • Show All 326 Lines • ▼ Show 20 Lines | $this->openTransaction(); | ||||
$this->getPHID(), | $this->getPHID(), | ||||
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); | PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST); | ||||
$ret = parent::delete(); | $ret = parent::delete(); | ||||
$this->saveTransaction(); | $this->saveTransaction(); | ||||
return $ret; | return $ret; | ||||
} | } | ||||
public function generateHeaders() { | |||||
$headers = array(); | |||||
$headers[] = array('X-Phabricator-Sent-This-Message', 'Yes'); | |||||
$headers[] = array('X-Mail-Transport-Agent', 'MetaMTA'); | |||||
// Some clients respect this to suppress OOF and other auto-responses. | |||||
$headers[] = array('X-Auto-Response-Suppress', 'All'); | |||||
// If the message has mailtags, filter out any recipients who don't want | |||||
// to receive this type of mail. | |||||
$mailtags = $this->getParam('mailtags'); | |||||
if ($mailtags) { | |||||
$tag_header = array(); | |||||
foreach ($mailtags as $mailtag) { | |||||
$tag_header[] = '<'.$mailtag.'>'; | |||||
} | |||||
$tag_header = implode(', ', $tag_header); | |||||
$headers[] = array('X-Phabricator-Mail-Tags', $tag_header); | |||||
} | |||||
$value = $this->getParam('headers', array()); | |||||
foreach ($value as $pair) { | |||||
list($header_key, $header_value) = $pair; | |||||
// NOTE: If we have \n in a header, SES rejects the email. | |||||
$header_value = str_replace("\n", ' ', $header_value); | |||||
$headers[] = array($header_key, $header_value); | |||||
} | |||||
$is_bulk = $this->getParam('is-bulk'); | |||||
if ($is_bulk) { | |||||
$headers[] = array('Precedence', 'bulk'); | |||||
} | |||||
return $headers; | |||||
} | |||||
public function getDeliveredHeaders() { | |||||
return $this->getParam('headers.sent'); | |||||
} | |||||
public function getDeliveredActors() { | |||||
return $this->getParam('actors.sent'); | |||||
} | |||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */ | /* -( PhabricatorPolicyInterface )----------------------------------------- */ | ||||
public function getCapabilities() { | public function getCapabilities() { | ||||
return array( | return array( | ||||
PhabricatorPolicyCapability::CAN_VIEW, | PhabricatorPolicyCapability::CAN_VIEW, | ||||
); | ); | ||||
Show All 18 Lines |