diff --git a/src/parser/PhutilEmailAddress.php b/src/parser/PhutilEmailAddress.php index b9a3a44..6acda75 100644 --- a/src/parser/PhutilEmailAddress.php +++ b/src/parser/PhutilEmailAddress.php @@ -1,86 +1,114 @@ $/', $email_address, $matches)) { $display_name = trim($matches[1], '\'" '); if (strpos($matches[2], '@') !== false) { list($local_part, $domain_name) = explode('@', $matches[2], 2); } else { $local_part = $matches[2]; $domain_name = null; } } else if (preg_match('/^(.*)@(.*)$/', $email_address, $matches)) { $display_name = null; $local_part = $matches[1]; $domain_name = $matches[2]; } else { $display_name = null; $local_part = $email_address; $domain_name = null; } $this->displayName = $display_name; $this->localPart = $local_part; $this->domainName = $domain_name; } public function __toString() { $address = $this->getAddress(); - if ($this->displayName) { - return $this->displayName.' <'.$address.'>'; + if (strlen($this->displayName)) { + $display_name = $this->encodeDisplayName($this->displayName); + return $display_name.' <'.$address.'>'; } else { return $address; } } public function setDisplayName($display_name) { $this->displayName = $display_name; return $this; } public function getDisplayName() { return $this->displayName; } public function setLocalPart($local_part) { $this->localPart = $local_part; return $this; } public function getLocalPart() { return $this->localPart; } public function setDomainName($domain_name) { $this->domainName = $domain_name; return $this; } public function getDomainName() { return $this->domainName; } + public function setAddress($address) { + $parts = explode('@', $address, 2); + + $this->localPart = $parts[0]; + if (isset($parts[1])) { + $this->domainName = $parts[1]; + } + + return $this; + } + public function getAddress() { $address = $this->localPart; - if ($this->domainName) { + if (strlen($this->domainName)) { $address .= '@'.$this->domainName; } return $address; } + private function encodeDisplayName($name) { + // NOTE: This is a reasonable effort based on a cursory reading of + // RFC2822, but may be significantly misguided. + + // Newlines are not permitted, even when escaped. Discard them. + $name = preg_replace("/\s*[\r\n]+\s*/", ' ', $name); + + // Escape double quotes and backslashes. + $name = addcslashes($name, '\\"'); + + // Quote the string. + $name = '"'.$name.'"'; + + return $name; + } + } diff --git a/src/parser/__tests__/PhutilEmailAddressTestCase.php b/src/parser/__tests__/PhutilEmailAddressTestCase.php index b052098..f2c4158 100644 --- a/src/parser/__tests__/PhutilEmailAddressTestCase.php +++ b/src/parser/__tests__/PhutilEmailAddressTestCase.php @@ -1,94 +1,130 @@ '); $this->assertEqual( 'Abraham Lincoln', $email->getDisplayName()); $this->assertEqual( 'alincoln', $email->getLocalPart()); $this->assertEqual( 'logcabin.com', $email->getDomainName()); $this->assertEqual( 'alincoln@logcabin.com', $email->getAddress()); $email = new PhutilEmailAddress('alincoln@logcabin.com'); $this->assertEqual( null, $email->getDisplayName()); $this->assertEqual( 'alincoln', $email->getLocalPart()); $this->assertEqual( 'logcabin.com', $email->getDomainName()); $this->assertEqual( 'alincoln@logcabin.com', $email->getAddress()); $email = new PhutilEmailAddress('"Abraham" '); $this->assertEqual( 'Abraham', $email->getDisplayName()); $this->assertEqual( 'alincoln', $email->getLocalPart()); $this->assertEqual( 'logcabin.com', $email->getDomainName()); $this->assertEqual( 'alincoln@logcabin.com', $email->getAddress()); $email = new PhutilEmailAddress(' alincoln@logcabin.com '); $this->assertEqual( null, $email->getDisplayName()); $this->assertEqual( 'alincoln', $email->getLocalPart()); $this->assertEqual( 'logcabin.com', $email->getDomainName()); $this->assertEqual( 'alincoln@logcabin.com', $email->getAddress()); $email = new PhutilEmailAddress('alincoln'); $this->assertEqual( null, $email->getDisplayName()); $this->assertEqual( 'alincoln', $email->getLocalPart()); $this->assertEqual( null, $email->getDomainName()); $this->assertEqual( 'alincoln', $email->getAddress()); $email = new PhutilEmailAddress('alincoln '); $this->assertEqual( 'alincoln', $email->getDisplayName()); $this->assertEqual( 'alincoln at logcabin dot com', $email->getLocalPart()); $this->assertEqual( null, $email->getDomainName()); $this->assertEqual( 'alincoln at logcabin dot com', $email->getAddress()); } + public function testEmailEncoding() { + $cases = array( + array( + 'Tangerine Q. Hawthorne', + 'thawthorne@blackspire.bunker', + '"Tangerine Q. Hawthorne" ', + ), + array( + 'Hector "\\" Backslash', + 'hector@backslash', + '"Hector \\"\\\\\\" Backslash" ', + ), + array( + 'My Middle Name "" Is My Email', + 'name@domain', + '"My Middle Name \\"\\" Is My Email" ', + ), + array( + "My Legal Name\nContains A Newline", + 'newline@example', + '"My Legal Name Contains A Newline" ', + ), + ); + + foreach ($cases as $case) { + list($name, $address, $expect) = $case; + $actual = (string)id(new PhutilEmailAddress()) + ->setDisplayName($name) + ->setAddress($address); + $this->assertEqual( + $expect, + $actual, + pht('Email: %s + %s -> %s', $name, $address, $expect)); + } + } + }