diff -r 000000000000 -r 876b1a06bc25 tests/auto/qversitreader/tst_qversitreader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/auto/qversitreader/tst_qversitreader.cpp Wed Aug 25 15:49:42 2010 +0300 @@ -0,0 +1,1501 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qversitreader.h" +#include "qversitreader.h" +#include "qversitproperty.h" +#ifdef QT_BUILD_INTERNAL +#include "qversitreader_p.h" +#include "versitutils_p.h" +#endif +#include +#include + +// Copied from tst_qcontactmanager.cpp +// Waits until __expr is true and fails if it doesn't happen within 5s. +#ifndef QTRY_VERIFY +#define QTRY_VERIFY(__expr) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if (!(__expr)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QVERIFY(__expr); \ + } while(0) +#endif + +// This says "NOKIA" in Katakana encoded with UTF-8 +const QByteArray KATAKANA_NOKIA("\xe3\x83\x8e\xe3\x82\xad\xe3\x82\xa2"); + +const static QByteArray SAMPLE_GIF_BASE64(QByteArray( + "R0lGODlhEgASAIAAAAAAAP///yH5BAEAAAEALAAAAAASABIAAAIdjI+py+0G" + "wEtxUmlPzRDnzYGfN3KBaKGT6rDmGxQAOw==")); + +const static QByteArray SAMPLE_GIF(QByteArray::fromBase64(SAMPLE_GIF_BASE64)); + +Q_DECLARE_METATYPE(QVersitDocument::VersitType); +Q_DECLARE_METATYPE(QVersitProperty); + +QTM_USE_NAMESPACE + +void tst_QVersitReader::init() +{ + mInputDevice = new QBuffer; + mInputDevice->open(QBuffer::ReadWrite); + mReader = new QVersitReader; +#ifdef QT_BUILD_INTERNAL + mReaderPrivate = new QVersitReaderPrivate; +#endif + mSignalCatcher = new SignalCatcher; + connect(mReader, SIGNAL(stateChanged(QVersitReader::State)), + mSignalCatcher, SLOT(stateChanged(QVersitReader::State))); + connect(mReader, SIGNAL(resultsAvailable()), + mSignalCatcher, SLOT(resultsAvailable())); + mAsciiCodec = QTextCodec::codecForName("ISO 8859-1"); +} + +void tst_QVersitReader::cleanup() +{ +#ifdef QT_BUILD_INTERNAL + delete mReaderPrivate; +#endif + delete mReader; + delete mInputDevice; + delete mSignalCatcher; +} + +void tst_QVersitReader::testDevice() +{ + // No device + QVERIFY(mReader->device() == NULL); + + // Device has been set + mReader->setDevice(mInputDevice); + QVERIFY(mReader->device() == mInputDevice); + + delete mInputDevice; + QVERIFY(mReader->device() == NULL); + + mInputDevice = new QBuffer; + mInputDevice->open(QBuffer::ReadWrite); + + QVERIFY(mReader->device() == NULL); + mReader->setDevice(mInputDevice); + QVERIFY(mReader->device() == mInputDevice); +} + +void tst_QVersitReader::testDefaultCodec() +{ + QVERIFY(mReader->defaultCodec() == QTextCodec::codecForName("UTF-8")); + mReader->setDefaultCodec(QTextCodec::codecForName("UTF-16BE")); + QVERIFY(mReader->defaultCodec() == QTextCodec::codecForName("UTF-16BE")); +} + + +void tst_QVersitReader::testReading() +{ + // No I/O device set + QVERIFY(!mReader->startReading()); + QCOMPARE(mReader->error(), QVersitReader::IOError); + + // Device set, no data + mReader->setDevice(mInputDevice); + mInputDevice->open(QBuffer::ReadOnly); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + QList results(mReader->results()); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::NoError); + QCOMPARE(results.count(),0); + + // Device set, one document + const QByteArray& oneDocument = + "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n"; + mInputDevice->close(); + mInputDevice->setData(oneDocument); + mInputDevice->open(QBuffer::ReadOnly); + mInputDevice->seek(0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::NoError); + QCOMPARE(results.count(),1); + + // Wide charset with no byte-order mark + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + QTextCodec::ConverterState converterState(QTextCodec::IgnoreHeader); + QString document = QString::fromAscii("BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n"); + const QByteArray& wideDocument = + codec->fromUnicode(document.data(), document.length(), &converterState); + mInputDevice->close(); + mInputDevice->setData(wideDocument); + mInputDevice->open(QBuffer::ReadOnly); + mInputDevice->seek(0); + mReader->setDefaultCodec(codec); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::NoError); + QCOMPARE(mReader->results().count(),1); + mReader->setDefaultCodec(NULL); + + // Two documents + const QByteArray& twoDocuments = + " \r\n BEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\nBEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n"; + mInputDevice->close(); + mInputDevice->setData(twoDocuments); + mInputDevice->open(QBuffer::ReadOnly); + mInputDevice->seek(0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::NoError); + QCOMPARE(results.count(),2); + + // Erroneous document (missing property name) + mInputDevice->close(); + mInputDevice->setData(QByteArray( + "BEGIN:VCARD\r\nFN:Jenny\r\n;Jenny;;;\r\nEND:VCARD\r\n" + "BEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n")); + mInputDevice->open(QBuffer::ReadOnly); + mInputDevice->seek(0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::ParseError); + QCOMPARE(results.count(), 1); + + // Valid documents and a grouped document between them + const QByteArray& validDocumentsAndGroupedDocument = +"BEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\n\ +X-GROUPING:pub gang\r\n\ +BEGIN:VCARD\r\nFN:Jeremy\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jeffery\r\nEND:VCARD\r\n\ +END:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:James\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jane\r\nEND:VCARD\r\n"; + mInputDevice->close(); + mInputDevice->setData(validDocumentsAndGroupedDocument); + mInputDevice->open(QBuffer::ReadWrite); + mInputDevice->seek(0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + // An error is logged because one failed, but the rest are readable. + QCOMPARE(mReader->error(), QVersitReader::ParseError); + QCOMPARE(results.count(),4); + + // Valid documents and a grouped document between them + const QByteArray& validDocumentsAndGroupedDocument2 = +"BEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\n\ +X-GROUPING:pub gang\r\n\ +BEGIN:VCARD\r\nFN:Jeremy\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jeffery\r\nEND:VCARD\r\n\ +END:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:James\r\nEND:VCARD\r\n\ +BEGIN:VCARD\r\nFN:Jane\r\nEND:VCARD"; + mInputDevice->close(); + mInputDevice->setData(validDocumentsAndGroupedDocument2); + mInputDevice->open(QBuffer::ReadWrite); + mInputDevice->seek(0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + // An error is logged because one failed, but the rest are readable. + QCOMPARE(mReader->error(), QVersitReader::ParseError); + QCOMPARE(mReader->results().count(),4); + + qApp->processEvents(); // clean up before we start sniffing signals + + // Asynchronous reading + mInputDevice->close(); + mInputDevice->setData(twoDocuments); + mInputDevice->open(QBuffer::ReadWrite); + mInputDevice->seek(0); + mSignalCatcher->mStateChanges.clear(); + mSignalCatcher->mResultsCount = 0; + QVERIFY(mReader->startReading()); + QTRY_VERIFY(mSignalCatcher->mStateChanges.count() >= 2); + QCOMPARE(mSignalCatcher->mStateChanges.at(0), QVersitReader::ActiveState); + QCOMPARE(mSignalCatcher->mStateChanges.at(1), QVersitReader::FinishedState); + QVERIFY(mSignalCatcher->mResultsCount >= 2); + QCOMPARE(mReader->results().size(), 2); + QCOMPARE(mReader->error(), QVersitReader::NoError); + + // Cancelling + mInputDevice->close(); + mInputDevice->setData(twoDocuments); + mInputDevice->open(QBuffer::ReadOnly); + mInputDevice->seek(0); + mSignalCatcher->mStateChanges.clear(); + mSignalCatcher->mResultsCount = 0; + QVERIFY(mReader->startReading()); + mReader->cancel(); + mReader->waitForFinished(); + QTRY_VERIFY(mSignalCatcher->mStateChanges.count() >= 2); + QCOMPARE(mSignalCatcher->mStateChanges.at(0), QVersitReader::ActiveState); + QVersitReader::State state(mSignalCatcher->mStateChanges.at(1)); + // It's possible that it finishes before it cancels. + QVERIFY(state == QVersitReader::CanceledState + || state == QVersitReader::FinishedState); +} + +void tst_QVersitReader::testResult() +{ + QCOMPARE(mReader->results().count(),0); +} + +void tst_QVersitReader::testSetVersionFromProperty() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QVersitDocument document; + + // Some other property than VERSION + QVersitProperty property; + property.setName(QString::fromAscii("N")); + QVERIFY(mReaderPrivate->setVersionFromProperty(document,property)); + + // VERSION property with 2.1 + property.setName(QString::fromAscii("VERSION")); + property.setValue(QString::fromAscii("2.1")); + QVERIFY(mReaderPrivate->setVersionFromProperty(document,property)); + QVERIFY(document.type() == QVersitDocument::VCard21Type); + + // VERSION property with 3.0 + property.setValue(QString::fromAscii("3.0")); + QVERIFY(mReaderPrivate->setVersionFromProperty(document,property)); + QVERIFY(document.type() == QVersitDocument::VCard30Type); + + // VERSION property with a not supported value + property.setValue(QString::fromAscii("4.0")); + QVERIFY(!mReaderPrivate->setVersionFromProperty(document,property)); + + // VERSION property with BASE64 encoded supported value + property.setValue(QString::fromAscii(QByteArray("2.1").toBase64())); + property.insertParameter(QString::fromAscii("ENCODING"),QString::fromAscii("BASE64")); + QVERIFY(mReaderPrivate->setVersionFromProperty(document,property)); + QVERIFY(document.type() == QVersitDocument::VCard21Type); + + // VERSION property with BASE64 encoded not supported value + property.setValue(QString::fromAscii(QByteArray("4.0").toBase64())); + QVERIFY(!mReaderPrivate->setVersionFromProperty(document,property)); +#endif +} + +void tst_QVersitReader::testParseNextVersitProperty() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QFETCH(QVersitDocument::VersitType, documentType); + QFETCH(QByteArray, input); + QFETCH(QVersitProperty, expectedProperty); + + QBuffer buffer(&input); + buffer.open(QIODevice::ReadOnly); + LineReader lineReader(&buffer, mAsciiCodec); + QVersitProperty property = mReaderPrivate->parseNextVersitProperty(documentType, lineReader); + if (property != expectedProperty) { + // compare each part of the property separately for easier debugging + QCOMPARE(property.groups(), expectedProperty.groups()); + QCOMPARE(property.name(), expectedProperty.name()); + QCOMPARE(property.valueType(), expectedProperty.valueType()); + + // QVariant doesn't support == on QVersitDocuments - do it manually + if (property.variantValue().userType() == qMetaTypeId()) { + QVERIFY(expectedProperty.variantValue().userType() == qMetaTypeId()); + QCOMPARE(property.value(), expectedProperty.value()); + } + else + QCOMPARE(property.variantValue(), expectedProperty.variantValue()); + + // Don't check parameters because the reader can add random parameters of its own (like CHARSET) + // QCOMPARE(property.parameters(), expectedProperty.parameters()); + } +#endif +} + +void tst_QVersitReader::testParseNextVersitProperty_data() +{ +#ifdef QT_BUILD_INTERNAL + QTest::addColumn("documentType"); + QTest::addColumn("input"); + QTest::addColumn("expectedProperty"); + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("BEGIN")); + expectedProperty.setValue(QLatin1String("vcard")); + QTest::newRow("begin") + << QVersitDocument::VCard21Type + << QByteArray("Begin:vcard\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("VERSION")); + expectedProperty.setValue(QLatin1String("2.1")); + expectedProperty.setValueType(QVersitProperty::PlainType); + QTest::newRow("version") + << QVersitDocument::VCard21Type + << QByteArray("VERSION:2.1\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("FN")); + expectedProperty.setValue(QLatin1String("John")); + expectedProperty.setValueType(QVersitProperty::PlainType); + QTest::newRow("fn") + << QVersitDocument::VCard21Type + << QByteArray("FN:John\r\n") + << expectedProperty; + } + + { + // "NOTE:\;\,\:\\" + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("NOTE")); + expectedProperty.setValue(QLatin1String("\\;\\,\\:\\\\")); + expectedProperty.setValueType(QVersitProperty::PlainType); + QTest::newRow("vcard21 note") + << QVersitDocument::VCard21Type + << QByteArray("NOTE:\\;\\,\\:\\\\\r\n") + << expectedProperty; + + expectedProperty.setValue(QLatin1String(";,:\\")); + QTest::newRow("vcard30 note") + << QVersitDocument::VCard30Type + << QByteArray("NOTE:\\;\\,\\:\\\\\r\n") + << expectedProperty; + } + + { + // "N:foo\;bar;foo\,bar;foo\:bar;foo\\bar;foo\\\;bar" + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("N")); + QStringList components; + components << QLatin1String("foo;bar") + << QLatin1String("foo\\,bar") + << QLatin1String("foo\\:bar") + << QLatin1String("foo\\\\bar") + << QLatin1String("foo\\\\;bar"); + expectedProperty.setValue(components); + expectedProperty.setValueType(QVersitProperty::CompoundType); + QTest::newRow("vcard21 n") + << QVersitDocument::VCard21Type + << QByteArray("N:foo\\;bar;foo\\,bar;foo\\:bar;foo\\\\bar;foo\\\\\\;bar\r\n") + << expectedProperty; + + components.clear(); + components << QLatin1String("foo;bar") + << QLatin1String("foo,bar") + << QLatin1String("foo:bar") + << QLatin1String("foo\\bar") + << QLatin1String("foo\\;bar"); + expectedProperty.setValue(components); + QTest::newRow("vcard30 n") + << QVersitDocument::VCard30Type + << QByteArray("N:foo\\;bar;foo\\,bar;foo\\:bar;foo\\\\bar;foo\\\\\\;bar\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("ADR")); + expectedProperty.setValue(QStringList(QString())); + expectedProperty.setValueType(QVersitProperty::CompoundType); + QTest::newRow("empty structured") + << QVersitDocument::VCard21Type + << QByteArray("ADR:\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("X-CHILDREN")); + expectedProperty.setValue(QStringList() << QLatin1String("Child1") << QLatin1String("Child2")); + expectedProperty.setValueType(QVersitProperty::ListType); + QTest::newRow("children") + << QVersitDocument::VCard21Type + << QByteArray("X-CHILDREN:Child1,Child2\r\n") + << expectedProperty; + } + + { + // "NICKNAME:foo\;bar,foo\,bar,foo\:bar,foo\\bar,foo\\\,bar" + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("NICKNAME")); + QStringList components; + components << QLatin1String("foo\\;bar") + << QLatin1String("foo,bar") + << QLatin1String("foo\\:bar") + << QLatin1String("foo\\\\bar") + << QLatin1String("foo\\\\,bar"); + expectedProperty.setValue(components); + expectedProperty.setValueType(QVersitProperty::ListType); + QTest::newRow("vcard21 nickname") + << QVersitDocument::VCard21Type + << QByteArray("NICKNAME:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n") + << expectedProperty; + + components.clear(); + components << QLatin1String("foo;bar") + << QLatin1String("foo,bar") + << QLatin1String("foo:bar") + << QLatin1String("foo\\bar") + << QLatin1String("foo\\,bar"); + expectedProperty.setValue(components); + QTest::newRow("vcard30 nickname") + << QVersitDocument::VCard30Type + << QByteArray("NICKNAME:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n") + << expectedProperty; + } + + { + // "CATEGORIES:foo\;bar,foo\,bar,foo\:bar,foo\\bar,foo\\\,bar" + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("CATEGORIES")); + QStringList components; + components << QLatin1String("foo\\;bar") + << QLatin1String("foo,bar") + << QLatin1String("foo\\:bar") + << QLatin1String("foo\\\\bar") + << QLatin1String("foo\\\\,bar"); + expectedProperty.setValue(components); + expectedProperty.setValueType(QVersitProperty::ListType); + QTest::newRow("vcard21 categories") + << QVersitDocument::VCard21Type + << QByteArray("CATEGORIES:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n") + << expectedProperty; + + components.clear(); + components << QLatin1String("foo;bar") + << QLatin1String("foo,bar") + << QLatin1String("foo:bar") + << QLatin1String("foo\\bar") + << QLatin1String("foo\\,bar"); + expectedProperty.setValue(components); + QTest::newRow("vcard30 categories") + << QVersitDocument::VCard30Type + << QByteArray("CATEGORIES:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n") + << expectedProperty; + + // "CATEGORIES:foobar\\,foobar\\\\,foo\\\\\,bar" + components.clear(); + components << QLatin1String("foobar\\") + << QLatin1String("foobar\\\\") + << QLatin1String("foo\\\\,bar"); + expectedProperty.setValue(components); + QTest::newRow("vcard30 unescaping") + << QVersitDocument::VCard30Type + << QByteArray("CATEGORIES:foobar\\\\,foobar\\\\\\\\,foo\\\\\\\\\\,bar") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("ORG")); + expectedProperty.setValue(QString::fromUtf8(KATAKANA_NOKIA)); + expectedProperty.setValueType(QVersitProperty::CompoundType); + QTest::newRow("org utf8") + << QVersitDocument::VCard21Type + << QByteArray("ORG;CHARSET=UTF-8:" + KATAKANA_NOKIA + "\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("ORG")); + expectedProperty.setValue(QString::fromUtf8(KATAKANA_NOKIA)); + expectedProperty.setValueType(QVersitProperty::CompoundType); + QTest::newRow("vcard21 org utf8 qp") + << QVersitDocument::VCard21Type + << QByteArray("ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2\r\n") + << expectedProperty; + QTest::newRow("vcard30 org utf8 qp") + << QVersitDocument::VCard30Type + << QByteArray("ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("PHOTO")); + expectedProperty.setValue(SAMPLE_GIF); + expectedProperty.setValueType(QVersitProperty::BinaryType); + QTest::newRow("vcard21 photo1") + << QVersitDocument::VCard21Type + << QByteArray("PHOTO;ENCODING=BASE64:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n") + << expectedProperty; + + QTest::newRow("vcard30 photo1") + << QVersitDocument::VCard30Type + << QByteArray("PHOTO;ENCODING=B:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n") + << expectedProperty; + + // Again, but without the explicit "ENCODING" parameter + QTest::newRow("vcard21 photo2") + << QVersitDocument::VCard21Type + << QByteArray("PHOTO;BASE64:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n") + << expectedProperty; + + QTest::newRow("vcard30 photo2") + << QVersitDocument::VCard30Type + << QByteArray("PHOTO;B:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n") + << expectedProperty; + } + + { + QVersitProperty expectedProperty; + expectedProperty.setGroups(QStringList() << QLatin1String("HOME") << QLatin1String("Springfield")); + expectedProperty.setName(QLatin1String("EMAIL")); + expectedProperty.setValue(QLatin1String("john.citizen@example.com")); + expectedProperty.setValueType(QVersitProperty::PlainType); + QTest::newRow("email qp") + << QVersitDocument::VCard21Type + << QByteArray("HOME.Springfield.EMAIL;Encoding=Quoted-Printable:john.citizen=40exam=\r\nple.com\r\n") + << expectedProperty; + } + + { + QVersitDocument subDocument; + subDocument.setType(QVersitDocument::VCard30Type); + QVersitProperty subProperty; + subProperty.setName(QLatin1String("FN")); + subProperty.setValue(QLatin1String("Jenny")); + subDocument.addProperty(subProperty); + + QVersitProperty expectedProperty; + expectedProperty.setName(QLatin1String("AGENT")); + expectedProperty.setValue(QVariant::fromValue(subDocument)); + expectedProperty.setValueType(QVersitProperty::VersitDocumentType); + QTest::newRow("agent") + << QVersitDocument::VCard21Type + << QByteArray("AGENT:\r\nBEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\n\r\n") + << expectedProperty; + } +#endif +} + +void tst_QVersitReader::testParseVersitDocument() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QFETCH(QByteArray, vCard); + QFETCH(bool, expectedSuccess); + QFETCH(int, expectedProperties); + + QBuffer buffer(&vCard); + buffer.open(QIODevice::ReadOnly); + LineReader lineReader(&buffer, QTextCodec::codecForName("UTF-8")); + + QVersitDocument document; + QCOMPARE(mReaderPrivate->parseVersitDocument(lineReader, document), expectedSuccess); + QCOMPARE(document.properties().count(), expectedProperties); + QCOMPARE(mReaderPrivate->mDocumentNestingLevel, 0); +#endif +} + +void tst_QVersitReader::testParseVersitDocument_data() +{ +#ifdef QT_BUILD_INTERNAL + QTest::addColumn("vCard"); + QTest::addColumn("expectedSuccess"); + QTest::addColumn("expectedProperties"); + + QTest::newRow("Basic vCard 2.1") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "FN:John\r\n" + "END:VCARD\r\n") + << true + << 1; + + QTest::newRow("vCard 2.1 with Agent") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "FN:John\r\n" + "AGENT:BEGIN:VCARD\r\nN:Jenny\r\nEND:VCARD\r\n\r\n" + "EMAIL;ENCODING=QUOTED-PRINTABLE:john.citizen=40exam=\r\nple.com\r\n" + "END:VCARD\r\n") + << true + << 3; + + QTest::newRow("vCard 3.0 with Agent") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:3.0\r\n" + "FN:John\r\n" + "AGENT:BEGIN\\:VCARD\\nN\\:Jenny\\nEND\\:VCARD\\n\r\n" + "EMAIL:john.citizen@example.com\r\n" + "END:VCARD\r\n") + << true + << 3; + + QTest::newRow("No BEGIN found") + << QByteArray( + "VCARD\r\n" + "VERSION:2.1\r\n" + "FN:Nobody\r\n" + "END:VCARD\r\n") + << false + << 0; + + QTest::newRow("Wrong card type") + << QByteArray( + "BEGIN:VCAL\r\n" + "END:VCAL\r\n") + << false + << 0; + + QTest::newRow("Wrong version") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + "FN:Nobody\r\n" + "END:VCARD\r\n") + << false + << 0; + + QTest::newRow("No trailing crlf") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "FN:Nobody\r\n" + "END:VCARD") + << true + << 1; + + QTest::newRow("No end") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "FN:Nobody\r\n") + << false + << 0; + + QTest::newRow("Grouped vCards are not supported. The whole vCard will be discarded.") + << QByteArray( + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "X-EXAMPLES:Family vCard\r\n" + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "N:Citizen;John\r\n" + "TEL;CELL:1111\r\n" + "EMAIL;ENCODING=QUOTED-PRINTABLE:john.citizen=40example.com\r\n" + "END:VCARD\r\n" + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "N:Citizen;Jenny\r\n" + "TEL;CELL:7777\r\n" + "END:VCARD\r\n" + "END:VCARD") + << false + << 0; +#endif +} + +void tst_QVersitReader::testDecodeQuotedPrintable() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QFETCH(QByteArray, encoded); + + QFETCH(QByteArray, decoded); + mReaderPrivate->decodeQuotedPrintable(encoded); + QCOMPARE(encoded, decoded); +#endif +} + +void tst_QVersitReader::testDecodeQuotedPrintable_data() +{ +#ifdef QT_BUILD_INTERNAL + QTest::addColumn("encoded"); + QTest::addColumn("decoded"); + + + QTest::newRow("Soft line breaks") + << QByteArray("This=\r\n is =\r\none line.") + << QByteArray("This is one line."); + + QTest::newRow("Characters recommended to be encoded according to RFC 1521") + << QByteArray("To be decoded: =0A=0D=21=22=23=24=3D=40=5B=5C=5D=5E=60=7B=7C=7D=7E") + << QByteArray("To be decoded: \n\r!\"#$=@[\\]^`{|}~"); + + QTest::newRow("Characters recommended to be encoded according to RFC 1521(lower case)") + << QByteArray("To be decoded: =0a=0d=21=22=23=24=3d=40=5b=5c=5d=5e=60=7b=7c=7d=7e") + << QByteArray("To be decoded: \n\r!\"#$=@[\\]^`{|}~"); + + QTest::newRow("random characters encoded") + << QByteArray("=45=6E=63=6F=64=65=64 =64=61=74=61") + << QByteArray("Encoded data"); + + QTest::newRow("short string1") + << QByteArray("-=_") + << QByteArray("-=_"); + + QTest::newRow("short string2") + << QByteArray("=0") + << QByteArray("=0"); + + QTest::newRow("short string2") + << QByteArray("\r") + << QByteArray("\r"); + + QTest::newRow("short string2") + << QByteArray("\n") + << QByteArray("\n"); + + QTest::newRow("short string2") + << QByteArray("\n\r") + << QByteArray("\n\r"); + + QTest::newRow("White spaces") + << QByteArray("=09=20") + << QByteArray("\t "); +#endif +} +void tst_QVersitReader::testParamName() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + // Empty value + QByteArray param; + QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec),QString()); + + // Only value present + param = "WORK"; + QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec), + QString::fromAscii("TYPE")); + + // The below tests intentionally use the misspelling TIPE to avoid the default behaviour of + // returning TYPE when the name can't be parsed. + // Both name and value, spaces after the name + param = "TIPE \t =WORK"; + QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec), + QString::fromAscii("TIPE")); + + // Both name and value, no spaces after the name + param = "TIPE=WORK"; + QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec), + QString::fromAscii("TIPE")); + + // Test wide character support. + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + param = codec->fromUnicode(QString::fromAscii("TIPE=WORK")); + QCOMPARE(mReaderPrivate->paramName(param, codec), + QString::fromAscii("TIPE")); +#endif +} + +void tst_QVersitReader::testParamValue() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + // Empty value + QByteArray param; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString()); + + // Only value present + param = "WORK"; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec), + QString::fromAscii("WORK")); + + // Name and equals sign, but no value + param = "TYPE="; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString()); + + // Name and equals sign, but value has only spaces + param = "TYPE= \t "; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString()); + + // Both name and value, spaces before the value + param = "TYPE= \t WORK"; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec), + QString::fromAscii("WORK")); + + // Both name and value, no spaces before the value + param = "ENCODING=QUOTED-PRINTABLE"; + QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec), + QString::fromAscii("QUOTED-PRINTABLE")); + + // Test wide character support. + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + param = codec->fromUnicode(QString::fromAscii("TYPE=WORK")); + QCOMPARE(mReaderPrivate->paramValue(param, codec), + QString::fromAscii("WORK")); +#endif +} + +void tst_QVersitReader::testExtractPart() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QByteArray originalStr; + + // Negative starting position + QCOMPARE(mReaderPrivate->extractPart(originalStr,-1,1), QByteArray()); + + // Empty original string + QCOMPARE(mReaderPrivate->extractPart(originalStr,0,1), QByteArray()); + + // Trimmed substring empty + originalStr = " \t \t"; + QCOMPARE(mReaderPrivate->extractPart(originalStr,0,4), QByteArray()); + + // The given substring length is greater than the original string length + originalStr = "ENCODING=7BIT"; + QCOMPARE(mReaderPrivate->extractPart(originalStr,0,100), originalStr); + + // Non-empty substring, from the beginning + originalStr = " TYPE=WORK ; X-PARAM=X-VALUE; ENCODING=8BIT"; + QCOMPARE(mReaderPrivate->extractPart(originalStr,0,11), + QByteArray("TYPE=WORK")); + + // Non-empty substring, from the middle + QCOMPARE(mReaderPrivate->extractPart(originalStr,12,16), + QByteArray("X-PARAM=X-VALUE")); + + // Non-empty substring, from the middle to the end + QCOMPARE(mReaderPrivate->extractPart(originalStr,29), + QByteArray("ENCODING=8BIT")); +#endif +} + +void tst_QVersitReader::testExtractParts() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QList parts; + + // Empty value + QByteArray text; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QVERIFY(parts.isEmpty()); + + // Only separator + text = ";"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QVERIFY(parts.isEmpty()); + + // One part + text = "part"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),1); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("part")); + + // Separator in the beginning, one part + text = ";part"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),1); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("part")); + + // Separator in the end, one part + text = "part;"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),1); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("part")); + + // One part that contains escaped separator + text = "part\\;"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),1); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("part\\;")); + + // Two parts + text = "part1;part2"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),2); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("part1")); + QCOMPARE(QString::fromAscii(parts[1]),QString::fromAscii("part2")); + + // Two parts that contain escaped separators + text = "pa\\;rt1;par\\;t2"; + parts = mReaderPrivate->extractParts(text,";", mAsciiCodec); + QCOMPARE(parts.count(),2); + QCOMPARE(QString::fromAscii(parts[0]),QString::fromAscii("pa\\;rt1")); + QCOMPARE(QString::fromAscii(parts[1]),QString::fromAscii("par\\;t2")); + + // Test wide character support + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + text = codec->fromUnicode(QString::fromAscii("part1;part2")); + parts = mReaderPrivate->extractParts(text,";", codec); + QCOMPARE(parts.count(),2); + QCOMPARE(codec->toUnicode(parts[0]),QString::fromAscii("part1")); + QCOMPARE(codec->toUnicode(parts[1]),QString::fromAscii("part2")); +#endif +} + +void tst_QVersitReader::testExtractPropertyGroupsAndName() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QPair groupsAndName; + + // Empty string + VersitCursor cursor(QByteArray(" ")); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString()); + + // No value -> returns empty string and no groups + QByteArray property("TEL"); + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString()); + + // Simple name and value + property = "TEL:123"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + + // One whitespace before colon + property = "TEL :123"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + + // Several whitespaces before colon + property = "TEL \t :123"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + + // Name contains a group + property = "group1.TEL:1234"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),1); + QCOMPARE(groupsAndName.first.takeFirst(),QString::fromAscii("group1")); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + + // Name contains more than one group + property = "group1.group2.TEL:12345"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),2); + QCOMPARE(groupsAndName.first.takeFirst(),QString::fromAscii("group1")); + QCOMPARE(groupsAndName.first.takeFirst(),QString::fromAscii("group2")); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + QCOMPARE(cursor.position, 17); + + // Property contains one parameter + property = "TEL;WORK:123"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + + // Property contains several parameters + property = "EMAIL;INTERNET;ENCODING=QUOTED-PRINTABLE:user=40ovi.com"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("EMAIL")); + + // Name contains an escaped semicolon + property = "X-proper\\;ty:value"; + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, mAsciiCodec); + QCOMPARE(groupsAndName.first.count(),0); + QCOMPARE(groupsAndName.second,QString::fromAscii("X-proper\\;ty")); + + // Test wide character support + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + property = codec->fromUnicode(QString::fromAscii("group1.group2.TEL;WORK:123")); + cursor.setData(property); + cursor.selection = property.size(); + groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(cursor, codec); + QCOMPARE(groupsAndName.first.count(),2); + QCOMPARE(groupsAndName.first.takeFirst(),QString::fromAscii("group1")); + QCOMPARE(groupsAndName.first.takeFirst(),QString::fromAscii("group2")); + QCOMPARE(groupsAndName.second,QString::fromAscii("TEL")); + QCOMPARE(cursor.position, 36); // 2 bytes * 17 characters + 2 byte BOM. +#endif +} + +void tst_QVersitReader::testExtractVCard21PropertyParams() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + // No parameters + VersitCursor cursor(QByteArray(":123")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec).count(), 0); + + // "Empty" parameter + cursor.setData(QByteArray(";:123")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec).count(), 0); + + // Semicolon found, but no value for the property + cursor.setData(QByteArray(";TYPE=X-TYPE")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec).count(), 0); + + // The property name contains an escaped semicolon, no parameters + cursor.setData(QByteArray(":value")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec).count(), 0); + + // The property value contains a semicolon, no parameters + cursor.setData(QByteArray(":va;lue")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec).count(), 0); + + // One parameter + cursor.setData(QByteArray(";HOME:123")); + cursor.setSelection(cursor.data.size()); + QMultiHash params = mReaderPrivate->extractVCard21PropertyParams(cursor, + mAsciiCodec); + QCOMPARE(1, params.count()); + QCOMPARE(1, params.values(QString::fromAscii("TYPE")).count()); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[0],QString::fromAscii("HOME")); + + // Two parameters of the same type + cursor.setData(QByteArray(";HOME;VOICE:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec); + QCOMPARE(2, params.count()); + QCOMPARE(2, params.values(QString::fromAscii("TYPE")).count()); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[0],QString::fromAscii("HOME")); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[1],QString::fromAscii("VOICE")); + + // Two parameters, several empty parameters (extra semicolons) + cursor.setData(QByteArray(";;;;HOME;;;;;VOICE;;;:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec); + QCOMPARE(2, params.count()); + QCOMPARE(2, params.values(QString::fromAscii("TYPE")).count()); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[0],QString::fromAscii("HOME")); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[1],QString::fromAscii("VOICE")); + + // Two parameters with different types + cursor.setData(QByteArray(";INTERNET;ENCODING=QUOTED-PRINTABLE:user=40ovi.com")); + cursor.setSelection(cursor.data.size()); + params.clear(); + params = mReaderPrivate->extractVCard21PropertyParams(cursor, mAsciiCodec); + QCOMPARE(2, params.count()); + QList typeParams = params.values(QString::fromAscii("TYPE")); + QCOMPARE(1, typeParams.count()); + QCOMPARE(typeParams[0],QString::fromAscii("INTERNET")); + QList encodingParams = params.values(QString::fromAscii("ENCODING")); + QCOMPARE(1, encodingParams.count()); + QCOMPARE(encodingParams[0],QString::fromAscii("QUOTED-PRINTABLE")); + + // Test wide character support. + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + QByteArray data = VersitUtils::encode(";HOME;CHARSET=UTF-16:123", codec); + cursor.setData(data); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard21PropertyParams(cursor, codec); + QCOMPARE(2, params.count()); + typeParams = params.values(QString::fromAscii("TYPE")); + QCOMPARE(1, typeParams.count()); + QCOMPARE(typeParams[0],QString::fromAscii("HOME")); + encodingParams = params.values(QString::fromAscii("CHARSET")); + QCOMPARE(1, encodingParams.count()); + QCOMPARE(encodingParams[0],QString::fromAscii("UTF-16")); +#endif +} + +void tst_QVersitReader::testExtractVCard30PropertyParams() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + // No parameters + VersitCursor cursor(QByteArray(":123")); + cursor.setSelection(cursor.data.size()); + QCOMPARE(mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec).count(), 0); + + // One parameter + cursor.setData(QByteArray(";TYPE=HOME:123")); + cursor.setSelection(cursor.data.size()); + QMultiHash params = mReaderPrivate->extractVCard30PropertyParams(cursor, + mAsciiCodec); + QCOMPARE(params.count(), 1); + QCOMPARE(params.values(QString::fromAscii("TYPE")).count(), 1); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[0], QString::fromAscii("HOME")); + + // One parameter with an escaped semicolon + cursor.setData(QByteArray(";para\\;meter:value")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 1); + QCOMPARE(params.values(QString::fromAscii("TYPE")).count(), 1); + QCOMPARE(params.values(QString::fromAscii("TYPE"))[0], QString::fromAscii("para;meter")); + + // One parameter with and escaped comma in the name and the value + cursor.setData(QByteArray(";X-PA\\,RAM=VAL\\,UE:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 1); + QCOMPARE(params.values(QString::fromAscii("X-PA,RAM")).count(), 1); + QCOMPARE(params.values(QString::fromAscii("X-PA,RAM"))[0], QString::fromAscii("VAL,UE")); + + // Two parameters of the same type + cursor.setData(QByteArray(";TYPE=HOME,VOICE:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 2); + QCOMPARE(params.values(QString::fromAscii("TYPE")).count(), 2); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("HOME"))); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("VOICE"))); + + // Two parameters of the same type in separate name-values + cursor.setData(QByteArray(";TYPE=HOME;TYPE=VOICE:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 2); + QCOMPARE(params.values(QString::fromAscii("TYPE")).count(), 2); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("HOME"))); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("VOICE"))); + + // Three parameters of the same type + cursor.setData(QByteArray(";TYPE=PREF,HOME,VOICE:123")); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 3); + QCOMPARE(params.values(QString::fromAscii("TYPE")).count(), 3); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("PREF"))); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("HOME"))); + QVERIFY(params.values(QString::fromAscii("TYPE")).contains(QString::fromAscii("VOICE"))); + + // Two parameters with different types + cursor.setData(QByteArray(";TYPE=HOME;X-PARAM=X-VALUE:Home Street 1")); + cursor.setSelection(cursor.data.size()); + params.clear(); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, mAsciiCodec); + QCOMPARE(params.count(), 2); + QList typeParams = params.values(QString::fromAscii("TYPE")); + QCOMPARE(typeParams.count(), 1); + QCOMPARE(typeParams[0],QString::fromAscii("HOME")); + QList encodingParams = params.values(QString::fromAscii("X-PARAM")); + QCOMPARE(encodingParams.count(), 1); + QCOMPARE(encodingParams[0],QString::fromAscii("X-VALUE")); + + // Test wide character support. + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + QByteArray data = VersitUtils::encode(";TIPE=HOME,VOICE;CHARSET=UTF-16:123", codec); + cursor.setData(data); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractVCard30PropertyParams(cursor, codec); + QCOMPARE(params.count(), 3); + typeParams = params.values(QString::fromAscii("TIPE")); + QCOMPARE(params.values(QString::fromAscii("TIPE")).count(), 2); + QVERIFY(params.values(QString::fromAscii("TIPE")).contains(QString::fromAscii("HOME"))); + QVERIFY(params.values(QString::fromAscii("TIPE")).contains(QString::fromAscii("VOICE"))); + encodingParams = params.values(QString::fromAscii("CHARSET")); + QCOMPARE(1, encodingParams.count()); + QCOMPARE(encodingParams[0],QString::fromAscii("UTF-16")); +#endif +} + +void tst_QVersitReader::testExtractParams() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + VersitCursor cursor; + QByteArray data = ":123"; + cursor.setData(data); + cursor.setPosition(0); + cursor.setSelection(cursor.data.size()); + QList params = mReaderPrivate->extractParams(cursor, mAsciiCodec); + QCOMPARE(params.size(), 0); + QCOMPARE(cursor.position, 1); + + data = "a;b:123"; + cursor.setData(data); + cursor.setPosition(0); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractParams(cursor, mAsciiCodec); + QCOMPARE(params.size(), 2); + QCOMPARE(cursor.position, 4); + QCOMPARE(params.at(0), QByteArray("a")); + QCOMPARE(params.at(1), QByteArray("b")); + + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + data = VersitUtils::encode(":123", codec); + cursor.setData(data); + cursor.setPosition(0); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractParams(cursor, codec); + QCOMPARE(params.size(), 0); + QCOMPARE(cursor.position, 2); + + data = VersitUtils::encode("a;b:123", codec); + cursor.setData(data); + cursor.setPosition(0); + cursor.setSelection(cursor.data.size()); + params = mReaderPrivate->extractParams(cursor, codec); + QCOMPARE(params.size(), 2); + QCOMPARE(cursor.position, 8); +#endif +} + +Q_DECLARE_METATYPE(QList) + +void tst_QVersitReader::testReadLine() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + QFETCH(QByteArray, codecName); + QFETCH(QString, data); + QFETCH(QList, expectedLines); + + QTextCodec* codec = QTextCodec::codecForName(codecName); + QTextEncoder* encoder = codec->makeEncoder(); + encoder->fromUnicode(QString()); + + QByteArray bytes(encoder->fromUnicode(data)); + + mInputDevice->close(); + mInputDevice->setData(bytes); + mInputDevice->open(QIODevice::ReadWrite); + + LineReader lineReader(mInputDevice, codec, 10); + + // Check that all expected lines are read. + foreach (QString expectedLine, expectedLines) { + QByteArray expectedBytes(encoder->fromUnicode(expectedLine)); + QVERIFY(!lineReader.atEnd()); + VersitCursor line = lineReader.readLine(); + QVERIFY(line.data.indexOf(expectedBytes) == line.position); + QCOMPARE(line.selection - line.position, expectedBytes.length()); + } + // And that there are no more lines + VersitCursor line = lineReader.readLine(); + QCOMPARE(line.selection, line.position); + QVERIFY(lineReader.atEnd()); + + delete encoder; +#endif +} + +void tst_QVersitReader::testReadLine_data() +{ +#ifdef QT_BUILD_INTERNAL + // Note: for this test, we set mLineReader to read 10 bytes at a time. Lines of multiples of + // 10 bytes are hence border cases. + QTest::addColumn("codecName"); + QTest::addColumn("data"); + QTest::addColumn >("expectedLines"); + + QList codecNames; + codecNames << "UTF-8" << "UTF-16"; + + foreach (QByteArray codecName, codecNames) { + QTest::newRow("empty " + codecName) + << codecName + << "" + << QList(); + + QTest::newRow("one line " + codecName) + << codecName + << "line" + << (QList() << QLatin1String("line")); + + QTest::newRow("one ten-byte line " + codecName) + << codecName + << "tenletters" + << (QList() << QLatin1String("tenletters")); + + QTest::newRow("one long line " + codecName) + << codecName + << "one line longer than ten characters" + << (QList() << QLatin1String("one line longer than ten characters")); + + QTest::newRow("one terminated line " + codecName) + << codecName + << "one line longer than ten characters\r\n" + << (QList() << QLatin1String("one line longer than ten characters")); + + QTest::newRow("two lines " + codecName) + << codecName + << "two\r\nlines" + << (QList() << QLatin1String("two") << QLatin1String("lines")); + + QTest::newRow("two terminated lines " + codecName) + << codecName + << "two\r\nlines\r\n" + << (QList() << QLatin1String("two") << QLatin1String("lines")); + + QTest::newRow("two long lines " + codecName) + << codecName + << "one line longer than ten characters\r\nanother line\r\n" + << (QList() << QLatin1String("one line longer than ten characters") << QLatin1String("another line")); + + QTest::newRow("two full lines " + codecName) + << codecName + << "tenletters\r\n8letters\r\n" + << (QList() << QLatin1String("tenletters") << QLatin1String("8letters")); + + QTest::newRow("a nine-byte line " + codecName) + << codecName + << "9 letters\r\nanother line\r\n" + << (QList() << QLatin1String("9 letters") << QLatin1String("another line")); + + QTest::newRow("a blank line " + codecName) + << codecName + << "one\r\n\r\ntwo\r\n" + << (QList() << QLatin1String("one") << QLatin1String("two")); + + QTest::newRow("folded lines " + codecName) + << codecName + << "folded\r\n line\r\nsecond line\r\n" + << (QList() << QLatin1String("folded line") << QLatin1String("second line")); + + QTest::newRow("multiply folded lines " + codecName) + << codecName + << "fo\r\n lded\r\n line\r\nseco\r\n\tnd l\r\n ine\r\n" + << (QList() << QLatin1String("folded line") << QLatin1String("second line")); + + QTest::newRow("fold hidden after a chunk " + codecName) + << codecName + << "8letters\r\n on one line\r\n" + << (QList() << QLatin1String("8letters on one line")); + + QTest::newRow("three mac lines " + codecName) + << codecName + << "one\rtwo\rthree\r" + << (QList() << QLatin1String("one") << QLatin1String("two") << QLatin1String("three")); + } +#endif +} + +void tst_QVersitReader::testByteArrayInput() +{ + delete mReader; + const QByteArray& oneDocument = + "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n"; + + mReader = new QVersitReader(oneDocument); + QVERIFY(mReader->device() == 0); + QVERIFY(mReader->startReading()); + QVERIFY(mReader->waitForFinished()); + QList results = mReader->results(); + QCOMPARE(mReader->state(), QVersitReader::FinishedState); + QCOMPARE(mReader->error(), QVersitReader::NoError); + QCOMPARE(results.count(),1); + QVersitDocument result = results.first(); + QCOMPARE(result.type(), QVersitDocument::VCard21Type); + QList properties = result.properties(); + QCOMPARE(properties.length(), 1); + QCOMPARE(properties.first().name(), QLatin1String("FN")); + QCOMPARE(properties.first().value(), QLatin1String("John")); +} + +void tst_QVersitReader::testRemoveBackSlashEscaping() +{ +#ifndef QT_BUILD_INTERNAL + QSKIP("Testing private API", SkipSingle); +#else + // Empty string + QString input; + QVersitReaderPrivate::removeBackSlashEscaping(input); + QCOMPARE(input,QString()); + + // Nothing to escape in the string + input = QString::fromAscii("Nothing to escape"); + QVersitReaderPrivate::removeBackSlashEscaping(input); + QCOMPARE(input,QString::fromAscii("Nothing to escape")); + + // Line break, semicolon, backslash and comma in the string + input = QString::fromAscii("These should be unescaped \\n \\N \\; \\, \\\\"); + QVersitReaderPrivate::removeBackSlashEscaping(input); + QCOMPARE(input, QString::fromAscii("These should be unescaped \r\n \r\n ; , \\")); + + // Don't remove escaping within quotes + input = QString::fromAscii("\"Quoted \\n \\N \\; \\,\""); + QVersitReaderPrivate::removeBackSlashEscaping(input); + QCOMPARE(input, QString::fromAscii("\"Quoted \\n \\N \\; \\,\"")); +#endif +} + +QTEST_MAIN(tst_QVersitReader) +