Skip to content

Commit

Permalink
[qmf] Correctly parse display names having comments after the quoted …
Browse files Browse the repository at this point in the history
…string.

According to rfc2822 display names in the format: '"Example" (nested-comments)'
are valid and any existent nested comment should not be quoted.
  • Loading branch information
Valerio Valerio committed Aug 21, 2014
1 parent 4746d58 commit 3311cd4
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 44 deletions.
159 changes: 124 additions & 35 deletions qmf/src/libraries/qmfclient/qmailaddress.cpp
Expand Up @@ -46,6 +46,37 @@

namespace {

static bool needsQuotes(const QString& src)
{
QRegExp specials = QRegExp("[<>\\[\\]:;@\\\\,.]");

QString characters(src);

// Remove any quoted-pair characters, since they don't require quoting
int index = 0;
while ((index = characters.indexOf('\\', index)) != -1)
characters.remove(index, 2);

if ( specials.indexIn( characters ) != -1 )
return true;

// '(' and ')' also need quoting, if they don't conform to nested comments
const QChar* it = characters.constData();
const QChar* const end = it + characters.length();

int commentDepth = 0;
for (; it != end; ++it)
if (*it == '(') {
++commentDepth;
}
else if (*it == ')') {
if (--commentDepth < 0)
return true;
}

return (commentDepth != 0);
}

struct CharacterProcessor
{
virtual ~CharacterProcessor();
Expand Down Expand Up @@ -458,6 +489,94 @@ static QString removeWhitespace(const QString& input)
return remover._result;
}

struct QuoteDisplayName : public CharacterProcessor
{
QuoteDisplayName();

virtual void process(QChar, bool, bool, int);
virtual void finished();

QString _result;

private:
void processPending();

bool _commentProcessing;
bool _quotedProcessing;
QString _processedWord;

};

QuoteDisplayName::QuoteDisplayName()
: _commentProcessing(false),
_quotedProcessing(false),
_processedWord(QString())
{
}

void QuoteDisplayName::processPending()
{
// flush any already processed chars
if (!_processedWord.isEmpty()) {
_result.append(needsQuotes(_processedWord) ? QMail::quoteString(_processedWord) : _processedWord);
_processedWord.clear();
}
}

void QuoteDisplayName::process(QChar character, bool quoted, bool escaped, int commentDepth)
{
Q_UNUSED(escaped);

// Start processing a comment
if (commentDepth > 0) {
if (!_commentProcessing) {
_commentProcessing = true;
}
_processedWord.append(character);
} else if (commentDepth == 0) {
// Flush the comment it does not need quoting
if (_commentProcessing) {
_commentProcessing = false;
_result.append(_processedWord);
_processedWord.clear();
}
// Start processing a quote
if (quoted) {
if (!_quotedProcessing) {
_quotedProcessing = true;
}
_processedWord.append(character);
} else if (!quoted && _quotedProcessing) {
// Finish processing a quote
_quotedProcessing = false;
processPending();
_processedWord.append(character);
} else {
_processedWord.append(character);
}
}
}

void QuoteDisplayName::finished()
{
// check if our processed word is a comment or not
if (!_processedWord.isEmpty()) {
QString tempWord = _processedWord.trimmed();
if (!tempWord.isEmpty() && tempWord.at(0) == '(' && tempWord.at(tempWord.length() -1) == ')') {
_result.append(_processedWord);
} else {
processPending();
}
}
}

static QString quoteIfNecessary(const QString& input)
{
QuoteDisplayName quoteDisplayName;
quoteDisplayName.processCharacters(input);
return quoteDisplayName._result;
}

QPair<int, int> findDelimiters(const QString& text)
{
int first = -1;
Expand Down Expand Up @@ -531,8 +650,9 @@ void parseMailbox(QString& input, QString& name, QString& address, QString& suff
address = input.mid(delimiters.first + 1, (delimiters.second - delimiters.first - 1)).trimmed();
}

if ( name.isEmpty() )
if (name.isEmpty()) {
name = address;
}
}
}

Expand Down Expand Up @@ -721,37 +841,6 @@ QString QMailAddressPrivate::minimalPhoneNumber() const
return minimal.toLower();
}

