Commit 4269073f authored by Sven Strickroth's avatar Sven Strickroth

Fixed issue #2560: Problems with MS Outlook and "Name" <john@example.com> formatted email-addresses

Based on mailinfo.c of the git project coded by Junio C Hamano <gitster@pobox.com>.
Signed-off-by: Sven Strickroth's avatarSven Strickroth <email@cs-ware.de>
parent fb37b615
......@@ -32,6 +32,7 @@ Released: unreleased
* Fixed issue #2598: Git Daemon error with root path (as "D:\")
* Fixed issue #2615: Error during Rebase Edit/Split on a root path
* Fixed issue #2528: Unreadably long message box when failing to read a file in a new project
* Fixed issue #2560: Problems with MS Outlook and "Name" <john@example.com> formatted email-addresses
= Release 1.8.15.0 =
Released: 2015-08-02
......
......@@ -50,6 +50,7 @@
#include "stdafx.h"
#include "MailMsg.h"
#include "UnicodeUtils.h"
#include "StringUtils.h"
CMailMsg::CMailMsg()
{
......@@ -68,20 +69,21 @@ CMailMsg::~CMailMsg()
void CMailMsg::SetFrom(const CString& sAddress, const CString& sName)
{
m_from = CUnicodeUtils::GetUTF8(L"SMTP:" + sAddress);
m_fromname = CUnicodeUtils::GetUTF8(sName);
m_from.email = CUnicodeUtils::GetUTF8(L"SMTP:" + sAddress);
m_from.name = CUnicodeUtils::GetUTF8(sName);
}
static void addAdresses(std::vector<std::string>& recipients, const CString& sAddresses)
static void addAdresses(std::vector<MailAddress>& recipients, const CString& sAddresses)
{
int start = 0;
while (start >= 0)
{
CString address = sAddresses.Tokenize(_T(";"), start);
address = address.Trim();
CString name;
CStringUtils::ParseEmailAddress(address, address, &name);
if (address.IsEmpty())
continue;
recipients.push_back((std::string)CUnicodeUtils::GetUTF8(L"SMTP:" + address));
recipients.emplace_back(L"SMTP:" + address, name);
}
}
......@@ -245,8 +247,8 @@ BOOL CMailMsg::Send()
// set from
pRecipients[0].ulReserved = 0;
pRecipients[0].ulRecipClass = MAPI_ORIG;
pRecipients[0].lpszAddress = (LPSTR)m_from.c_str();
pRecipients[0].lpszName = (LPSTR)m_fromname.c_str();
pRecipients[0].lpszAddress = (LPSTR)m_from.email.c_str();
pRecipients[0].lpszName = (LPSTR)m_from.name.c_str();
pRecipients[0].ulEIDSize = 0;
pRecipients[0].lpEntryID = NULL;
......@@ -256,8 +258,8 @@ BOOL CMailMsg::Send()
++nIndex;
pRecipients[nIndex].ulReserved = 0;
pRecipients[nIndex].ulRecipClass = MAPI_TO;
pRecipients[nIndex].lpszAddress = (LPSTR)m_to.at(i).c_str();
pRecipients[nIndex].lpszName = (LPSTR)m_to.at(i).c_str() + 5;
pRecipients[nIndex].lpszAddress = (LPSTR)m_to.at(i).email.c_str();
pRecipients[nIndex].lpszName = (LPSTR)m_to.at(i).name.c_str();
pRecipients[nIndex].ulEIDSize = 0;
pRecipients[nIndex].lpEntryID = NULL;
}
......@@ -268,8 +270,8 @@ BOOL CMailMsg::Send()
++nIndex;
pRecipients[nIndex].ulReserved = 0;
pRecipients[nIndex].ulRecipClass = MAPI_CC;
pRecipients[nIndex].lpszAddress = (LPSTR)m_cc.at(i).c_str();
pRecipients[nIndex].lpszName = (LPSTR)m_cc.at(i).c_str() + 5;
pRecipients[nIndex].lpszAddress = (LPSTR)m_cc.at(i).email.c_str();
pRecipients[nIndex].lpszName = (LPSTR)m_cc.at(i).name.c_str();
pRecipients[nIndex].ulEIDSize = 0;
pRecipients[nIndex].lpEntryID = NULL;
}
......
......@@ -48,6 +48,19 @@
typedef std::map<std::string, std::string> TStrStrMap;
typedef struct MailAddress
{
std::string email;
std::string name;
MailAddress() {};
MailAddress(const CString& email, const CString& name)
: email((CStringA)email)
, name((CStringA)name)
{}
} MailAddress;
// ===========================================================================
// CMailMsg
//
......@@ -78,11 +91,10 @@ public:
CString GetLastErrorMsg(){ return m_sErrorMsg; }
protected:
std::string m_from; // From <address,name>
std::string m_fromname; // From <address,name>
std::vector<std::string> m_to; // To receipients
MailAddress m_from;
std::vector<MailAddress> m_to; // To receipients
TStrStrMap m_attachments; // Attachment <file,title>
std::vector<std::string> m_cc; // CC receipients
std::vector<MailAddress> m_cc; // CC receipients
std::string m_sSubject; // EMail subject
std::string m_sMessage; // EMail message
......
// TortoiseSVN - a Windows shell extension for easy version control
// TortoiseGit - a Windows shell extension for easy version control
// Copyright (C) 2015 - TortoiseGit
// Copyright (C) 2003-2011, 2015 - TortoiseSVN
// This program is free software; you can redistribute it and/or
......@@ -429,6 +430,100 @@ int CStringUtils::FastCompareNoCase (const CStringW& lhs, const CStringW& rhs)
return 0;
}
static void cleanup_space(CString& string)
{
for (int pos = 0; pos < string.GetLength(); ++pos)
{
if (_istspace(string[pos])) {
string.SetAt(pos ,_T(' '));
int cnt;
for (cnt = 0; _istspace(string[pos + cnt + 1]); ++cnt);
string.Delete(pos + 1, cnt);
}
}
}
static void get_sane_name(CString* out, const CString* name, const CString& email)
{
const CString* src = name;
if (name->GetLength() < 3 || 60 < name->GetLength() || _tcschr(*name, _T('@')) || _tcschr(*name, _T('<')) || _tcschr(*name, _T('>')))
src = &email;
else if (name == out)
return;
*out = *src;
}
static void parse_bogus_from(const CString& mailaddress, CString& parsedAddress, CString* parsedName)
{
/* John Doe <johndoe> */
int bra = mailaddress.Find(L"<");
if (bra < 0)
return;
int ket = mailaddress.Find(L">");
if (ket < 0)
return;
parsedAddress = mailaddress.Mid(bra + 1, ket - bra - 1);
if (parsedName)
{
*parsedName = mailaddress.Left(bra).Trim();
get_sane_name(parsedName, parsedName, parsedAddress);
}
}
void CStringUtils::ParseEmailAddress(CString mailaddress, CString& parsedAddress, CString* parsedName)
{
auto buf = mailaddress.GetBuffer();
auto at = _tcschr(buf, _T('@'));
if (!at)
{
parse_bogus_from(mailaddress, parsedAddress, parsedName);
return;
}
/* Pick up the string around '@', possibly delimited with <>
* pair; that is the email part.
*/
while (at > buf)
{
auto c = at[-1];
if (_istspace(c))
break;
if (c == _T('<')) {
at[-1] = _T(' ');
break;
}
at--;
}
mailaddress.ReleaseBuffer();
size_t el = _tcscspn(at, _T(" \n\t\r\v\f>"));
parsedAddress = mailaddress.Mid((int)(at - buf), (int)el);
mailaddress.Delete((int)(at - buf), (int)(el + (at[el] ? 1 : 0)));
/* The remainder is name. It could be
*
* - "John Doe <john.doe@xz>" (a), or
* - "john.doe@xz (John Doe)" (b), or
* - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
*
* but we have removed the email part, so
*
* - remove extra spaces which could stay after email (case 'c'), and
* - trim from both ends, possibly removing the () pair at the end
* (cases 'b' and 'c').
*/
cleanup_space(mailaddress);
mailaddress.Trim();
if (!mailaddress.IsEmpty() && ((mailaddress[0] == _T('(') && mailaddress[mailaddress.GetLength() - 1] == _T(')')) || (mailaddress[0] == _T('"') && mailaddress[mailaddress.GetLength() - 1] == _T('"'))))
mailaddress = mailaddress.Mid(1, mailaddress.GetLength() - 2);
if (parsedName)
get_sane_name(parsedName, &mailaddress, parsedAddress);
}
#endif // #if defined(CSTRING_AVAILABLE) || defined(_MFC_VER)
bool CStringUtils::WriteStringToTextFile(const std::wstring& path, const std::wstring& text, bool bUTF8 /* = true */)
......
......@@ -100,6 +100,8 @@ public:
* Optimizing wrapper around CompareNoCase.
*/
static int FastCompareNoCase (const CStringW& lhs, const CStringW& rhs);
static void ParseEmailAddress(CString mailaddress, CString& parsedAddress, CString* parsedName = nullptr);
#endif
/**
* Writes the string \text to the file \path, either in utf16 or utf8 encoding,
......
......@@ -96,3 +96,85 @@ TEST(CStringUtils, RemoveAccelerators)
CStringUtils::RemoveAccelerators(text6);
EXPECT_STREQ(L"Some & text", text6);
}
TEST(CStringUtils, ParseEmailAddress)
{
CString mail, name;
CStringUtils::ParseEmailAddress(_T(""), mail, &name);
EXPECT_STREQ(_T(""), mail);
EXPECT_STREQ(_T(""), name);
CStringUtils::ParseEmailAddress(_T(" "), mail, &name);
EXPECT_STREQ(_T(""), mail);
EXPECT_STREQ(_T(""), name);
mail.Empty();
CStringUtils::ParseEmailAddress(_T("test@example.com "), mail);
EXPECT_STREQ(_T("test@example.com"), mail);
mail.Empty();
CStringUtils::ParseEmailAddress(_T(" test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), mail);
mail.Empty();
CStringUtils::ParseEmailAddress(_T("test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), mail);
mail.Empty();
CStringUtils::ParseEmailAddress(_T("John Doe <johndoe>"), mail);
EXPECT_STREQ(_T("johndoe"), mail);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("test@example.com"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("<test@example.com>"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("John Doe <test@example.com>"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("John Doe"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("\"John Doe\" <test@example.com>"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("John Doe"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("<test@example.com"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("test@example.com>"), mail, &name);
EXPECT_STREQ(_T("test@example.com"), mail);
EXPECT_STREQ(_T("test@example.com"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("John Doe <johndoe>"), mail, &name);
EXPECT_STREQ(_T("johndoe"), mail);
EXPECT_STREQ(_T("John Doe"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("john.doe@example.com (John Doe)"), mail, &name);
EXPECT_STREQ(_T("john.doe@example.com"), mail);
EXPECT_STREQ(_T("John Doe"), name);
mail.Empty();
name.Empty();
CStringUtils::ParseEmailAddress(_T("John (zzz) Doe <john.doe@example.com> (Comment)"), mail, &name);
EXPECT_STREQ(_T("john.doe@example.com"), mail);
EXPECT_STREQ(_T("John (zzz) Doe (Comment)"), name);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment