1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34//TESTED_COMPONENT=src/versit
35
36#include "tst_qversitreader.h"
37#include <QtVersit/qversitproperty.h>
38#include <QtVersit/private/qversitreader_p.h>
39#include <QtVersit/private/qversitutils_p.h>
40#include <QtTest/QtTest>
41#include <QSignalSpy>
42
43// This says "NOKIA" in Katakana encoded with UTF-8
44const QByteArray KATAKANA_NOKIA("\xe3\x83\x8e\xe3\x82\xad\xe3\x82\xa2");
45
46const static QByteArray SAMPLE_GIF_BASE64(QByteArray(
47 "R0lGODlhEgASAIAAAAAAAP///yH5BAEAAAEALAAAAAASABIAAAIdjI+py+0G"
48 "wEtxUmlPzRDnzYGfN3KBaKGT6rDmGxQAOw=="));
49
50const static QByteArray SAMPLE_GIF(QByteArray::fromBase64(base64: SAMPLE_GIF_BASE64));
51
52Q_DECLARE_METATYPE(QTVERSIT_PREPEND_NAMESPACE(QVersitDocument::VersitType))
53Q_DECLARE_METATYPE(QTVERSIT_PREPEND_NAMESPACE(QVersitProperty))
54
55QTVERSIT_USE_NAMESPACE
56
57void tst_QVersitReader::init()
58{
59 mInputDevice = new QBuffer;
60 mInputDevice->open(openMode: QBuffer::ReadWrite);
61 mReader = new QVersitReader;
62#ifdef QT_BUILD_INTERNAL
63 mReaderPrivate = new QVersitReaderPrivate;
64#endif
65 mSignalCatcher = new SignalCatcher;
66 connect(sender: mReader, SIGNAL(stateChanged(QVersitReader::State)),
67 receiver: mSignalCatcher, SLOT(stateChanged(QVersitReader::State)));
68 connect(sender: mReader, SIGNAL(resultsAvailable()),
69 receiver: mSignalCatcher, SLOT(resultsAvailable()));
70 mAsciiCodec = QTextCodec::codecForName(name: "ISO 8859-1");
71}
72
73void tst_QVersitReader::cleanup()
74{
75#ifdef QT_BUILD_INTERNAL
76 delete mReaderPrivate;
77#endif
78 delete mReader;
79 delete mInputDevice;
80 delete mSignalCatcher;
81}
82
83void tst_QVersitReader::testDevice()
84{
85 // No device
86 QVERIFY(mReader->device() == NULL);
87
88 // Device has been set
89 mReader->setDevice(mInputDevice);
90 QVERIFY(mReader->device() == mInputDevice);
91
92 delete mInputDevice;
93 QVERIFY(mReader->device() == NULL);
94
95 mInputDevice = new QBuffer;
96 mInputDevice->open(openMode: QBuffer::ReadWrite);
97
98 QVERIFY(mReader->device() == NULL);
99 mReader->setDevice(mInputDevice);
100 QVERIFY(mReader->device() == mInputDevice);
101}
102
103void tst_QVersitReader::testNullDevice()
104{
105 QVersitReader vr;
106 QVERIFY(vr.device() == NULL);
107 QVERIFY(vr.startReading() == false);
108 QVERIFY(vr.error() == QVersitReader::IOError);
109
110 vr.setDevice(NULL);
111 QVERIFY(vr.device() == NULL);
112 QVERIFY(vr.startReading() == false);
113 QVERIFY(vr.error() == QVersitReader::IOError);
114
115 QFile f("does not exist or else");
116 vr.setDevice(&f);
117 QVERIFY(vr.device() == &f);
118 QVERIFY(vr.startReading() == false);
119 QVERIFY(vr.error() == QVersitReader::IOError);
120
121}
122
123void tst_QVersitReader::testDefaultCodec()
124{
125 QVERIFY(mReader->defaultCodec() == 0);
126 mReader->setDefaultCodec(QTextCodec::codecForName(name: "UTF-16BE"));
127 QVERIFY(mReader->defaultCodec() == QTextCodec::codecForName("UTF-16BE"));
128}
129
130void tst_QVersitReader::testValidateUtf8()
131{
132 QFETCH(QByteArray, bytes);
133 QFETCH(bool, isValid);
134 QCOMPARE(VersitUtils::isValidUtf8(bytes), isValid);
135}
136
137void tst_QVersitReader::testValidateUtf8_data()
138{
139 // These test cases are taken from
140 // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
141 // See that page for a description of what they test
142 QTest::addColumn<QByteArray>(name: "bytes");
143 QTest::addColumn<bool>(name: "isValid");
144 // The first 18 are marked as "valid" according to the above page
145 QTest::newRow(dataTag: "1") << QByteArray("\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5") << true;
146 QTest::newRow(dataTag: "2") << QByteArray("\x00") << true;
147 QTest::newRow(dataTag: "3") << QByteArray("\xc2\x80") << true;
148 QTest::newRow(dataTag: "4") << QByteArray("\xe0\xa0\x80") << true;
149 QTest::newRow(dataTag: "5") << QByteArray("\xf0\x90\x80\x80") << true;
150 // We treat 5 and 6 byte characters as invalid as per RFC3629
151 QTest::newRow(dataTag: "6") << QByteArray("\xf8\x88\x80\x80\x80") << false;
152 QTest::newRow(dataTag: "7") << QByteArray("\xfc\x84\x80\x80\x80\x80") << false;
153 QTest::newRow(dataTag: "8") << QByteArray("\x7f") << true;
154 QTest::newRow(dataTag: "9") << QByteArray("\xdf\xbf") << true;
155 QTest::newRow(dataTag: "10") << QByteArray("\xef\xbf\xbd") << true;
156 QTest::newRow(dataTag: "11") << QByteArray("\xf4\x8f\xbf\xbf") << true;
157 // We treat 5 and 6 byte characters as invalid as per RFC3629
158 QTest::newRow(dataTag: "12") << QByteArray("\xfb\xbf\xbf\xbf\xbf") << false;
159 QTest::newRow(dataTag: "13") << QByteArray("\xfd\xbf\xbf\xbf\xbf\xbf") << false;
160 QTest::newRow(dataTag: "14") << QByteArray("\xed\x9f\xbf") << true;
161 QTest::newRow(dataTag: "15") << QByteArray("\xee\x80\x80") << true;
162 QTest::newRow(dataTag: "16") << QByteArray("\xef\xbf\xbd") << true;
163 QTest::newRow(dataTag: "17") << QByteArray("\xf4\x8f\xbf\xbf") << true;
164 QTest::newRow(dataTag: "18") << QByteArray("\xf4\x90\x80\x80") << false; // outside the range
165
166 // The rest are marked as "invalid" according to the above page
167 QTest::newRow(dataTag: "19") << QByteArray("\x80") << false;
168 QTest::newRow(dataTag: "20") << QByteArray("\xbf") << false;
169 QTest::newRow(dataTag: "21") << QByteArray("\x80\xbf") << false;
170 QTest::newRow(dataTag: "22") << QByteArray("\x80\xbf\x80") << false;
171 QTest::newRow(dataTag: "23") << QByteArray("\x80\xbf\x80\xbf") << false;
172 QTest::newRow(dataTag: "24") << QByteArray("\x80\xbf\x80\xbf\x80") << false;
173 QTest::newRow(dataTag: "25") << QByteArray("\x80\xbf\x80\xbf\x80\xbf") << false;
174 QTest::newRow(dataTag: "26") << QByteArray("\x80\xbf\x80\xbf\x80\xbf\x80") << false;
175 QTest::newRow(dataTag: "27") << QByteArray("\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d"
176 "\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1"
177 "\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
178 "\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf") << false;
179 QTest::newRow(dataTag: "28") << QByteArray("\xc0\x20\xc1\x20\xc2\x20\xc3\x20\xc4\x20\xc5\x20\xc6\x20"
180 "\xc7\x20\xc8\x20\xc9\x20\xca\x20\xcb\x20\xcc\x20\xcd\x20\xce\x20\xcf\x20\xd0\x20"
181 "\xd1\x20\xd2\x20\xd3\x20\xd4\x20\xd5\x20\xd6\x20\xd7\x20\xd8\x20\xd9\x20\xda\x20"
182 "\xdb\x20\xdc\x20\xdd\x20\xde\x20\xdf\x20") << false;
183 QTest::newRow(dataTag: "29") << QByteArray("\xe0\x20\xe1\x20\xe2\x20\xe3\x20\xe4\x20\xe5\x20\xe6\x20"
184 "\xe7\x20\xe8\x20\xe9\x20\xea\x20\xeb\x20\xec\x20\xed\x20\xee\x20\xef\x20") << false;
185 QTest::newRow(dataTag: "30") << QByteArray("\xf0\x20\xf1\x20\xf2\x20\xf3\x20\xf4\x20\xf5\x20\xf6\x20"
186 "\xf7\x20") << false;
187 QTest::newRow(dataTag: "31") << QByteArray("\xf8\x20\xf9\x20\xfa\x20\xfb\x20") << false;
188 QTest::newRow(dataTag: "32") << QByteArray("\xfc\x20\xfd\x20") << false;
189 QTest::newRow(dataTag: "33") << QByteArray("\xc0") << false;
190 QTest::newRow(dataTag: "34") << QByteArray("\xe0\x80") << false;
191 QTest::newRow(dataTag: "35") << QByteArray("\xf0\x80\x80") << false;
192 QTest::newRow(dataTag: "36") << QByteArray("\xf8\x80\x80\x80") << false;
193 QTest::newRow(dataTag: "37") << QByteArray("\xfc\x80\x80\x80\x80") << false;
194 QTest::newRow(dataTag: "38") << QByteArray("\xdf") << false;
195 QTest::newRow(dataTag: "39") << QByteArray("\xef\xbf") << false;
196 QTest::newRow(dataTag: "40") << QByteArray("\xf7\xbf\xbf") << false;
197 QTest::newRow(dataTag: "41") << QByteArray("\xfb\xbf\xbf\xbf") << false;
198 QTest::newRow(dataTag: "42") << QByteArray("\xfd\xbf\xbf\xbf\xbf") << false;
199 QTest::newRow(dataTag: "43") << QByteArray("\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80"
200 "\x80\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf") << false;
201 QTest::newRow(dataTag: "44") << QByteArray("\xfe") << false;
202 QTest::newRow(dataTag: "45") << QByteArray("\xff") << false;
203 QTest::newRow(dataTag: "46") << QByteArray("\xfe\xfe\xff\xff") << false;
204 QTest::newRow(dataTag: "47") << QByteArray("\xc0\xaf") << false;
205 QTest::newRow(dataTag: "48") << QByteArray("\xe0\x80\xaf") << false;
206 QTest::newRow(dataTag: "49") << QByteArray("\xf0\x80\x80\xaf") << false;
207 QTest::newRow(dataTag: "50") << QByteArray("\xf8\x80\x80\x80\xaf") << false;
208 QTest::newRow(dataTag: "51") << QByteArray("\xfc\x80\x80\x80\x80\xaf") << false;
209 QTest::newRow(dataTag: "52") << QByteArray("\xc1\xbf") << false;
210 QTest::newRow(dataTag: "53") << QByteArray("\xe0\x9f\xbf") << false;
211 QTest::newRow(dataTag: "54") << QByteArray("\xf0\x8f\xbf\xbf") << false;
212 QTest::newRow(dataTag: "55") << QByteArray("\xf8\x87\xbf\xbf\xbf") << false;
213 QTest::newRow(dataTag: "56") << QByteArray("\xfc\x83\xbf\xbf\xbf\xbf") << false;
214 QTest::newRow(dataTag: "57") << QByteArray("\xc0\x80") << false;
215 QTest::newRow(dataTag: "58") << QByteArray("\xe0\x80\x80") << false;
216 QTest::newRow(dataTag: "59") << QByteArray("\xf0\x80\x80\x80") << false;
217 QTest::newRow(dataTag: "60") << QByteArray("\xf8\x80\x80\x80\x80") << false;
218 QTest::newRow(dataTag: "61") << QByteArray("\xfc\x80\x80\x80\x80\x80") << false;
219 QTest::newRow(dataTag: "62") << QByteArray("\xed\xa0\x80") << false;
220 QTest::newRow(dataTag: "63") << QByteArray("\xed\xad\xbf") << false;
221 QTest::newRow(dataTag: "64") << QByteArray("\xed\xae\x80") << false;
222 QTest::newRow(dataTag: "65") << QByteArray("\xed\xaf\xbf") << false;
223 QTest::newRow(dataTag: "66") << QByteArray("\xed\xb0\x80") << false;
224 QTest::newRow(dataTag: "67") << QByteArray("\xed\xbe\x80") << false;
225 QTest::newRow(dataTag: "68") << QByteArray("\xed\xbf\xbf") << false;
226 QTest::newRow(dataTag: "69") << QByteArray("\xed\xa0\x80\xed\xb0\x80") << false;
227 QTest::newRow(dataTag: "70") << QByteArray("\xed\xa0\x80\xed\xbf\xbf") << false;
228 QTest::newRow(dataTag: "71") << QByteArray("\xed\xad\xbf\xed\xb0\x80") << false;
229 QTest::newRow(dataTag: "72") << QByteArray("\xed\xad\xbf\xed\xbf\xbf") << false;
230 QTest::newRow(dataTag: "73") << QByteArray("\xed\xae\x80\xed\xb0\x80") << false;
231 QTest::newRow(dataTag: "74") << QByteArray("\xed\xae\x80\xed\xbf\xbf") << false;
232 QTest::newRow(dataTag: "75") << QByteArray("\xed\xaf\xbf\xed\xb0\x80") << false;
233 QTest::newRow(dataTag: "76") << QByteArray("\xed\xaf\xbf\xed\xbf\xbf") << false;
234 QTest::newRow(dataTag: "77") << QByteArray("\xef\xbf\xbe") << false;
235 QTest::newRow(dataTag: "78") << QByteArray("\xef\xbf\xbf") << false;
236
237 // My own tests
238 // 0x110000 is the first one outside the Unicode range
239 QTest::newRow(dataTag: "79") << QByteArray("\xf4\x90\x80\x80") << false;
240 // a 3 byte sequence followed by a single byte
241 QTest::newRow(dataTag: "80") << QByteArray("\xef\xbf\xbd\x20") << true;
242 // a 4 byte sequence followed by a single byte
243 QTest::newRow(dataTag: "81") << QByteArray("\xf4\x8f\xbf\xbf\x20") << true;
244}
245
246void tst_QVersitReader::testDetectCodec()
247{
248 QFETCH(QByteArray, bytes);
249 QFETCH(QString, expectedFnValue);
250
251 QTextCodec::setCodecForLocale(QTextCodec::codecForName(name: "ISO 8859-1"));
252 mInputDevice->close();
253 mInputDevice->setData(bytes);
254 mInputDevice->open(openMode: QBuffer::ReadOnly);
255 mInputDevice->seek(off: 0);
256 mReader->setDevice(mInputDevice);
257 QVERIFY(mReader->defaultCodec() == 0);
258 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
259 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
260 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
261 QCOMPARE(mReader->error(), QVersitReader::NoError);
262 QVERIFY(mReader->defaultCodec() == 0); // shouldn't change
263 QList<QVersitDocument> results = mReader->results();
264 QCOMPARE(results.count(),1);
265 QVersitDocument document = results.first();
266 QCOMPARE(document.properties().size(), 1);
267 QVersitProperty property = document.properties().first();
268 QCOMPARE(property.name(), QStringLiteral("FN"));
269 QCOMPARE(property.value(), expectedFnValue);
270}
271
272void tst_QVersitReader::testDetectCodec_data()
273{
274 QTest::addColumn<QByteArray>(name: "bytes");
275 QTest::addColumn<QString>(name: "expectedFnValue");
276
277 const QString& documentString =
278 QStringLiteral("BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n");
279 {
280 const QByteArray& document =
281 "\xef\xbb\xbf" + documentString.toUtf8();
282 QTest::newRow(dataTag: "UTF-8 with BOM") << document << QString::fromLatin1(str: "John");
283 }
284 {
285 const QByteArray& document =
286 QTextCodec::codecForName(name: "UTF-16BE")->fromUnicode(uc: documentString);
287 QTest::newRow(dataTag: "UTF-16BE with BOM") << document << QString::fromLatin1(str: "John");
288 }
289 {
290 const QByteArray& document =
291 QTextCodec::codecForName(name: "UTF-16LE")->fromUnicode(uc: documentString);
292 QTest::newRow(dataTag: "UTF-16LE with BOM") << document << QString::fromLatin1(str: "John");
293 }
294 {
295 const QByteArray& document =
296 VersitUtils::encode(ba: documentString.toLatin1(), codec: QTextCodec::codecForName(name: "UTF-16BE"));
297 QTest::newRow(dataTag: "UTF-16BE without BOM") << document << QString::fromLatin1(str: "John");
298 }
299 {
300 const QByteArray& document =
301 VersitUtils::encode(ba: documentString.toLatin1(), codec: QTextCodec::codecForName(name: "UTF-16LE"));
302 QTest::newRow(dataTag: "UTF-16LE without BOM") << document << QString::fromLatin1(str: "John");
303 }
304 {
305 const QByteArray& document =
306 QTextCodec::codecForName(name: "UTF-32BE")->fromUnicode(uc: documentString);
307 QTest::newRow(dataTag: "UTF-32BE with BOM") << document << QString::fromLatin1(str: "John");
308 }
309 {
310 const QByteArray& document =
311 QTextCodec::codecForName(name: "UTF-32LE")->fromUnicode(uc: documentString);
312 QTest::newRow(dataTag: "UTF-32LE with BOM") << document << QString::fromLatin1(str: "John");
313 }
314 {
315 const QByteArray& document = documentString.toUtf8();
316 QTest::newRow(dataTag: "Plain ASCII") << document << QString::fromLatin1(str: "John");
317 }
318 {
319 const QByteArray& document = "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:"
320 + KATAKANA_NOKIA
321 + "\r\nEND:VCARD\r\n";
322 QTest::newRow(dataTag: "Non-ASCII UTF-8") << document << QString::fromUtf8(str: KATAKANA_NOKIA);
323 }
324 {
325 // some Scandinavian characters, note that "\xe4\xe4" is invalid UTF-8, as is "\xf6n"
326 const QByteArray& document =
327 "BEGIN:VCARD\r\nVERSION:2.1\r\n"
328 "FN:P\xe4\xe4kk\xf6nen\r\n"
329 "END:VCARD\r\n";
330 QTest::newRow(dataTag: "Non-ASCII Latin-1") << document
331 << QString::fromLatin1(str: "P\xe4\xe4kk\xf6nen");
332 }
333 {
334 // as above, but quoted-printable
335 const QByteArray& document =
336 "BEGIN:VCARD\r\nVERSION:2.1\r\n"
337 "FN;ENCODING=QUOTED-PRINTABLE:P=E4=E4kk=F6nen\r\n"
338 "END:VCARD\r\n";
339 QTest::newRow(dataTag: "Non-ASCII Latin-1 QP") << document
340 << QString::fromLatin1(str: "P\xe4\xe4kk\xf6nen");
341 }
342}
343
344void tst_QVersitReader::testReading()
345{
346 // No I/O device set
347 QVERIFY(!mReader->startReading());
348 QCOMPARE(mReader->error(), QVersitReader::IOError);
349
350 // Device set, no data
351 mReader->setDevice(mInputDevice);
352 mInputDevice->open(openMode: QBuffer::ReadOnly);
353 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
354 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
355 QList<QVersitDocument> results(mReader->results());
356 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
357 QCOMPARE(mReader->error(), QVersitReader::NoError);
358 QCOMPARE(results.count(),0);
359
360 // Device set, one document
361 const QByteArray& oneDocument =
362 "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n";
363 mInputDevice->close();
364 mInputDevice->setData(oneDocument);
365 mInputDevice->open(openMode: QBuffer::ReadOnly);
366 mInputDevice->seek(off: 0);
367 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
368 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
369 results = mReader->results();
370 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
371 QCOMPARE(mReader->error(), QVersitReader::NoError);
372 QCOMPARE(results.count(),1);
373
374 // Device set, two documents concatenated in a malformed manner (no \r\n separation)
375 const QByteArray& twoMalformedDocument =
376 "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD"
377 "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:James\r\nEND:VCARD";
378 mInputDevice->close();
379 mInputDevice->setData(twoMalformedDocument);
380 mInputDevice->open(openMode: QBuffer::ReadOnly);
381 mInputDevice->seek(off: 0);
382 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
383 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
384 results = mReader->results();
385 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
386 QCOMPARE(mReader->error(), QVersitReader::NoError);
387 QCOMPARE(results.count(),2);
388
389 // Exception case for a property ending in =CrLfCrLf, ie "=\r\n\r\n"
390 const QByteArray& myTest =
391 "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John\r\n"
392 "EMAIL;ENCODING=QUOTED-PRINTABLE:john.citizen=40exam=\r\nple.com=abc=\r\n\r\n"
393 "END:VCARD\r\n";
394 mInputDevice->close();
395 mInputDevice->setData(myTest);
396 mInputDevice->open(openMode: QBuffer::ReadOnly);
397 mInputDevice->seek(off: 0);
398 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
399 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
400 results = mReader->results();
401 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
402 QCOMPARE(mReader->error(), QVersitReader::NoError);
403 QCOMPARE(results.count(),1);
404
405
406 // vCard 4.0
407 const QByteArray& vcard40 =
408 "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John\r\nEND:VCARD\r\n";
409 mInputDevice->close();
410 mInputDevice->setData(vcard40);
411 mInputDevice->open(openMode: QBuffer::ReadOnly);
412 mInputDevice->seek(off: 0);
413 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
414 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
415 results = mReader->results();
416 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
417 QCOMPARE(mReader->error(), QVersitReader::NoError);
418 QCOMPARE(results.count(),1);
419
420 // Wide charset with no byte-order mark
421 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
422 QTextCodec::ConverterState converterState(QTextCodec::IgnoreHeader);
423 QString document = QStringLiteral("BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n");
424 const QByteArray& wideDocument =
425 codec->fromUnicode(in: document.data(), length: document.length(), state: &converterState);
426 mInputDevice->close();
427 mInputDevice->setData(wideDocument);
428 mInputDevice->open(openMode: QBuffer::ReadOnly);
429 mInputDevice->seek(off: 0);
430 mReader->setDefaultCodec(codec);
431 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
432 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
433 results = mReader->results();
434 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
435 QCOMPARE(mReader->error(), QVersitReader::NoError);
436 QCOMPARE(mReader->results().count(),1);
437 mReader->setDefaultCodec(NULL);
438
439 // Two documents
440 const QByteArray& twoDocuments =
441 " \r\n BEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\nBEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n";
442 mInputDevice->close();
443 mInputDevice->setData(twoDocuments);
444 mInputDevice->open(openMode: QBuffer::ReadOnly);
445 mInputDevice->seek(off: 0);
446 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
447 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
448 results = mReader->results();
449 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
450 QCOMPARE(mReader->error(), QVersitReader::NoError);
451 QCOMPARE(results.count(),2);
452
453 // Valid documents and a grouped document between them
454 const QByteArray& validDocumentsAndGroupedDocument =
455"BEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\n"
456"BEGIN:VCARD\r\nX-GROUPING:pub gang\r\nBEGIN:VCARD\r\nFN:Jeremy\r\nEND:VCARD\r\nBEGIN:VCARD\r\nFN:Jeffery\r\nEND:VCARD\r\nEND:VCARD\r\n"
457"BEGIN:VCARD\r\nFN:Jake\r\nEND:VCARD\r\n"
458"BEGIN:VCARD\r\nFN:James\r\nEND:VCARD\r\n"
459"BEGIN:VCARD\r\nFN:Jane\r\nEND:VCARD\r\n";
460 mInputDevice->close();
461 mInputDevice->setData(validDocumentsAndGroupedDocument);
462 mInputDevice->open(openMode: QBuffer::ReadWrite);
463 mInputDevice->seek(off: 0);
464 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
465 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
466 results = mReader->results();
467 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
468 QCOMPARE(mReader->error(), QVersitReader::NoError);
469 QCOMPARE(results.count(),5);
470
471 qApp->processEvents(); // clean up before we start sniffing signals
472
473 // calling setData directly on reader
474 mReader->setData(validDocumentsAndGroupedDocument);
475 QVERIFY(mReader->startReading());
476 mReader->waitForFinished();
477 QCOMPARE(mReader->results().size(), 5);
478
479 // Asynchronous reading
480 mReader->setDevice(mInputDevice);
481 mInputDevice->close();
482 mInputDevice->setData(twoDocuments);
483 mInputDevice->open(openMode: QBuffer::ReadWrite);
484 mInputDevice->seek(off: 0);
485 mSignalCatcher->mStateChanges.clear();
486 mSignalCatcher->mResultsCount = 0;
487 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
488 QTRY_VERIFY(mSignalCatcher->mStateChanges.count() >= 2);
489 QCOMPARE(mSignalCatcher->mStateChanges.at(0), QVersitReader::ActiveState);
490 QCOMPARE(mSignalCatcher->mStateChanges.at(1), QVersitReader::FinishedState);
491 QVERIFY(mSignalCatcher->mResultsCount >= 2);
492 QCOMPARE(mReader->results().size(), 2);
493 QCOMPARE(mReader->error(), QVersitReader::NoError);
494
495 // Cancelling
496 mInputDevice->close();
497 mInputDevice->setData(twoDocuments);
498 mInputDevice->open(openMode: QBuffer::ReadOnly);
499 mInputDevice->seek(off: 0);
500 mSignalCatcher->mStateChanges.clear();
501 mSignalCatcher->mResultsCount = 0;
502 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
503 mReader->cancel();
504 mReader->waitForFinished();
505 QTRY_VERIFY(mSignalCatcher->mStateChanges.count() >= 2);
506 QCOMPARE(mSignalCatcher->mStateChanges.at(0), QVersitReader::ActiveState);
507 QVersitReader::State state(mSignalCatcher->mStateChanges.at(i: 1));
508 // It's possible that it finishes before it cancels.
509 QVERIFY(state == QVersitReader::CanceledState
510 || state == QVersitReader::FinishedState);
511}
512
513void tst_QVersitReader::testResult()
514{
515 QCOMPARE(mReader->results().count(),0);
516}
517
518void tst_QVersitReader::testParseNextVersitProperty()
519{
520#ifndef QT_BUILD_INTERNAL
521 QSKIP("Testing private API");
522#else
523 QFETCH(QVersitDocument::VersitType, documentType);
524 QFETCH(QByteArray, input);
525 QFETCH(QVersitProperty, expectedProperty);
526
527 QBuffer buffer(&input);
528 buffer.open(openMode: QIODevice::ReadOnly);
529 LineReader lineReader(&buffer, mAsciiCodec);
530 QVersitProperty property = mReaderPrivate->parseNextVersitProperty(versitType: documentType, lineReader: &lineReader);
531 if (property != expectedProperty) {
532 // compare each part of the property separately for easier debugging
533 QCOMPARE(property.groups(), expectedProperty.groups());
534 QCOMPARE(property.name(), expectedProperty.name());
535 QCOMPARE(property.valueType(), expectedProperty.valueType());
536
537 // QVariant doesn't support == on QVersitDocuments - do it manually
538 if (property.variantValue().userType() == qMetaTypeId<QVersitDocument>()) {
539 QVERIFY(expectedProperty.variantValue().userType() == qMetaTypeId<QVersitDocument>());
540 QCOMPARE(property.value<QVersitDocument>(), expectedProperty.value<QVersitDocument>());
541 }
542 else
543 QCOMPARE(property.variantValue(), expectedProperty.variantValue());
544
545 // Don't check parameters because the reader can add random parameters of its own (like CHARSET)
546 // QCOMPARE(property.parameters(), expectedProperty.parameters());
547 }
548#endif
549}
550
551void tst_QVersitReader::testParseNextVersitProperty_data()
552{
553#ifdef QT_BUILD_INTERNAL
554 QTest::addColumn<QVersitDocument::VersitType>(name: "documentType");
555 QTest::addColumn<QByteArray>(name: "input");
556 QTest::addColumn<QVersitProperty>(name: "expectedProperty");
557
558 {
559 QVersitProperty expectedProperty;
560 expectedProperty.setName(QStringLiteral("BEGIN"));
561 expectedProperty.setValue(QStringLiteral("vcard"));
562 QTest::newRow(dataTag: "begin")
563 << QVersitDocument::VCard21Type
564 << QByteArray("Begin:vcard\r\n")
565 << expectedProperty;
566 }
567
568 {
569 QVersitProperty expectedProperty;
570 expectedProperty.setName(QStringLiteral("VERSION"));
571 expectedProperty.setValue(QStringLiteral("2.1"));
572 expectedProperty.setValueType(QVersitProperty::PlainType);
573 QTest::newRow(dataTag: "version")
574 << QVersitDocument::VCard21Type
575 << QByteArray("VERSION:2.1\r\n")
576 << expectedProperty;
577 }
578
579 {
580 QVersitProperty expectedProperty;
581 expectedProperty.setName(QStringLiteral("FN"));
582 expectedProperty.setValue(QStringLiteral("John"));
583 expectedProperty.setValueType(QVersitProperty::PlainType);
584 QTest::newRow(dataTag: "fn")
585 << QVersitDocument::VCard21Type
586 << QByteArray("FN:John\r\n")
587 << expectedProperty;
588 }
589
590 {
591 // "NOTE:\;\,\:\\"
592 QVersitProperty expectedProperty;
593 expectedProperty.setName(QStringLiteral("NOTE"));
594 expectedProperty.setValue(QStringLiteral("\\;\\,\\:\\\\"));
595 expectedProperty.setValueType(QVersitProperty::PlainType);
596 QTest::newRow(dataTag: "vcard21 note")
597 << QVersitDocument::VCard21Type
598 << QByteArray("NOTE:\\;\\,\\:\\\\\r\n")
599 << expectedProperty;
600
601 expectedProperty.setValue(QStringLiteral(";,:\\"));
602 QTest::newRow(dataTag: "vcard30 note")
603 << QVersitDocument::VCard30Type
604 << QByteArray("NOTE:\\;\\,\\:\\\\\r\n")
605 << expectedProperty;
606 }
607
608 {
609 // "N:foo\;bar;foo\,bar;foo\:bar;foo\\bar;foo\\\;bar"
610 QVersitProperty expectedProperty;
611 expectedProperty.setName(QStringLiteral("N"));
612 QStringList components;
613 components << QStringLiteral("foo;bar")
614 << QStringLiteral("foo\\,bar")
615 << QStringLiteral("foo\\:bar")
616 << QStringLiteral("foo\\\\bar")
617 << QStringLiteral("foo\\\\;bar");
618 expectedProperty.setValue(components);
619 expectedProperty.setValueType(QVersitProperty::CompoundType);
620 QTest::newRow(dataTag: "vcard21 n")
621 << QVersitDocument::VCard21Type
622 << QByteArray("N:foo\\;bar;foo\\,bar;foo\\:bar;foo\\\\bar;foo\\\\\\;bar\r\n")
623 << expectedProperty;
624
625 components.clear();
626 components << QStringLiteral("foo;bar")
627 << QStringLiteral("foo,bar")
628 << QStringLiteral("foo:bar")
629 << QStringLiteral("foo\\bar")
630 << QStringLiteral("foo\\;bar");
631 expectedProperty.setValue(components);
632 QTest::newRow(dataTag: "vcard30 n")
633 << QVersitDocument::VCard30Type
634 << QByteArray("N:foo\\;bar;foo\\,bar;foo\\:bar;foo\\\\bar;foo\\\\\\;bar\r\n")
635 << expectedProperty;
636 }
637
638 {
639 QVersitProperty expectedProperty;
640 expectedProperty.setName(QStringLiteral("ADR"));
641 expectedProperty.setValue(QStringList(QString()));
642 expectedProperty.setValueType(QVersitProperty::CompoundType);
643 QTest::newRow(dataTag: "empty structured")
644 << QVersitDocument::VCard21Type
645 << QByteArray("ADR:\r\n")
646 << expectedProperty;
647 }
648
649 {
650 QVersitProperty expectedProperty;
651 expectedProperty.setName(QStringLiteral("X-QTPROJECT-FAVORITE"));
652 QStringList components;
653 components << QStringLiteral("false")
654 << QStringLiteral("10");
655 expectedProperty.setValue(components);
656 expectedProperty.setValueType(QVersitProperty::CompoundType);
657 QTest::newRow(dataTag: "vcard21 favorite")
658 << QVersitDocument::VCard21Type
659 << QByteArray("X-QTPROJECT-FAVORITE:false;10")
660 << expectedProperty;
661 }
662
663 {
664 QVersitProperty expectedProperty;
665 expectedProperty.setName(QStringLiteral("X-QTPROJECT-EXTENDED-DETAIL"));
666 QStringList components;
667 components << QStringLiteral("name")
668 << QStringLiteral("data");
669 expectedProperty.setValue(components);
670 expectedProperty.setValueType(QVersitProperty::CompoundType);
671 QTest::newRow(dataTag: "qtproject extended detail")
672 << QVersitDocument::VCard21Type
673 << QByteArray("X-QTPROJECT-EXTENDED-DETAIL:name;data")
674 << expectedProperty;
675 }
676
677 {
678 QVersitProperty expectedProperty;
679 expectedProperty.setName(QStringLiteral("X-CHILDREN"));
680 expectedProperty.setValue(QStringList() << QStringLiteral("Child1") << QStringLiteral("Child2"));
681 expectedProperty.setValueType(QVersitProperty::ListType);
682 QTest::newRow(dataTag: "children")
683 << QVersitDocument::VCard21Type
684 << QByteArray("X-CHILDREN:Child1,Child2\r\n")
685 << expectedProperty;
686 }
687
688 {
689 // "NICKNAME:foo\;bar,foo\,bar,foo\:bar,foo\\bar,foo\\\,bar"
690 QVersitProperty expectedProperty;
691 expectedProperty.setName(QStringLiteral("NICKNAME"));
692 QStringList components;
693 components << QStringLiteral("foo\\;bar")
694 << QStringLiteral("foo,bar")
695 << QStringLiteral("foo\\:bar")
696 << QStringLiteral("foo\\\\bar")
697 << QStringLiteral("foo\\\\,bar");
698 expectedProperty.setValue(components);
699 expectedProperty.setValueType(QVersitProperty::ListType);
700 QTest::newRow(dataTag: "vcard21 nickname")
701 << QVersitDocument::VCard21Type
702 << QByteArray("NICKNAME:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n")
703 << expectedProperty;
704
705 components.clear();
706 components << QStringLiteral("foo;bar")
707 << QStringLiteral("foo,bar")
708 << QStringLiteral("foo:bar")
709 << QStringLiteral("foo\\bar")
710 << QStringLiteral("foo\\,bar");
711 expectedProperty.setValue(components);
712 QTest::newRow(dataTag: "vcard30 nickname")
713 << QVersitDocument::VCard30Type
714 << QByteArray("NICKNAME:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n")
715 << expectedProperty;
716 }
717
718 {
719 // "CATEGORIES:foo\;bar,foo\,bar,foo\:bar,foo\\bar,foo\\\,bar"
720 QVersitProperty expectedProperty;
721 expectedProperty.setName(QStringLiteral("CATEGORIES"));
722 QStringList components;
723 components << QStringLiteral("foo\\;bar")
724 << QStringLiteral("foo,bar")
725 << QStringLiteral("foo\\:bar")
726 << QStringLiteral("foo\\\\bar")
727 << QStringLiteral("foo\\\\,bar");
728 expectedProperty.setValue(components);
729 expectedProperty.setValueType(QVersitProperty::ListType);
730 QTest::newRow(dataTag: "vcard21 categories")
731 << QVersitDocument::VCard21Type
732 << QByteArray("CATEGORIES:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n")
733 << expectedProperty;
734
735 components.clear();
736 components << QStringLiteral("foo;bar")
737 << QStringLiteral("foo,bar")
738 << QStringLiteral("foo:bar")
739 << QStringLiteral("foo\\bar")
740 << QStringLiteral("foo\\,bar");
741 expectedProperty.setValue(components);
742 QTest::newRow(dataTag: "vcard30 categories")
743 << QVersitDocument::VCard30Type
744 << QByteArray("CATEGORIES:foo\\;bar,foo\\,bar,foo\\:bar,foo\\\\bar,foo\\\\\\,bar\r\n")
745 << expectedProperty;
746
747 // "CATEGORIES:foobar\\,foobar\\\\,foo\\\\\,bar"
748 components.clear();
749 components << QStringLiteral("foobar\\")
750 << QStringLiteral("foobar\\\\")
751 << QStringLiteral("foo\\\\,bar");
752 expectedProperty.setValue(components);
753 QTest::newRow(dataTag: "vcard30 unescaping")
754 << QVersitDocument::VCard30Type
755 << QByteArray("CATEGORIES:foobar\\\\,foobar\\\\\\\\,foo\\\\\\\\\\,bar")
756 << expectedProperty;
757 }
758
759 {
760 QVersitProperty expectedProperty;
761 expectedProperty.setName(QStringLiteral("ORG"));
762 expectedProperty.setValue(QString::fromUtf8(str: KATAKANA_NOKIA));
763 expectedProperty.setValueType(QVersitProperty::CompoundType);
764 QTest::newRow(dataTag: "org utf8")
765 << QVersitDocument::VCard21Type
766 << QByteArray("ORG;CHARSET=UTF-8:" + KATAKANA_NOKIA + "\r\n")
767 << expectedProperty;
768 }
769
770 {
771 QVersitProperty expectedProperty;
772 expectedProperty.setName(QStringLiteral("ORG"));
773 expectedProperty.setValue(QString::fromUtf8(str: KATAKANA_NOKIA));
774 expectedProperty.setValueType(QVersitProperty::CompoundType);
775 QTest::newRow(dataTag: "vcard21 org utf8 qp")
776 << QVersitDocument::VCard21Type
777 << QByteArray("ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2\r\n")
778 << expectedProperty;
779 QTest::newRow(dataTag: "vcard30 org utf8 qp")
780 << QVersitDocument::VCard30Type
781 << QByteArray("ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2\r\n")
782 << expectedProperty;
783 }
784
785 {
786 QVersitProperty expectedProperty;
787 expectedProperty.setName(QStringLiteral("PHOTO"));
788 expectedProperty.setValue(SAMPLE_GIF);
789 expectedProperty.setValueType(QVersitProperty::BinaryType);
790 QTest::newRow(dataTag: "vcard21 photo1")
791 << QVersitDocument::VCard21Type
792 << QByteArray("PHOTO;ENCODING=BASE64:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n")
793 << expectedProperty;
794
795 QTest::newRow(dataTag: "vcard30 photo1")
796 << QVersitDocument::VCard30Type
797 << QByteArray("PHOTO;ENCODING=B:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n")
798 << expectedProperty;
799
800 // Again, but without the explicit "ENCODING" parameter
801 QTest::newRow(dataTag: "vcard21 photo2")
802 << QVersitDocument::VCard21Type
803 << QByteArray("PHOTO;BASE64:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n")
804 << expectedProperty;
805
806 QTest::newRow(dataTag: "vcard30 photo2")
807 << QVersitDocument::VCard30Type
808 << QByteArray("PHOTO;B:") + SAMPLE_GIF_BASE64 + QByteArray("\r\n\r\n")
809 << expectedProperty;
810 }
811
812 {
813 QVersitProperty expectedProperty;
814 expectedProperty.setGroups(QStringList() << QStringLiteral("HOME") << QStringLiteral("Springfield"));
815 expectedProperty.setName(QStringLiteral("EMAIL"));
816 expectedProperty.setValue(QStringLiteral("john.citizen@example.com"));
817 expectedProperty.setValueType(QVersitProperty::PlainType);
818 QTest::newRow(dataTag: "email qp")
819 << QVersitDocument::VCard21Type
820 << QByteArray("HOME.Springfield.EMAIL;Encoding=Quoted-Printable:john.citizen=40exam=\r\nple.com\r\n")
821 << expectedProperty;
822 }
823
824 {
825 QVersitDocument subDocument;
826 subDocument.setComponentType(QStringLiteral("VCARD"));
827 subDocument.setType(QVersitDocument::VCard21Type);
828 QVersitProperty subProperty;
829 subProperty.setName(QStringLiteral("FN"));
830 subProperty.setValue(QStringLiteral("Jenny"));
831 subDocument.addProperty(property: subProperty);
832
833 QVersitProperty expectedProperty;
834 expectedProperty.setName(QStringLiteral("AGENT"));
835 expectedProperty.setValue(QVariant::fromValue(value: subDocument));
836 expectedProperty.setValueType(QVersitProperty::VersitDocumentType);
837 QTest::newRow(dataTag: "agent")
838 << QVersitDocument::VCard21Type
839 << QByteArray("AGENT:\r\nBEGIN:VCARD\r\nFN:Jenny\r\nEND:VCARD\r\n\r\n")
840 << expectedProperty;
841 }
842
843 // Some MeeGo.com specific types (for roundtripping)
844 {
845 QVersitProperty expectedProperty;
846 expectedProperty.setName(QStringLiteral("X-EDS-QTCONTACTS"));
847 QStringList values;
848 values << "This is a test";
849 values << "I have a ; in the middle";
850 values << "fini";
851 expectedProperty.setValue(values);
852 expectedProperty.setValueType(QVersitProperty::CompoundType);
853 QTest::newRow(dataTag: "org utf8")
854 << QVersitDocument::VCard21Type
855 << QByteArray("X-EDS-QTCONTACTS:This is a test;I have a \\; in the middle;fini\r\n")
856 << expectedProperty;
857 }
858 {
859 QVersitProperty expectedProperty;
860 expectedProperty.setName(QStringLiteral("X-SYNCEVO-QTCONTACTS"));
861 QStringList values;
862 values << "This is a test";
863 values << "I have a ; in the middle";
864 values << "fini";
865 expectedProperty.setValue(values);
866 expectedProperty.setValueType(QVersitProperty::CompoundType);
867 QTest::newRow(dataTag: "org utf8")
868 << QVersitDocument::VCard21Type
869 << QByteArray("X-SYNCEVO-QTCONTACTS:This is a test;I have a \\; in the middle;fini\r\n")
870 << expectedProperty;
871 }
872 // This one should not be a compound type
873 {
874 QVersitProperty expectedProperty;
875 expectedProperty.setName(QStringLiteral("X-NOT-A-COMPOUND"));
876 QStringList values;
877 values << "This is a test";
878 values << "I have a ; in the middle";
879 values << "fini";
880 expectedProperty.setValue(QString::fromLatin1(str: "This is a test;I have a \\; in the middle;fini"));
881 expectedProperty.setValueType(QVersitProperty::PlainType);
882 QTest::newRow(dataTag: "org utf8")
883 << QVersitDocument::VCard21Type
884 << QByteArray("X-NOT-A-COMPOUND:This is a test;I have a \\; in the middle;fini\r\n")
885 << expectedProperty;
886 }
887
888#endif
889}
890
891void tst_QVersitReader::testParseVersitDocument()
892{
893 QFETCH(QByteArray, vCard);
894 QFETCH(bool, expectedSuccess);
895 QFETCH(QVersitDocument, expectedDocument);
896
897 QBuffer buffer(&vCard);
898 buffer.open(openMode: QIODevice::ReadOnly);
899 LineReader lineReader(&buffer, QTextCodec::codecForName(name: "UTF-8"));
900
901 mReader->setDevice(&buffer);
902 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
903 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
904 QCOMPARE(mReader->error(), expectedSuccess ? QVersitReader::NoError : QVersitReader::ParseError);
905 if (expectedSuccess) {
906 QList<QVersitDocument> documents = mReader->results();
907 QCOMPARE(documents.size(), 1);
908 QVersitDocument document = documents.at(i: 0);
909 if (document != expectedDocument) {
910 qDebug() << "Expected: " << expectedDocument;
911 qDebug() << "Actual: " << document;
912 QCOMPARE(document, expectedDocument);
913 }
914 }
915}
916
917void tst_QVersitReader::testParseVersitDocument_data()
918{
919 QTest::addColumn<QByteArray>(name: "vCard");
920 QTest::addColumn<bool>(name: "expectedSuccess");
921 QTest::addColumn<QVersitDocument>(name: "expectedDocument");
922
923 {
924 QVersitDocument expected(QVersitDocument::VCard21Type);
925 expected.setComponentType(QStringLiteral("VCARD"));
926 QVersitProperty property;
927 property.setName(QStringLiteral("FN"));
928 property.setValue(QStringLiteral("John"));
929 expected.addProperty(property);
930 QTest::newRow(dataTag: "Basic vCard 2.1")
931 << QByteArray(
932 "BEGIN:VCARD\r\n"
933 "VERSION:2.1\r\n"
934 "FN:John\r\n"
935 "END:VCARD\r\n")
936 << true
937 << expected;
938 }
939
940 {
941 QVersitDocument expected(QVersitDocument::VCard21Type);
942 expected.setComponentType(QStringLiteral("VCARD"));
943 QVersitProperty property;
944 property.setName(QStringLiteral("FN"));
945 property.setValue(QStringLiteral("John"));
946 expected.addProperty(property);
947 QVersitDocument agent(QVersitDocument::VCard21Type);
948 agent.setComponentType(QStringLiteral("VCARD"));
949 property.setValue(QStringLiteral("Jenny"));
950 agent.addProperty(property);
951 property.clear();
952 property.setName(QStringLiteral("AGENT"));
953 property.setValue(QVariant::fromValue(value: agent));
954 property.setValueType(QVersitProperty::VersitDocumentType);
955 expected.addProperty(property);
956 property.clear();
957 property.setName(QStringLiteral("EMAIL"));
958 property.setValue(QStringLiteral("john.citizen@example.com"));
959 expected.addProperty(property);
960 QTest::newRow(dataTag: "vCard 2.1 with Agent")
961 << QByteArray(
962 "BEGIN:VCARD\r\n"
963 "VERSION:2.1\r\n"
964 "FN:John\r\n"
965 "AGENT:BEGIN:VCARD\r\n"
966 "FN:Jenny\r\n"
967 "END:VCARD\r\n"
968 "\r\n"
969 "EMAIL;ENCODING=QUOTED-PRINTABLE:john.citizen=40exam=\r\nple.com\r\n"
970 "END:VCARD\r\n")
971 << true
972 << expected;
973 }
974
975 {
976 QVersitDocument expected(QVersitDocument::VCard30Type);
977 expected.setComponentType(QStringLiteral("VCARD"));
978 QVersitProperty property;
979 property.setName(QStringLiteral("FN"));
980 property.setValue(QStringLiteral("John"));
981 expected.addProperty(property);
982 QVersitDocument agent(QVersitDocument::VCard30Type);
983 agent.setComponentType(QStringLiteral("VCARD"));
984 property.setValue(QStringLiteral("Jenny"));
985 agent.addProperty(property);
986 property.clear();
987 property.setName(QStringLiteral("AGENT"));
988 property.setValue(QVariant::fromValue(value: agent));
989 property.setValueType(QVersitProperty::VersitDocumentType);
990 expected.addProperty(property);
991 property.clear();
992 property.setName(QStringLiteral("EMAIL"));
993 property.setValue(QStringLiteral("john.citizen@example.com"));
994 expected.addProperty(property);
995 QTest::newRow(dataTag: "vCard 3.0 with Agent")
996 << QByteArray(
997 "BEGIN:VCARD\r\n"
998 "VERSION:3.0\r\n"
999 "FN:John\r\n"
1000 "AGENT:BEGIN\\:VCARD\\nFN\\:Jenny\\nEND\\:VCARD\\n\r\n"
1001 "EMAIL:john.citizen@example.com\r\n"
1002 "END:VCARD\r\n")
1003 << true
1004 << expected;
1005 }
1006
1007 QTest::newRow(dataTag: "No BEGIN found")
1008 << QByteArray(
1009 "VCARD\r\n"
1010 "VERSION:2.1\r\n"
1011 "FN:Nobody\r\n"
1012 "END:VCARD\r\n")
1013 << false
1014 << QVersitDocument();
1015
1016 QTest::newRow(dataTag: "Wrong card type")
1017 << QByteArray(
1018 "BEGIN:VCAL\r\n"
1019 "END:VCAL\r\n")
1020 << false
1021 << QVersitDocument();
1022
1023 QTest::newRow(dataTag: "Wrong version")
1024 << QByteArray(
1025 "BEGIN:VCARD\r\n"
1026 "VERSION:1234\r\n"
1027 "FN:Nobody\r\n"
1028 "END:VCARD\r\n")
1029 << false
1030 << QVersitDocument();
1031
1032 {
1033 QVersitDocument expected(QVersitDocument::VCard21Type);
1034 expected.setComponentType(QStringLiteral("VCARD"));
1035 QVersitProperty property;
1036 property.setName(QStringLiteral("FN"));
1037 property.setValue(QStringLiteral("Nobody"));
1038 expected.addProperty(property);
1039 QTest::newRow(dataTag: "No trailing crlf")
1040 << QByteArray(
1041 "BEGIN:VCARD\r\n"
1042 "VERSION:2.1\r\n"
1043 "FN:Nobody\r\n"
1044 "END:VCARD")
1045 << true
1046 << expected;
1047 }
1048
1049 QTest::newRow(dataTag: "No end")
1050 << QByteArray(
1051 "BEGIN:VCARD\r\n"
1052 "VERSION:2.1\r\n"
1053 "FN:Nobody\r\n")
1054 << false
1055 << QVersitDocument();
1056
1057 {
1058 QVersitDocument expected(QVersitDocument::VCard21Type);
1059 expected.setComponentType(QStringLiteral("VCARD"));
1060 QVersitProperty property;
1061 property.setName(QStringLiteral("X-EXAMPLES"));
1062 property.setValue(QStringLiteral("Family vCard"));
1063 expected.addProperty(property);
1064
1065 QVersitDocument nested1(QVersitDocument::VCard21Type);
1066 nested1.setComponentType(QStringLiteral("VCARD"));
1067 property.setName(QStringLiteral("FN"));
1068 property.setValue(QStringLiteral("John"));
1069 nested1.addProperty(property);
1070 expected.addSubDocument(subdocument: nested1);
1071
1072 QVersitDocument nested2(QVersitDocument::VCard21Type);
1073 nested2.setComponentType(QStringLiteral("VCARD"));
1074 property.setName(QStringLiteral("FN"));
1075 property.setValue(QStringLiteral("Jenny"));
1076 nested2.addProperty(property);
1077 expected.addSubDocument(subdocument: nested2);
1078
1079 QTest::newRow(dataTag: "Grouped vCard")
1080 << QByteArray(
1081 "BEGIN:VCARD\r\n"
1082 "VERSION:2.1\r\n"
1083 "X-EXAMPLES:Family vCard\r\n"
1084 "BEGIN:VCARD\r\n"
1085 "VERSION:2.1\r\n"
1086 "FN:John\r\n"
1087 "END:VCARD\r\n"
1088 "BEGIN:VCARD\r\n"
1089 "VERSION:2.1\r\n"
1090 "FN:Jenny\r\n"
1091 "END:VCARD\r\n"
1092 "END:VCARD")
1093 << true
1094 << expected;
1095 }
1096
1097 {
1098 QVersitDocument expected(QVersitDocument::ICalendar20Type);
1099 expected.setComponentType(QStringLiteral("VCALENDAR"));
1100 QVersitProperty property;
1101 property.setName(QStringLiteral("PRODID"));
1102 property.setValue(QStringLiteral("-//hacksw/handcal//NONSGML v1.0//EN"));
1103 expected.addProperty(property);
1104 QVersitDocument nested(QVersitDocument::ICalendar20Type);
1105 nested.setComponentType(QStringLiteral("VEVENT"));
1106 property.setName(QStringLiteral("DTSTART"));
1107 property.setValue(QStringLiteral("19970714T170000Z"));
1108 nested.addProperty(property);
1109 property.setName(QStringLiteral("DTEND"));
1110 property.setValue(QStringLiteral("19970715T035959Z"));
1111 nested.addProperty(property);
1112 property.setName(QStringLiteral("SUMMARY"));
1113 property.setValue(QStringLiteral("Bastille Day Party"));
1114 nested.addProperty(property);
1115 QMultiHash<QString,QString> parameters;
1116 QVersitDocument nestedAlarm(QVersitDocument::ICalendar20Type);
1117 nestedAlarm.setComponentType("VALARM");
1118 property.setName("TRIGGER");
1119 parameters.insert(QStringLiteral("VALUE"), QStringLiteral("DATE-TIME"));
1120 property.setParameters(parameters);
1121 property.setValue(QStringLiteral("19970714T170000Z"));
1122 nestedAlarm.addProperty(property);
1123 property.clear();
1124 property.setName(QStringLiteral("REPEAT"));
1125 property.setValue(4);
1126 nestedAlarm.addProperty(property);
1127 property.setName("DURATION");
1128 property.setValue(QStringLiteral("PT15M"));
1129 nestedAlarm.addProperty(property);
1130 property.setName(QStringLiteral("ACTION"));
1131 property.setValue(QStringLiteral("AUDIO"));
1132 nestedAlarm.addProperty(property);
1133 property.setName(QStringLiteral("ATTACH"));
1134 parameters.clear();
1135 parameters.insert(QStringLiteral("FMTTYPE"), QStringLiteral("audio/basic"));
1136 property.setParameters(parameters);
1137 property.setValue(QUrl(QStringLiteral("ftp://host.com/pub/sounds/bell-01.aud")));
1138 nestedAlarm.addProperty(property);
1139 nested.addSubDocument(subdocument: nestedAlarm);
1140 expected.addSubDocument(subdocument: nested);
1141 QTest::newRow(dataTag: "iCalendar sample from spec")
1142 << QByteArray(
1143 "BEGIN:VCALENDAR\r\n"
1144 "VERSION:2.0\r\n"
1145 "PRODID:-//hacksw/handcal//NONSGML v1.0//EN\r\n"
1146 "BEGIN:VEVENT\r\n"
1147 "DTSTART:19970714T170000Z\r\n"
1148 "DTEND:19970715T035959Z\r\n"
1149 "SUMMARY:Bastille Day Party\r\n"
1150 "BEGIN:VALARM\r\n"
1151 "TRIGGER;VALUE=DATE-TIME:19970714T170000Z\r\n"
1152 "REPEAT:4\r\n"
1153 "DURATION:PT15M\r\n"
1154 "ACTION:AUDIO\r\n"
1155 "ATTACH;FMTTYPE=audio/basic:ftp://host.com/pub/sounds/bell-01.aud\r\n"
1156 "END:VALARM\r\n"
1157 "END:VEVENT\r\n"
1158 "END:VCALENDAR\r\n")
1159 << true
1160 << expected;
1161 }
1162}
1163
1164void tst_QVersitReader::testDecodeQuotedPrintable()
1165{
1166#ifndef QT_BUILD_INTERNAL
1167 QSKIP("Testing private API");
1168#else
1169 QFETCH(QByteArray, encoded);
1170
1171 QFETCH(QByteArray, decoded);
1172 mReaderPrivate->decodeQuotedPrintable(text: &encoded);
1173 QCOMPARE(encoded, decoded);
1174#endif
1175}
1176
1177void tst_QVersitReader::testDecodeQuotedPrintable_data()
1178{
1179#ifdef QT_BUILD_INTERNAL
1180 QTest::addColumn<QByteArray>(name: "encoded");
1181 QTest::addColumn<QByteArray>(name: "decoded");
1182
1183
1184 QTest::newRow(dataTag: "Soft line breaks")
1185 << QByteArray("This=\r\n is =\r\none line.")
1186 << QByteArray("This is one line.");
1187
1188 QTest::newRow(dataTag: "Characters recommended to be encoded according to RFC 1521")
1189 << QByteArray("To be decoded: =0A=0D=21=22=23=24=3D=40=5B=5C=5D=5E=60=7B=7C=7D=7E")
1190 << QByteArray("To be decoded: \n\r!\"#$=@[\\]^`{|}~");
1191
1192 QTest::newRow(dataTag: "Characters recommended to be encoded according to RFC 1521(lower case)")
1193 << QByteArray("To be decoded: =0a=0d=21=22=23=24=3d=40=5b=5c=5d=5e=60=7b=7c=7d=7e")
1194 << QByteArray("To be decoded: \n\r!\"#$=@[\\]^`{|}~");
1195
1196 QTest::newRow(dataTag: "random characters encoded")
1197 << QByteArray("=45=6E=63=6F=64=65=64 =64=61=74=61")
1198 << QByteArray("Encoded data");
1199
1200 QTest::newRow(dataTag: "short string1")
1201 << QByteArray("-=_")
1202 << QByteArray("-=_");
1203
1204 QTest::newRow(dataTag: "short string2")
1205 << QByteArray("=0")
1206 << QByteArray("=0");
1207
1208 QTest::newRow(dataTag: "short string2")
1209 << QByteArray("\r")
1210 << QByteArray("\r");
1211
1212 QTest::newRow(dataTag: "short string2")
1213 << QByteArray("\n")
1214 << QByteArray("\n");
1215
1216 QTest::newRow(dataTag: "short string2")
1217 << QByteArray("\n\r")
1218 << QByteArray("\n\r");
1219
1220 QTest::newRow(dataTag: "White spaces")
1221 << QByteArray("=09=20")
1222 << QByteArray("\t ");
1223#endif
1224}
1225void tst_QVersitReader::testParamName()
1226{
1227#ifndef QT_BUILD_INTERNAL
1228 QSKIP("Testing private API");
1229#else
1230 // Empty value
1231 QByteArray param;
1232 QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec),QString());
1233
1234 // Only value present
1235 param = "WORK";
1236 QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec),
1237 QStringLiteral("TYPE"));
1238
1239 // The below tests intentionally use the misspelling TIPE to avoid the default behaviour of
1240 // returning TYPE when the name can't be parsed.
1241 // Both name and value, spaces after the name
1242 param = "TIPE \t =WORK";
1243 QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec),
1244 QStringLiteral("TIPE"));
1245
1246 // Both name and value, no spaces after the name
1247 param = "TIPE=WORK";
1248 QCOMPARE(mReaderPrivate->paramName(param, mAsciiCodec),
1249 QStringLiteral("TIPE"));
1250
1251 // Test wide character support.
1252 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1253 param = codec->fromUnicode(QStringLiteral("TIPE=WORK"));
1254 QCOMPARE(mReaderPrivate->paramName(param, codec),
1255 QStringLiteral("TIPE"));
1256#endif
1257}
1258
1259void tst_QVersitReader::testParamValue()
1260{
1261#ifndef QT_BUILD_INTERNAL
1262 QSKIP("Testing private API");
1263#else
1264 // Empty value
1265 QByteArray param;
1266 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString());
1267
1268 // Only value present
1269 param = "WORK";
1270 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),
1271 QStringLiteral("WORK"));
1272
1273 // Name and equals sign, but no value
1274 param = "TYPE=";
1275 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString());
1276
1277 // Name and equals sign, but value has only spaces
1278 param = "TYPE= \t ";
1279 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),QString());
1280
1281 // Both name and value, spaces before the value
1282 param = "TYPE= \t WORK";
1283 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),
1284 QStringLiteral("WORK"));
1285
1286 // Both name and value, no spaces before the value
1287 param = "ENCODING=QUOTED-PRINTABLE";
1288 QCOMPARE(mReaderPrivate->paramValue(param, mAsciiCodec),
1289 QStringLiteral("QUOTED-PRINTABLE"));
1290
1291 // Test wide character support.
1292 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1293 param = codec->fromUnicode(QStringLiteral("TYPE=WORK"));
1294 QCOMPARE(mReaderPrivate->paramValue(param, codec),
1295 QStringLiteral("WORK"));
1296#endif
1297}
1298
1299void tst_QVersitReader::testExtractPart()
1300{
1301#ifndef QT_BUILD_INTERNAL
1302 QSKIP("Testing private API");
1303#else
1304 QByteArray originalStr;
1305
1306 // Negative starting position
1307 QCOMPARE(mReaderPrivate->extractPart(originalStr,-1,1), QByteArray());
1308
1309 // Empty original string
1310 QCOMPARE(mReaderPrivate->extractPart(originalStr,0,1), QByteArray());
1311
1312 // Trimmed substring empty
1313 originalStr = " \t \t";
1314 QCOMPARE(mReaderPrivate->extractPart(originalStr,0,4), QByteArray());
1315
1316 // The given substring length is greater than the original string length
1317 originalStr = "ENCODING=7BIT";
1318 QCOMPARE(mReaderPrivate->extractPart(originalStr,0,100), originalStr);
1319
1320 // Non-empty substring, from the beginning
1321 originalStr = " TYPE=WORK ; X-PARAM=X-VALUE; ENCODING=8BIT";
1322 QCOMPARE(mReaderPrivate->extractPart(originalStr,0,11),
1323 QByteArray("TYPE=WORK"));
1324
1325 // Non-empty substring, from the middle
1326 QCOMPARE(mReaderPrivate->extractPart(originalStr,12,16),
1327 QByteArray("X-PARAM=X-VALUE"));
1328
1329 // Non-empty substring, from the middle to the end
1330 QCOMPARE(mReaderPrivate->extractPart(originalStr,29),
1331 QByteArray("ENCODING=8BIT"));
1332#endif
1333}
1334
1335void tst_QVersitReader::testExtractParts()
1336{
1337#ifndef QT_BUILD_INTERNAL
1338 QSKIP("Testing private API");
1339#else
1340 QList<QByteArray> parts;
1341
1342 // Empty value
1343 QByteArray text;
1344 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1345 QVERIFY(parts.isEmpty());
1346
1347 // Only separator
1348 text = ";";
1349 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1350 QVERIFY(parts.isEmpty());
1351
1352 // One part
1353 text = "part";
1354 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1355 QCOMPARE(parts.count(),1);
1356 QCOMPARE(QLatin1String(parts[0]),QLatin1String("part"));
1357
1358 // Separator in the beginning, one part
1359 text = ";part";
1360 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1361 QCOMPARE(parts.count(),1);
1362 QCOMPARE(QLatin1String(parts[0]),QLatin1String("part"));
1363
1364 // Separator in the end, one part
1365 text = "part;";
1366 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1367 QCOMPARE(parts.count(),1);
1368 QCOMPARE(QLatin1String(parts[0]),QLatin1String("part"));
1369
1370 // One part that contains escaped separator
1371 text = "part\\;";
1372 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1373 QCOMPARE(parts.count(),1);
1374 QCOMPARE(QLatin1String(parts[0]),QLatin1String("part\\;"));
1375
1376 // Two parts
1377 text = "part1;part2";
1378 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1379 QCOMPARE(parts.count(),2);
1380 QCOMPARE(QLatin1String(parts[0]),QLatin1String("part1"));
1381 QCOMPARE(QLatin1String(parts[1]),QLatin1String("part2"));
1382
1383 // Two parts that contain escaped separators
1384 text = "pa\\;rt1;par\\;t2";
1385 parts = mReaderPrivate->extractParts(text,separator: ";", codec: mAsciiCodec);
1386 QCOMPARE(parts.count(),2);
1387 QCOMPARE(QLatin1String(parts[0]),QLatin1String("pa\\;rt1"));
1388 QCOMPARE(QLatin1String(parts[1]),QLatin1String("par\\;t2"));
1389
1390 // Test wide character support
1391 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1392 text = codec->fromUnicode(QStringLiteral("part1;part2"));
1393 parts = mReaderPrivate->extractParts(text,separator: ";", codec);
1394 QCOMPARE(parts.count(),2);
1395 QCOMPARE(codec->toUnicode(parts[0]),QStringLiteral("part1"));
1396 QCOMPARE(codec->toUnicode(parts[1]),QStringLiteral("part2"));
1397#endif
1398}
1399
1400void tst_QVersitReader::testExtractPropertyGroupsAndName()
1401{
1402#ifndef QT_BUILD_INTERNAL
1403 QSKIP("Testing private API");
1404#else
1405 QPair<QStringList,QString> groupsAndName;
1406
1407 // Empty string
1408 LByteArray cursor(QByteArray(" "));
1409 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1410 QCOMPARE(groupsAndName.first.count(),0);
1411 QCOMPARE(groupsAndName.second,QString());
1412
1413 // No value -> returns empty string and no groups
1414 QByteArray property("TEL");
1415 cursor = property;
1416 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1417 QCOMPARE(groupsAndName.first.count(),0);
1418 QCOMPARE(groupsAndName.second,QString());
1419
1420 // Simple name and value
1421 property = "TEL:123";
1422 cursor = property;
1423 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1424 QCOMPARE(groupsAndName.first.count(),0);
1425 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1426
1427 // One whitespace before colon
1428 property = "TEL :123";
1429 cursor = property;
1430 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1431 QCOMPARE(groupsAndName.first.count(),0);
1432 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1433
1434 // Several whitespaces before colon
1435 property = "TEL \t :123";
1436 cursor = property;
1437 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1438 QCOMPARE(groupsAndName.first.count(),0);
1439 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1440
1441 // Name contains a group
1442 property = "group1.TEL:1234";
1443 cursor = property;
1444 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1445 QCOMPARE(groupsAndName.first.count(),1);
1446 QCOMPARE(groupsAndName.first.takeFirst(),QStringLiteral("group1"));
1447 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1448
1449 // Name contains more than one group
1450 property = "group1.group2.TEL:12345";
1451 cursor = property;
1452 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1453 QCOMPARE(groupsAndName.first.count(),2);
1454 QCOMPARE(groupsAndName.first.takeFirst(),QStringLiteral("group1"));
1455 QCOMPARE(groupsAndName.first.takeFirst(),QStringLiteral("group2"));
1456 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1457 QCOMPARE(cursor.toByteArray(), QByteArray(":12345"));
1458
1459 // Property contains one parameter
1460 property = "TEL;WORK:123";
1461 cursor = property;
1462 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1463 QCOMPARE(groupsAndName.first.count(),0);
1464 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1465
1466 // Property contains several parameters
1467 property = "EMAIL;INTERNET;ENCODING=QUOTED-PRINTABLE:user=40ovi.com";
1468 cursor = property;
1469 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1470 QCOMPARE(groupsAndName.first.count(),0);
1471 QCOMPARE(groupsAndName.second,QStringLiteral("EMAIL"));
1472
1473 // Name contains an escaped semicolon
1474 property = "X-proper\\;ty:value";
1475 cursor = property;
1476 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec: mAsciiCodec);
1477 QCOMPARE(groupsAndName.first.count(),0);
1478 QCOMPARE(groupsAndName.second,QStringLiteral("X-proper\\;ty"));
1479
1480 // Test wide character support
1481 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1482 property = codec->fromUnicode(QStringLiteral("group1.group2.TEL;WORK:123"));
1483 cursor = property;
1484 groupsAndName = mReaderPrivate->extractPropertyGroupsAndName(line: &cursor, codec);
1485 QCOMPARE(groupsAndName.first.count(),2);
1486 QCOMPARE(groupsAndName.first.takeFirst(),QStringLiteral("group1"));
1487 QCOMPARE(groupsAndName.first.takeFirst(),QStringLiteral("group2"));
1488 QCOMPARE(groupsAndName.second,QStringLiteral("TEL"));
1489 QCOMPARE(cursor.size(), 18); // ";WORK:123" in UTF16 is 18 bytes
1490#endif
1491}
1492
1493void tst_QVersitReader::testExtractVCard21PropertyParams()
1494{
1495#ifndef QT_BUILD_INTERNAL
1496 QSKIP("Testing private API");
1497#else
1498 // No parameters
1499 LByteArray cursor(QByteArray(":123"));
1500 QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(&cursor, mAsciiCodec).count(), 0);
1501
1502 // "Empty" parameter
1503 cursor = QByteArray(";:123");
1504 QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(&cursor, mAsciiCodec).count(), 0);
1505
1506 // Semicolon found, but no value for the property
1507 cursor = QByteArray(";TYPE=X-TYPE");
1508 QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(&cursor, mAsciiCodec).count(), 0);
1509
1510 // The property name contains an escaped semicolon, no parameters
1511 cursor = QByteArray(":value");
1512 QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(&cursor, mAsciiCodec).count(), 0);
1513
1514 // The property value contains a semicolon, no parameters
1515 cursor = QByteArray(":va;lue");
1516 QCOMPARE(mReaderPrivate->extractVCard21PropertyParams(&cursor, mAsciiCodec).count(), 0);
1517
1518 // One parameter
1519 cursor = QByteArray(";HOME:123");
1520 QMultiHash<QString,QString> params = mReaderPrivate->extractVCard21PropertyParams(line: &cursor,
1521 codec: mAsciiCodec);
1522 QCOMPARE(1, params.count());
1523 QCOMPARE(1, params.values(QStringLiteral("TYPE")).count());
1524 QCOMPARE(params.values(QStringLiteral("TYPE"))[0],QStringLiteral("HOME"));
1525
1526 // Two parameters of the same type
1527 cursor = QByteArray(";HOME;VOICE:123");
1528 params = mReaderPrivate->extractVCard21PropertyParams(line: &cursor, codec: mAsciiCodec);
1529 QCOMPARE(2, params.count());
1530 QCOMPARE(2, params.values(QStringLiteral("TYPE")).count());
1531 QCOMPARE(params.values(QStringLiteral("TYPE"))[0],QStringLiteral("HOME"));
1532 QCOMPARE(params.values(QStringLiteral("TYPE"))[1],QStringLiteral("VOICE"));
1533
1534 // Two parameters, several empty parameters (extra semicolons)
1535 cursor = QByteArray(";;;;HOME;;;;;VOICE;;;:123");
1536 params = mReaderPrivate->extractVCard21PropertyParams(line: &cursor, codec: mAsciiCodec);
1537 QCOMPARE(2, params.count());
1538 QCOMPARE(2, params.values(QStringLiteral("TYPE")).count());
1539 QCOMPARE(params.values(QStringLiteral("TYPE"))[0],QStringLiteral("HOME"));
1540 QCOMPARE(params.values(QStringLiteral("TYPE"))[1],QStringLiteral("VOICE"));
1541
1542 // Two parameters with different types
1543 cursor = QByteArray(";INTERNET;ENCODING=QUOTED-PRINTABLE:user=40ovi.com");
1544 params.clear();
1545 params = mReaderPrivate->extractVCard21PropertyParams(line: &cursor, codec: mAsciiCodec);
1546 QCOMPARE(2, params.count());
1547 QList<QString> typeParams = params.values(QStringLiteral("TYPE"));
1548 QCOMPARE(1, typeParams.count());
1549 QCOMPARE(typeParams[0],QStringLiteral("INTERNET"));
1550 QList<QString> encodingParams = params.values(QStringLiteral("ENCODING"));
1551 QCOMPARE(1, encodingParams.count());
1552 QCOMPARE(encodingParams[0],QStringLiteral("QUOTED-PRINTABLE"));
1553
1554 // Test wide character support.
1555 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1556 QByteArray data = VersitUtils::encode(ba: ";HOME;CHARSET=UTF-16:123", codec);
1557 cursor = data;
1558 params = mReaderPrivate->extractVCard21PropertyParams(line: &cursor, codec);
1559 QCOMPARE(2, params.count());
1560 typeParams = params.values(QStringLiteral("TYPE"));
1561 QCOMPARE(1, typeParams.count());
1562 QCOMPARE(typeParams[0],QStringLiteral("HOME"));
1563 encodingParams = params.values(QStringLiteral("CHARSET"));
1564 QCOMPARE(1, encodingParams.count());
1565 QCOMPARE(encodingParams[0],QStringLiteral("UTF-16"));
1566#endif
1567}
1568
1569void tst_QVersitReader::testExtractVCard30PropertyParams()
1570{
1571#ifndef QT_BUILD_INTERNAL
1572 QSKIP("Testing private API");
1573#else
1574 // No parameters
1575 LByteArray cursor(QByteArray(":123"));
1576 QCOMPARE(mReaderPrivate->extractVCard30PropertyParams(&cursor, mAsciiCodec).count(), 0);
1577
1578 // One parameter
1579 cursor = QByteArray(";TYPE=HOME:123");
1580 QMultiHash<QString,QString> params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor,
1581 codec: mAsciiCodec);
1582 QCOMPARE(params.count(), 1);
1583 QCOMPARE(params.values(QStringLiteral("TYPE")).count(), 1);
1584 QCOMPARE(params.values(QStringLiteral("TYPE"))[0], QStringLiteral("HOME"));
1585
1586 // One parameter with an escaped semicolon
1587 cursor = QByteArray(";para\\;meter:value");
1588 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1589 QCOMPARE(params.count(), 1);
1590 QCOMPARE(params.values(QStringLiteral("TYPE")).count(), 1);
1591 QCOMPARE(params.values(QStringLiteral("TYPE"))[0], QStringLiteral("para;meter"));
1592
1593 // One parameter with and escaped comma in the name and the value
1594 cursor = QByteArray(";X-PA\\,RAM=VAL\\,UE:123");
1595 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1596 QCOMPARE(params.count(), 1);
1597 QCOMPARE(params.values(QStringLiteral("X-PA,RAM")).count(), 1);
1598 QCOMPARE(params.values(QStringLiteral("X-PA,RAM"))[0], QStringLiteral("VAL,UE"));
1599
1600 // Two parameters of the same type
1601 cursor = QByteArray(";TYPE=HOME,VOICE:123");
1602 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1603 QCOMPARE(params.count(), 2);
1604 QCOMPARE(params.values(QStringLiteral("TYPE")).count(), 2);
1605 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("HOME")));
1606 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("VOICE")));
1607
1608 // Two parameters of the same type in separate name-values
1609 cursor = QByteArray(";TYPE=HOME;TYPE=VOICE:123");
1610 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1611 QCOMPARE(params.count(), 2);
1612 QCOMPARE(params.values(QStringLiteral("TYPE")).count(), 2);
1613 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("HOME")));
1614 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("VOICE")));
1615
1616 // Three parameters of the same type
1617 cursor = QByteArray(";TYPE=PREF,HOME,VOICE:123");
1618 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1619 QCOMPARE(params.count(), 3);
1620 QCOMPARE(params.values(QStringLiteral("TYPE")).count(), 3);
1621 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("PREF")));
1622 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("HOME")));
1623 QVERIFY(params.values(QStringLiteral("TYPE")).contains(QStringLiteral("VOICE")));
1624
1625 // Two parameters with different types
1626 cursor = QByteArray(";TYPE=HOME;X-PARAM=X-VALUE:Home Street 1");
1627 params.clear();
1628 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec: mAsciiCodec);
1629 QCOMPARE(params.count(), 2);
1630 QList<QString> typeParams = params.values(QStringLiteral("TYPE"));
1631 QCOMPARE(typeParams.count(), 1);
1632 QCOMPARE(typeParams[0],QStringLiteral("HOME"));
1633 QList<QString> encodingParams = params.values(QStringLiteral("X-PARAM"));
1634 QCOMPARE(encodingParams.count(), 1);
1635 QCOMPARE(encodingParams[0],QStringLiteral("X-VALUE"));
1636
1637 // Test wide character support.
1638 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1639 QByteArray data = VersitUtils::encode(ba: ";TIPE=HOME,VOICE;CHARSET=UTF-16:123", codec);
1640 cursor = data;
1641 params = mReaderPrivate->extractVCard30PropertyParams(line: &cursor, codec);
1642 QCOMPARE(params.count(), 3);
1643 typeParams = params.values(QStringLiteral("TIPE"));
1644 QCOMPARE(params.values(QStringLiteral("TIPE")).count(), 2);
1645 QVERIFY(params.values(QStringLiteral("TIPE")).contains(QStringLiteral("HOME")));
1646 QVERIFY(params.values(QStringLiteral("TIPE")).contains(QStringLiteral("VOICE")));
1647 encodingParams = params.values(QStringLiteral("CHARSET"));
1648 QCOMPARE(1, encodingParams.count());
1649 QCOMPARE(encodingParams[0],QStringLiteral("UTF-16"));
1650#endif
1651}
1652
1653void tst_QVersitReader::testExtractParams()
1654{
1655#ifndef QT_BUILD_INTERNAL
1656 QSKIP("Testing private API");
1657#else
1658 LByteArray cursor;
1659 QByteArray data = ":123";
1660 cursor = data;
1661 QList<QByteArray> params = mReaderPrivate->extractParams(line: &cursor, codec: mAsciiCodec);
1662 QCOMPARE(params.size(), 0);
1663 QVERIFY(cursor == QByteArray("123"));
1664
1665 data = "a;b:123";
1666 cursor = data;
1667 params = mReaderPrivate->extractParams(line: &cursor, codec: mAsciiCodec);
1668 QCOMPARE(params.size(), 2);
1669 QVERIFY(cursor == QByteArray("123"));
1670 QCOMPARE(params.at(0), QByteArray("a"));
1671 QCOMPARE(params.at(1), QByteArray("b"));
1672
1673 QTextCodec* codec = QTextCodec::codecForName(name: "UTF-16BE");
1674 data = VersitUtils::encode(ba: ":123", codec);
1675 cursor = data;
1676 params = mReaderPrivate->extractParams(line: &cursor, codec);
1677 QCOMPARE(params.size(), 0);
1678 QCOMPARE(cursor.size(), 6); // "123" takes up 6 bytes in UTF-16
1679
1680 data = VersitUtils::encode(ba: "a;b:123", codec);
1681 cursor = data;
1682 params = mReaderPrivate->extractParams(line: &cursor, codec);
1683 QCOMPARE(params.size(), 2);
1684 QCOMPARE(cursor.size(), 6); // "123" takes up 6 bytes in UTF-16
1685#endif
1686}
1687
1688Q_DECLARE_METATYPE(QList<QString>)
1689
1690void tst_QVersitReader::testReadLine()
1691{
1692#ifndef QT_BUILD_INTERNAL
1693 QSKIP("Testing private API");
1694#else
1695 QFETCH(QByteArray, codecName);
1696 QFETCH(QString, data);
1697 QFETCH(QList<QString>, expectedLines);
1698
1699 QTextCodec* codec = QTextCodec::codecForName(name: codecName);
1700 QTextEncoder* encoder = codec->makeEncoder();
1701 encoder->fromUnicode(str: QString());
1702
1703 QByteArray bytes(encoder->fromUnicode(str: data));
1704
1705 mInputDevice->close();
1706 mInputDevice->setData(bytes);
1707 mInputDevice->open(openMode: QIODevice::ReadWrite);
1708
1709 LineReader lineReader(mInputDevice, codec, 10);
1710
1711 QByteArray testLine("test pushed line");
1712 // Check that all expected lines are read...
1713 foreach (QString expectedLine, expectedLines) {
1714 // (test push a line and read it)
1715 lineReader.pushLine(line: testLine);
1716 QVERIFY(!lineReader.atEnd());
1717 LByteArray line = lineReader.readLine();
1718 QCOMPARE(line.toByteArray(), testLine);
1719
1720
1721 QByteArray expectedBytes(encoder->fromUnicode(str: expectedLine));
1722 QVERIFY(!lineReader.atEnd());
1723 line = lineReader.readLine();
1724 if(line.toByteArray() != expectedBytes) {
1725 qDebug() << line.toByteArray();
1726 qDebug() << expectedBytes;
1727 QCOMPARE(line.toByteArray(), expectedBytes);
1728 }
1729 QCOMPARE(line.size(), expectedBytes.length());
1730 }
1731
1732 // (test push a line to a line reader that's reached its end)
1733 lineReader.pushLine(line: testLine);
1734 QVERIFY(!lineReader.atEnd());
1735 LByteArray line = lineReader.readLine();
1736 QCOMPARE(line.toByteArray(), testLine);
1737
1738 // ...And that there are no more lines
1739 line = lineReader.readLine();
1740 QVERIFY(line.isEmpty());
1741 QVERIFY(lineReader.atEnd());
1742
1743 delete encoder;
1744#endif
1745}
1746
1747void tst_QVersitReader::testReadLine_data()
1748{
1749#ifdef QT_BUILD_INTERNAL
1750 // Note: for this test, we set mLineReader to read 10 bytes at a time. Lines of multiples of
1751 // 10 bytes are hence border cases.
1752 // Note: QVersitReaders' LineReader contains hacks that sniff for colons in the input to enable
1753 // a workaround for malformed vCards with badly wrapped lines (see the last test case)
1754 // For testing of normal wrapping behaviour, a colon must appear in every line.
1755 QTest::addColumn<QByteArray>(name: "codecName");
1756 QTest::addColumn<QString>(name: "data");
1757 QTest::addColumn<QList<QString> >(name: "expectedLines");
1758
1759 QList<QByteArray> codecNames;
1760 codecNames << "UTF-8" << "UTF-16";
1761
1762 foreach (QByteArray codecName, codecNames) {
1763 QTest::newRow(dataTag: "empty " + codecName)
1764 << codecName
1765 << ""
1766 << QList<QString>();
1767
1768 QTest::newRow(dataTag: "one line " + codecName)
1769 << codecName
1770 << "line:"
1771 << (QList<QString>() << QStringLiteral("line:"));
1772
1773 QTest::newRow(dataTag: "one ten-byte line " + codecName)
1774 << codecName
1775 << "10letters:"
1776 << (QList<QString>() << QStringLiteral("10letters:"));
1777
1778 QTest::newRow(dataTag: "one long line " + codecName)
1779 << codecName
1780 << "one:line longer than ten characters"
1781 << (QList<QString>() << QStringLiteral("one:line longer than ten characters"));
1782
1783 QTest::newRow(dataTag: "one terminated line " + codecName)
1784 << codecName
1785 << "one:line longer than ten characters\r\n"
1786 << (QList<QString>() << QStringLiteral("one:line longer than ten characters"));
1787
1788 QTest::newRow(dataTag: "two lines " + codecName)
1789 << codecName
1790 << "two:\r\nlines:"
1791 << (QList<QString>() << QStringLiteral("two:") << QStringLiteral("lines:"));
1792
1793 QTest::newRow(dataTag: "two terminated lines " + codecName)
1794 << codecName
1795 << "two:\r\nlines:\r\n"
1796 << (QList<QString>() << QStringLiteral("two:") << QStringLiteral("lines:"));
1797
1798 QTest::newRow(dataTag: "two long lines " + codecName)
1799 << codecName
1800 << "one:line longer than ten characters\r\nanother line:\r\n"
1801 << (QList<QString>() << QStringLiteral("one:line longer than ten characters") << QStringLiteral("another line:"));
1802
1803 QTest::newRow(dataTag: "two full lines " + codecName)
1804 << codecName
1805 << "10letters:\r\n8letter:\r\n"
1806 << (QList<QString>() << QStringLiteral("10letters:") << QStringLiteral("8letter:"));
1807
1808 QTest::newRow(dataTag: "a nine-byte line " + codecName)
1809 << codecName
1810 << "9letters:\r\nanother:line\r\n"
1811 << (QList<QString>() << QStringLiteral("9letters:") << QStringLiteral("another:line"));
1812
1813 QTest::newRow(dataTag: "a blank line " + codecName)
1814 << codecName
1815 << "one:\r\n\r\ntwo:\r\n"
1816 << (QList<QString>() << QStringLiteral("one:") << QStringLiteral("two:"));
1817
1818 QTest::newRow(dataTag: "folded lines " + codecName)
1819 << codecName
1820 << "fold:ed\r\n line\r\nsecond: line\r\n"
1821 << (QList<QString>() << QStringLiteral("fold:ed line") << QStringLiteral("second: line"));
1822
1823 QTest::newRow(dataTag: "multiply folded lines " + codecName)
1824 << codecName
1825 << "fo\r\n lded:\r\n line\r\nseco\r\n\tnd:l\r\n ine\r\n"
1826 << (QList<QString>() << QStringLiteral("folded: line") << QStringLiteral("second:line"));
1827
1828 QTest::newRow(dataTag: "fold hidden after a chunk " + codecName)
1829 << codecName
1830 << "8letter:\r\n on one line\r\n"
1831 << (QList<QString>() << QStringLiteral("8letter: on one line"));
1832
1833 QTest::newRow(dataTag: "three mac lines " + codecName)
1834 << codecName
1835 << "one:\rtwo:\rthree:\r"
1836 << (QList<QString>() << QStringLiteral("one:") << QStringLiteral("two:") << QStringLiteral("three:"));
1837
1838 // Tests a workaround to parse a certain malformed vCard
1839 QTest::newRow(dataTag: "badly wrapped lines " + codecName)
1840 << codecName
1841 << "one:line\r\ntwo\r\nthree\r\n"
1842 << (QList<QString>() << QStringLiteral("one:linetwothree"));
1843 }
1844#endif
1845}
1846
1847void tst_QVersitReader::testByteArrayInput()
1848{
1849 delete mReader;
1850 const QByteArray& oneDocument =
1851 "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John\r\nEND:VCARD\r\n";
1852
1853 mReader = new QVersitReader(oneDocument);
1854 QVERIFY(mReader->device() == 0);
1855 QVERIFY2(mReader->startReading(), QString::number(mReader->error()).toLatin1().data());
1856 QVERIFY2(mReader->waitForFinished(), QString::number(mReader->error()).toLatin1().data());
1857 QList<QVersitDocument> results = mReader->results();
1858 QCOMPARE(mReader->state(), QVersitReader::FinishedState);
1859 QCOMPARE(mReader->error(), QVersitReader::NoError);
1860 QCOMPARE(results.count(),1);
1861 QVersitDocument result = results.first();
1862 QCOMPARE(result.type(), QVersitDocument::VCard21Type);
1863 QList<QVersitProperty> properties = result.properties();
1864 QCOMPARE(properties.length(), 1);
1865 QCOMPARE(properties.first().name(), QStringLiteral("FN"));
1866 QCOMPARE(properties.first().value(), QStringLiteral("John"));
1867}
1868
1869void tst_QVersitReader::testRemoveBackSlashEscaping()
1870{
1871#ifndef QT_BUILD_INTERNAL
1872 QSKIP("Testing private API");
1873#else
1874 // Empty string
1875 QString input;
1876 QVersitReaderPrivate::removeBackSlashEscaping(text: &input);
1877 QCOMPARE(input,QString());
1878
1879 // Nothing to escape in the string
1880 input = QStringLiteral("Nothing to escape");
1881 QVersitReaderPrivate::removeBackSlashEscaping(text: &input);
1882 QCOMPARE(input,QStringLiteral("Nothing to escape"));
1883
1884 // Line break, semicolon, backslash and comma in the string
1885 input = QStringLiteral("These should be unescaped \\n \\N \\; \\, \\\\");
1886 QVersitReaderPrivate::removeBackSlashEscaping(text: &input);
1887 QCOMPARE(input, QStringLiteral("These should be unescaped \r\n \r\n ; , \\"));
1888
1889 // Don't remove escaping within quotes
1890 input = QStringLiteral("\"Quoted \\n \\N \\; \\,\"");
1891 QVersitReaderPrivate::removeBackSlashEscaping(text: &input);
1892 QCOMPARE(input, QStringLiteral("\"Quoted \\n \\N \\; \\,\""));
1893#endif
1894}
1895
1896QTEST_MAIN(tst_QVersitReader)
1897
1898

source code of qtpim/tests/auto/versit/qversitreader/tst_qversitreader.cpp