static bool needsQuotes(const QString& src)
{
QRegExp specials = QRegExp("[<>\\[\\]:;@\\\\,.]");

QString characters(src);

// Remove any quoted-pair characters, since they don't require quoting
int index = 0;
while ((index = characters.indexOf('\\', index)) != -1)
characters.remove(index, 2);

if ( specials.indexIn( characters ) != -1 )
return true;

// '(' and ')' also need quoting, if they don't conform to nested comments
const QChar* it = characters.constData();
const QChar* const end = it + characters.length();

int commentDepth = 0;
for (; it != end; ++it)
if (*it == '(') {
++commentDepth;
}
else if (*it == ')') {
if (--commentDepth < 0)
return true;
}

return (commentDepth != 0);
}

QString QMailAddressPrivate::toString(bool forceDelimited) const
{
QString result;
Expand All @@ -763,9 +852,9 @@ QString QMailAddressPrivate::toString(bool forceDelimited) const
result.append( _name ).append( ": " ).append( _address ).append( ';' );
} else {
// If there are any 'special' characters in the name it needs to be quoted
if ( !_name.isEmpty() )
result = ( needsQuotes( _name ) ? QMail::quoteString( _name ) : _name );

if ( !_name.isEmpty() ) {
result = ::quoteIfNecessary(_name);
}
if ( !_address.isEmpty() ) {
if ( !forceDelimited && result.isEmpty() ) {
result = _address;
Expand Down
1 change: 0 additions & 1 deletion qmf/src/plugins/messageservices/imap/imapconfiguration.cpp
Expand Up @@ -210,7 +210,6 @@ void ImapConfiguration::setSearchLimit(int limit)

bool ImapConfiguration::acceptUntrustedCertificates() const
{
qDebug() << Q_FUNC_INFO << value("acceptUntrustedCertificates", "0");
return (value("acceptUntrustedCertificates", "0").toInt() != 0);
}

Expand Down
136 changes: 128 additions & 8 deletions qmf/tests/tst_qmailaddress/tst_qmailaddress.cpp
Expand Up @@ -303,13 +303,69 @@ void tst_QMailAddress::constructor1_data()
<< "wizard@oz.test"
<< "\"O. Wizard\" <wizard@oz.test>";

/* Honestly, I don't know what to do about this...
QTest::newRow("'\\' needs quoting")
<< true
<< "\"Wizard\\Oz\" <wizard@oz.test>"
<< "Wizard\\Oz"
<< "wizard@oz.test"
<< "\"Wizard\\Oz\" <wizard@oz.test>";
QTest::newRow("Quoted with trailing comment")
<< true
<< "\"Wizard+\" (wizard@oz.test) <wizard@oz.test>"
<< "\"Wizard+\" (wizard@oz.test)"
<< "wizard@oz.test"
<< "\"Wizard+\" (wizard@oz.test) <wizard@oz.test>";

QTest::newRow("Quoted with 2 trailing comment")
<< true
<< "\"Wizard+\" (wizard@oz.test) (comment) <wizard@oz.test>"
<< "\"Wizard+\" (wizard@oz.test) (comment)"
<< "wizard@oz.test"
<< "\"Wizard+\" (wizard@oz.test) (comment) <wizard@oz.test>";

QTest::newRow("2 comments with quote in the middle")
<< true
<< "(wizard@oz.test) \"Wizard+\" (comment) <wizard@oz.test>"
<< "(wizard@oz.test) \"Wizard+\" (comment)"
<< "wizard@oz.test"
<< "(wizard@oz.test) \"Wizard+\" (comment) <wizard@oz.test>";

QTest::newRow("2 comments with name in the middle")
<< true
<< "(wizard@oz.test) WizardOz (comment) <wizard@oz.test>"
<< "(wizard@oz.test) WizardOz (comment)"
<< "wizard@oz.test"
<< "(wizard@oz.test) WizardOz (comment) <wizard@oz.test>";

QTest::newRow("Word with quoted char in the middle")
<< true
<< "Wiz\"a\"rd+ <wizard@oz.test>"
<< "Wiz\"a\"rd+"
<< "wizard@oz.test"
<< "Wiz\"a\"rd+ <wizard@oz.test>";

QTest::newRow("Quoted word with quoted char in the middle")
<< true
<< "\"Wiz\"a\"rd+\" <wizard@oz.test>"
<< "Wiz\"a\"rd+"
<< "wizard@oz.test"
<< "\"Wiz\"a\"rd+\" <wizard@oz.test>";

QTest::newRow("Quoted word with comment with a quote")
<< true
<< "\"Wizard+\" (comment\"quoted\") <wizard@oz.test>"
<< "\"Wizard+\" (comment\"quoted\")"
<< "wizard@oz.test"
<< "\"Wizard+\" (comment\"quoted\") <wizard@oz.test>";

QTest::newRow("Quoted word with empty space")
<< true
<< " \"Wizard+\" (comment) <wizard@oz.test>"
<< "\"Wizard+\" (comment)"
<< "wizard@oz.test"
<< "\"Wizard+\" (comment) <wizard@oz.test>";

/*Honestly, I don't know what to do about this..
QTest::newRow("'\\' needs quoting")
<< true
<< "\"Wizard\\Oz\" <wizard@oz.test>"
<< "Wizard\\Oz"
<< "wizard@oz.test"
<< "\"Wizard\\Oz\" <wizard@oz.test>";
*/
}

Expand Down Expand Up @@ -510,8 +566,72 @@ void tst_QMailAddress::constructor2_data()
<< "wizard@oz.test"
<< "\"O. Wizard\" <wizard@oz.test>";

QTest::newRow("Quoted with trailing comment")
<< "\"Wizard+\" (wizard@oz.test)"
<< "wizard@oz.test"
<< true
<< "\"Wizard+\" (wizard@oz.test)"
<< "wizard@oz.test"
<< "\"Wizard+\" (wizard@oz.test) <wizard@oz.test>";

QTest::newRow("Quoted with 2 trailing comment")
<< "\"Wizard+\" (wizard@oz.test) (comment)"
<< "wizard@oz.test"
<< true
<< "\"Wizard+\" (wizard@oz.test) (comment)"
<< "wizard@oz.test"
<< "\"Wizard+\" (wizard@oz.test) (comment) <wizard@oz.test>";

QTest::newRow("2 comments with quote in the middle")
<< "(wizard@oz.test) \"Wizard+\" (comment)"
<< "wizard@oz.test"
<< true
<< "(wizard@oz.test) \"Wizard+\" (comment)"
<< "wizard@oz.test"
<< "(wizard@oz.test) \"Wizard+\" (comment) <wizard@oz.test>";

QTest::newRow("2 comments with name in the middle")
<< "(wizard@oz.test) WizardOz (comment)"
<< "wizard@oz.test"
<< true
<< "(wizard@oz.test) WizardOz (comment)"
<< "wizard@oz.test"
<< "(wizard@oz.test) WizardOz (comment) <wizard@oz.test>";

QTest::newRow("Word with quoted char in the middle")
<< "Wiz\"a\"rd+"
<< "wizard@oz.test"
<< true
<< "Wiz\"a\"rd+"
<< "wizard@oz.test"
<< "Wiz\"a\"rd+ <wizard@oz.test>";

QTest::newRow("Quoted word with quoted char in the middle")
<< "\"Wiz\"a\"rd+\""
<< "wizard@oz.test"
<< true
<< "Wiz\"a\"rd+"
<< "wizard@oz.test"
<< "\"Wiz\"a\"rd+\" <wizard@oz.test>";

QTest::newRow("Quoted word with comment with a quote")
<< "\"Wizard+\" (comment\"quoted\")"
<< "wizard@oz.test"
<< true
<< "\"Wizard+\" (comment\"quoted\")"
<< "wizard@oz.test"
<< "\"Wizard+\" (comment\"quoted\") <wizard@oz.test>";

QTest::newRow("Quoted word with empty space")
<< " \"Wizard+\" (comment)"
<< "wizard@oz.test"
<< true
<< "\"Wizard+\" (comment)"
<< "wizard@oz.test"
<< "\"Wizard+\" (comment) <wizard@oz.test>";

/* Honestly, I don't know what to do about this...
QTest::newRow("'\\' needs quoting")
QTest::newRow("'\\' needs quoting")
<< "Wizard\\Oz"
<< "wizard@oz.test"
<< true
Expand Down

0 comments on commit 3311cd4

Please sign in to comment.