diff --git a/lib/helpers/mail/Mail.php b/lib/helpers/mail/Mail.php index 5c83e2f14..70a38f942 100644 --- a/lib/helpers/mail/Mail.php +++ b/lib/helpers/mail/Mail.php @@ -19,10 +19,10 @@ class ReplyTo implements \JsonSerializable public function __construct($email, $name = null) { - $this->email = $email; + $this->setEmail($email); if (!is_null($name)) { - $this->name = $name; + $this->setName($name); } } @@ -38,7 +38,35 @@ public function getEmail() public function setName($name) { - $this->name = $name; + /* + Issue #368 + ========== + + If the name is not wrapped in double quotes and contains a comma or + semicolon, the API fails to parse it correctly. + + When wrapped in double quotes, commas, semicolons and unescaped single + quotes are supported. + Escaped double quotes are supported as well but will appear unescaped in + the mail (e.g. "O\'Keefe"). + + Double quotes will be shown in some email clients, so the name should + only be wrapped when necessary. + */ + + // Only wrapp in double quote if comma or semicolon are found + if (false !== strpos($name, ',') || false !== strpos($name, ';')) { + // Unescape quotes + $name = stripslashes(html_entity_decode($name, ENT_QUOTES)); + + // Escape only double quotes + $name = str_replace('"', '\\"', $name); + + // Wrapp in double quotes + $name = '"' . $name . '"'; + } + + $this->name = (!empty($name)) ? $name : null; } public function getName() @@ -897,13 +925,41 @@ class Email implements \JsonSerializable public function __construct($name, $email) { - $this->name = $name; - $this->email = $email; + $this->setName($name); + $this->setEmail($email); } public function setName($name) { - $this->name = $name; + /* + Issue #368 + ========== + + If the name is not wrapped in double quotes and contains a comma or + semicolon, the API fails to parse it correctly. + + When wrapped in double quotes, commas, semicolons and unescaped single + quotes are supported. + Escaped double quotes are supported as well but will appear unescaped in + the mail (e.g. "O\'Keefe"). + + Double quotes will be shown in some email clients, so the name should + only be wrapped when necessary. + */ + + // Only wrapp in double quote if comma or semicolon are found + if (false !== strpos($name, ',') || false !== strpos($name, ';')) { + // Unescape quotes + $name = stripslashes(html_entity_decode($name, ENT_QUOTES)); + + // Escape only double quotes + $name = str_replace('"', '\\"', $name); + + // Wrapp in double quotes + $name = '"' . $name . '"'; + } + + $this->name = (!empty($name)) ? $name : null; } public function getName() diff --git a/test/unit/Mail/MailHelperTest.php b/test/unit/Mail/MailHelperTest.php index 51a17d47b..717429507 100644 --- a/test/unit/Mail/MailHelperTest.php +++ b/test/unit/Mail/MailHelperTest.php @@ -201,4 +201,35 @@ public function testKitchenSinkExample() $this->assertEquals($json, '{"from":{"name":"DX","email":"test@example.com"},"personalizations":[{"to":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"cc":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"bcc":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"subject":"Hello World from the SendGrid PHP Library","headers":{"X-Test":"test","X-Mock":"true"},"substitutions":{"%name%":"Example User","%city%":"Denver"},"custom_args":{"user_id":"343","type":"marketing"},"send_at":1443636843},{"to":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"cc":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"bcc":[{"name":"Example User","email":"test@example.com"},{"name":"Example User","email":"test@example.com"}],"subject":"Hello World from the SendGrid PHP Library","headers":{"X-Test":"test","X-Mock":"true"},"substitutions":{"%name%":"Example User","%city%":"Denver"},"custom_args":{"user_id":"343","type":"marketing"},"send_at":1443636843}],"subject":"Hello World from the SendGrid PHP Library","content":[{"type":"text\/plain","value":"some text here"},{"type":"text\/html","value":"some text here<\/body><\/html>"}],"attachments":[{"content":"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12","type":"application\/pdf","filename":"balance_001.pdf","disposition":"attachment","content_id":"Balance Sheet"},{"content":"BwdW","type":"image\/png","filename":"banner.png","disposition":"inline","content_id":"Banner"}],"template_id":"439b6d66-4408-4ead-83de-5c83c2ee313a","sections":{"%section1%":"Substitution Text for Section 1","%section2%":"Substitution Text for Section 2"},"headers":{"X-Test1":"1","X-Test2":"2"},"categories":["May","2016"],"custom_args":{"campaign":"welcome","weekday":"morning"},"send_at":1443636842,"asm":{"group_id":99,"groups_to_display":[4,5,6,7,8]},"ip_pool_name":"23","mail_settings":{"bcc":{"enable":true,"email":"test@example.com"},"bypass_list_management":{"enable":true},"footer":{"enable":true,"text":"Footer Text","html":"Footer Text<\/body><\/html>"},"sandbox_mode":{"enable":true},"spam_check":{"enable":true,"threshold":1,"post_to_url":"https:\/\/spamcatcher.sendgrid.com"}},"tracking_settings":{"click_tracking":{"enable":true,"enable_text":true},"open_tracking":{"enable":true,"substitution_tag":"Optional tag to replace with the open image in the body of the message"},"subscription_tracking":{"enable":true,"text":"text to insert into the text\/plain portion of the message","html":"html to insert into the text\/html portion of the message<\/body><\/html>","substitution_tag":"Optional tag to replace with the open image in the body of the message"},"ganalytics":{"enable":true,"utm_source":"some source","utm_medium":"some medium","utm_term":"some term","utm_content":"some content","utm_campaign":"some name"}},"reply_to":{"email":"test@example.com"}}'); } + + public function testEmailName() + { + $email = new Email('John Doe', 'test@example.com'); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"name":"John Doe","email":"test@example.com"}'); + + $email->setName(''); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"email":"test@example.com"}'); + + $email->setName(null); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"email":"test@example.com"}'); + + $email->setName('Doe, John'); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"name":"\\"Doe, John\\"","email":"test@example.com"}'); + + $email->setName('Doe; John'); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"name":"\\"Doe; John\\"","email":"test@example.com"}'); + + $email->setName('John "Billy" O\'Keeffe'); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"name":"John \\"Billy\\" O\'Keeffe","email":"test@example.com"}'); + + $email->setName('O\'Keeffe, John "Billy"'); + $json = json_encode($email->jsonSerialize()); + $this->assertEquals($json, '{"name":"\\"O\'Keeffe, John \\\\\\"Billy\\\\\\"\\"","email":"test@example.com"}'); + } }