Extract some email address utility code from the receiver stack
Summary: Ref T7477. We have some address normalization code in the reciever stack that is really shared code. I want to introduce some new callsites elsewhere but don't want to put a lot of static calls to other random objects all over the place. This technically "solves" T7477 (it changes "to" to "to + cc" for finding receivers) but doesn't yet implement proper behavior (with multiple receivers, for example). Test Plan: Ran unit tests, which cover this pretty well. Additional changes will vet this more thoroughly. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T7477 Differential Revision: https://secure.phabricator.com/D19948
This commit is contained in:
		@@ -3408,6 +3408,7 @@ phutil_register_library_map(array(
 | 
			
		||||
    'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php',
 | 
			
		||||
    'PhabricatorMailStamp' => 'applications/metamta/stamp/PhabricatorMailStamp.php',
 | 
			
		||||
    'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php',
 | 
			
		||||
    'PhabricatorMailUtil' => 'applications/metamta/util/PhabricatorMailUtil.php',
 | 
			
		||||
    'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php',
 | 
			
		||||
    'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php',
 | 
			
		||||
    'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php',
 | 
			
		||||
@@ -9215,6 +9216,7 @@ phutil_register_library_map(array(
 | 
			
		||||
    'PhabricatorMailRoutingRule' => 'Phobject',
 | 
			
		||||
    'PhabricatorMailStamp' => 'Phobject',
 | 
			
		||||
    'PhabricatorMailTarget' => 'Phobject',
 | 
			
		||||
    'PhabricatorMailUtil' => 'Phobject',
 | 
			
		||||
    'PhabricatorMainMenuBarExtension' => 'Phobject',
 | 
			
		||||
    'PhabricatorMainMenuSearchView' => 'AphrontView',
 | 
			
		||||
    'PhabricatorMainMenuView' => 'AphrontView',
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,10 @@ abstract class PhabricatorMailReceiver extends Phobject {
 | 
			
		||||
      ->withApplicationPHIDs(array($app->getPHID()))
 | 
			
		||||
      ->execute();
 | 
			
		||||
 | 
			
		||||
    foreach ($mail->getToAddresses() as $to_address) {
 | 
			
		||||
    foreach ($mail->newTargetAddresses() as $address) {
 | 
			
		||||
      foreach ($application_emails as $application_email) {
 | 
			
		||||
        $create_address = $application_email->getAddress();
 | 
			
		||||
        if ($this->matchAddresses($create_address, $to_address)) {
 | 
			
		||||
        $create_address = $application_email->newAddress();
 | 
			
		||||
        if (PhabricatorMailUtil::matchAddresses($create_address, $address)) {
 | 
			
		||||
          $this->setApplicationEmail($application_email);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
@@ -194,66 +194,6 @@ abstract class PhabricatorMailReceiver extends Phobject {
 | 
			
		||||
      $reasons);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determine if two inbound email addresses are effectively identical. This
 | 
			
		||||
   * method strips and normalizes addresses so that equivalent variations are
 | 
			
		||||
   * correctly detected as identical. For example, these addresses are all
 | 
			
		||||
   * considered to match one another:
 | 
			
		||||
   *
 | 
			
		||||
   *   "Abraham Lincoln" <alincoln@example.com>
 | 
			
		||||
   *   alincoln@example.com
 | 
			
		||||
   *   <ALincoln@example.com>
 | 
			
		||||
   *   "Abraham" <phabricator+ALINCOLN@EXAMPLE.COM> # With configured prefix.
 | 
			
		||||
   *
 | 
			
		||||
   * @param   string  Email address.
 | 
			
		||||
   * @param   string  Another email address.
 | 
			
		||||
   * @return  bool    True if addresses match.
 | 
			
		||||
   */
 | 
			
		||||
  public static function matchAddresses($u, $v) {
 | 
			
		||||
    $u = self::getRawAddress($u);
 | 
			
		||||
    $v = self::getRawAddress($v);
 | 
			
		||||
 | 
			
		||||
    $u = self::stripMailboxPrefix($u);
 | 
			
		||||
    $v = self::stripMailboxPrefix($v);
 | 
			
		||||
 | 
			
		||||
    return ($u === $v);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Strip a global mailbox prefix from an address if it is present. Phabricator
 | 
			
		||||
   * can be configured to prepend a prefix to all reply addresses, which can
 | 
			
		||||
   * make forwarding rules easier to write. A prefix looks like:
 | 
			
		||||
   *
 | 
			
		||||
   *  example@phabricator.example.com              # No Prefix
 | 
			
		||||
   *  phabricator+example@phabricator.example.com  # Prefix "phabricator"
 | 
			
		||||
   *
 | 
			
		||||
   * @param   string  Email address, possibly with a mailbox prefix.
 | 
			
		||||
   * @return  string  Email address with any prefix stripped.
 | 
			
		||||
   */
 | 
			
		||||
  public static function stripMailboxPrefix($address) {
 | 
			
		||||
    $address = id(new PhutilEmailAddress($address))->getAddress();
 | 
			
		||||
 | 
			
		||||
    $prefix_key = 'metamta.single-reply-handler-prefix';
 | 
			
		||||
    $prefix = PhabricatorEnv::getEnvConfig($prefix_key);
 | 
			
		||||
 | 
			
		||||
    $len = strlen($prefix);
 | 
			
		||||
 | 
			
		||||
    if ($len) {
 | 
			
		||||
      $prefix = $prefix.'+';
 | 
			
		||||
      $len = $len + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($len) {
 | 
			
		||||
      if (!strncasecmp($address, $prefix, $len)) {
 | 
			
		||||
        $address = substr($address, strlen($prefix));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $address;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reduce an email address to its canonical form. For example, an address
 | 
			
		||||
   * like:
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,7 @@ abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver {
 | 
			
		||||
  private function matchObjectAddressInMail(
 | 
			
		||||
    PhabricatorMetaMTAReceivedMail $mail) {
 | 
			
		||||
 | 
			
		||||
    foreach ($mail->getToAddresses() as $address) {
 | 
			
		||||
    foreach ($mail->newTargetAddresses() as $address) {
 | 
			
		||||
      $parts = $this->matchObjectAddress($address);
 | 
			
		||||
      if ($parts) {
 | 
			
		||||
        return $parts;
 | 
			
		||||
@@ -160,12 +160,11 @@ abstract class PhabricatorObjectMailReceiver extends PhabricatorMailReceiver {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function matchObjectAddress($address) {
 | 
			
		||||
  private function matchObjectAddress(PhutilEmailAddress $address) {
 | 
			
		||||
    $address = PhabricatorMailUtil::normalizeAddress($address);
 | 
			
		||||
    $local = $address->getLocalPart();
 | 
			
		||||
 | 
			
		||||
    $regexp = $this->getAddressRegexp();
 | 
			
		||||
 | 
			
		||||
    $address = self::stripMailboxPrefix($address);
 | 
			
		||||
    $local = id(new PhutilEmailAddress($address))->getLocalPart();
 | 
			
		||||
 | 
			
		||||
    $matches = null;
 | 
			
		||||
    if (!preg_match($regexp, $local, $matches)) {
 | 
			
		||||
      return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ final class PhabricatorMailReceiverTestCase extends PhabricatorTestCase {
 | 
			
		||||
 | 
			
		||||
    foreach ($same as $address) {
 | 
			
		||||
      $this->assertTrue(
 | 
			
		||||
        PhabricatorMailReceiver::matchAddresses($base, $address),
 | 
			
		||||
        PhabricatorMailUtil::matchAddresses(
 | 
			
		||||
          new PhutilEmailAddress($base),
 | 
			
		||||
          new PhutilEmailAddress($address)),
 | 
			
		||||
        pht('Address %s', $address));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +34,9 @@ final class PhabricatorMailReceiverTestCase extends PhabricatorTestCase {
 | 
			
		||||
 | 
			
		||||
    foreach ($diff as $address) {
 | 
			
		||||
      $this->assertFalse(
 | 
			
		||||
        PhabricatorMailReceiver::matchAddresses($base, $address),
 | 
			
		||||
        PhabricatorMailUtil::matchAddresses(
 | 
			
		||||
          new PhutilEmailAddress($base),
 | 
			
		||||
          new PhutilEmailAddress($address)),
 | 
			
		||||
        pht('Address: %s', $address));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,10 @@ final class PhabricatorMetaMTAApplicationEmail
 | 
			
		||||
    return $message;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function newAddress() {
 | 
			
		||||
    return new PhutilEmailAddress($this->getAddress());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,27 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
 | 
			
		||||
    return $this->getRawEmailAddresses(idx($this->headers, 'to'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function newTargetAddresses() {
 | 
			
		||||
    $raw_addresses = array();
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getToAddresses() as $raw_address) {
 | 
			
		||||
      $raw_addresses[] = $raw_address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($this->getCCAddresses() as $raw_address) {
 | 
			
		||||
      $raw_addresses[] = $raw_address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $raw_addresses = array_unique($raw_addresses);
 | 
			
		||||
 | 
			
		||||
    $addresses = array();
 | 
			
		||||
    foreach ($raw_addresses as $raw_address) {
 | 
			
		||||
      $addresses[] = new PhutilEmailAddress($raw_address);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $addresses;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function loadAllRecipientPHIDs() {
 | 
			
		||||
    $addresses = array_merge(
 | 
			
		||||
      $this->getToAddresses(),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								src/applications/metamta/util/PhabricatorMailUtil.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/applications/metamta/util/PhabricatorMailUtil.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
final class PhabricatorMailUtil
 | 
			
		||||
  extends Phobject {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Normalize an email address for comparison or lookup.
 | 
			
		||||
   *
 | 
			
		||||
   * Phabricator can be configured to prepend a prefix to all reply addresses,
 | 
			
		||||
   * which can make forwarding rules easier to write. This method strips the
 | 
			
		||||
   * prefix if it is present, and normalizes casing and whitespace.
 | 
			
		||||
   *
 | 
			
		||||
   * @param PhutilEmailAddress Email address.
 | 
			
		||||
   * @return PhutilEmailAddress Normalized address.
 | 
			
		||||
   */
 | 
			
		||||
  public static function normalizeAddress(PhutilEmailAddress $address) {
 | 
			
		||||
    $raw_address = $address->getAddress();
 | 
			
		||||
    $raw_address = phutil_utf8_strtolower($raw_address);
 | 
			
		||||
    $raw_address = trim($raw_address);
 | 
			
		||||
 | 
			
		||||
    // If a mailbox prefix is configured and present, strip it off.
 | 
			
		||||
    $prefix_key = 'metamta.single-reply-handler-prefix';
 | 
			
		||||
    $prefix = PhabricatorEnv::getEnvConfig($prefix_key);
 | 
			
		||||
    $len = strlen($prefix);
 | 
			
		||||
 | 
			
		||||
    if ($len) {
 | 
			
		||||
      $prefix = $prefix.'+';
 | 
			
		||||
      $len = $len + 1;
 | 
			
		||||
 | 
			
		||||
      if (!strncasecmp($raw_address, $prefix, $len)) {
 | 
			
		||||
        $raw_address = substr($raw_address, $len);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return id(clone $address)
 | 
			
		||||
      ->setAddress($raw_address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determine if two inbound email addresses are effectively identical.
 | 
			
		||||
   *
 | 
			
		||||
   * This method strips and normalizes addresses so that equivalent variations
 | 
			
		||||
   * are correctly detected as identical. For example, these addresses are all
 | 
			
		||||
   * considered to match one another:
 | 
			
		||||
   *
 | 
			
		||||
   *   "Abraham Lincoln" <alincoln@example.com>
 | 
			
		||||
   *   alincoln@example.com
 | 
			
		||||
   *   <ALincoln@example.com>
 | 
			
		||||
   *   "Abraham" <phabricator+ALINCOLN@EXAMPLE.COM> # With configured prefix.
 | 
			
		||||
   *
 | 
			
		||||
   * @param   PhutilEmailAddress Email address.
 | 
			
		||||
   * @param   PhutilEmailAddress Another email address.
 | 
			
		||||
   * @return  bool True if addresses are effectively the same address.
 | 
			
		||||
   */
 | 
			
		||||
  public static function matchAddresses(
 | 
			
		||||
    PhutilEmailAddress $u,
 | 
			
		||||
    PhutilEmailAddress $v) {
 | 
			
		||||
 | 
			
		||||
    $u = self::normalizeAddress($u);
 | 
			
		||||
    $v = self::normalizeAddress($v);
 | 
			
		||||
 | 
			
		||||
    return ($u->getAddress() === $v->getAddress());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user