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_qvcard21writer.h" |
37 | #ifdef QT_BUILD_INTERNAL |
38 | #include <QtVersit/private/qvcard21writer_p.h> |
39 | #endif |
40 | #include <QtVersit/qversitproperty.h> |
41 | #include <QtVersit/qversitdocument.h> |
42 | #include <QtTest/QtTest> |
43 | #include <QByteArray> |
44 | #include <QVariant> |
45 | |
46 | // This says "NOKIA" in Katakana |
47 | const QString KATAKANA_NOKIA(QString::fromUtf8(str: "\xe3\x83\x8e\xe3\x82\xad\xe3\x82\xa2" )); |
48 | |
49 | QTVERSIT_USE_NAMESPACE |
50 | |
51 | Q_DECLARE_METATYPE(QVersitProperty) |
52 | |
53 | // Because the QFETCH macro balks on the comma in QMultiHash<QString,QString> |
54 | typedef QMultiHash<QString,QString> StringHash; |
55 | Q_DECLARE_METATYPE(StringHash) |
56 | #ifdef QT_BUILD_INTERNAL |
57 | void tst_QVCard21Writer::init() |
58 | { |
59 | mWriter = new QVCard21Writer(QVersitDocument::VCard21Type); |
60 | mWriter->setCodec(QTextCodec::codecForName(name: "ISO_8859-1" )); |
61 | } |
62 | |
63 | void tst_QVCard21Writer::cleanup() |
64 | { |
65 | delete mWriter; |
66 | } |
67 | |
68 | void tst_QVCard21Writer::testEncodeVersitProperty() |
69 | { |
70 | QFETCH(QVersitProperty, property); |
71 | QFETCH(QByteArray, expectedResult); |
72 | QFETCH(QByteArray, codec); |
73 | QTextCodec* textCodec = QTextCodec::codecForName(name: codec); |
74 | QByteArray encodedProperty; |
75 | QBuffer buffer(&encodedProperty); |
76 | mWriter->setDevice(&buffer); |
77 | mWriter->setCodec(textCodec); |
78 | buffer.open(openMode: QIODevice::WriteOnly); |
79 | |
80 | mWriter->encodeVersitProperty(property); |
81 | if (encodedProperty != expectedResult) { |
82 | qDebug() << "Encoded: " << encodedProperty; |
83 | qDebug() << "Expected: " << expectedResult; |
84 | QVERIFY(false); |
85 | } |
86 | } |
87 | |
88 | void tst_QVCard21Writer::testEncodeVersitProperty_data() |
89 | { |
90 | QTest::addColumn<QVersitProperty>(name: "property" ); |
91 | QTest::addColumn<QByteArray>(name: "expectedResult" ); |
92 | QTest::addColumn<QByteArray>(name: "codec" ); |
93 | |
94 | QVersitProperty property; |
95 | QByteArray expectedResult; |
96 | QByteArray codec("ISO-8859_1" ); |
97 | |
98 | // normal case |
99 | property.setName(QString::fromLatin1(str: "FN" )); |
100 | property.setValue(QString::fromLatin1(str: "John Citizen" )); |
101 | property.setValueType(QVersitProperty::PlainType); |
102 | expectedResult = "FN:John Citizen\r\n" ; |
103 | QTest::newRow(dataTag: "No parameters" ) << property << expectedResult << codec; |
104 | |
105 | // Structured N - escaping should happen for semicolons, not for commas |
106 | property.setName(QStringLiteral("N" )); |
107 | property.setValue(QStringList() |
108 | << QStringLiteral("La;st" ) // needs to be backslash escaped |
109 | << QStringLiteral("Fi,rst" ) |
110 | << QStringLiteral("Mi:ddle" ) |
111 | << QStringLiteral("Pr\\efix" ) // needs to be QP encoded |
112 | << QStringLiteral("Suffix" )); |
113 | property.setValueType(QVersitProperty::CompoundType); |
114 | expectedResult = "N;ENCODING=QUOTED-PRINTABLE:La\\;st;Fi,rst;Mi:ddle;Pr=5Cefix;Suffix\r\n" ; |
115 | QTest::newRow(dataTag: "N property" ) << property << expectedResult << codec; |
116 | |
117 | // Structured N - there was a bug where if two fields had to be escaped, |
118 | // two ENCODING parameters were added |
119 | property.setName(QStringLiteral("N" )); |
120 | property.setValue(QStringList() |
121 | << QStringLiteral("La\\st" ) |
122 | << QStringLiteral("Fi\\rst" ) |
123 | << QString() |
124 | << QString() |
125 | << QString()); |
126 | property.setValueType(QVersitProperty::CompoundType); |
127 | expectedResult = "N;ENCODING=QUOTED-PRINTABLE:La=5Cst;Fi=5Crst;;;\r\n" ; |
128 | QTest::newRow(dataTag: "N property, double-encoded" ) << property << expectedResult << codec; |
129 | |
130 | // Structured N - one field needs to be encoded in UTF-8 while the other doesn't |
131 | // correct behaviour is to encode the whole thing in UTF-8 |
132 | property.setName(QStringLiteral("N" )); |
133 | property.setValue(QStringList() |
134 | << QString::fromUtf8(str: "\xE2\x82\xAC" ) // euro sign |
135 | << QString::fromLatin1(str: "\xA3" ) // pound sign (upper Latin-1) |
136 | << QString() |
137 | << QString() |
138 | << QString()); |
139 | property.setValueType(QVersitProperty::CompoundType); |
140 | expectedResult = "N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E2=82=AC;=C2=A3;;;\r\n" ; |
141 | QTest::newRow(dataTag: "N property, double-encoded" ) << property << expectedResult << codec; |
142 | |
143 | // Structured CATEGORIES - escaping should happen for commas, not semicolons |
144 | property.setName(QStringLiteral("CATEGORIES" )); |
145 | property.setValue(QStringList() |
146 | << QStringLiteral("re;d" ) |
147 | << QStringLiteral("gr,een" ) |
148 | << QStringLiteral("bl:ue" )); |
149 | property.setValueType(QVersitProperty::ListType); |
150 | expectedResult = "CATEGORIES:re;d,gr\\,een,bl:ue\r\n" ; |
151 | QTest::newRow(dataTag: "CATEGORIES property" ) << property << expectedResult << codec; |
152 | |
153 | // With parameter(s). No special characters in the value. |
154 | // -> No need to Quoted-Printable encode the value. |
155 | expectedResult = "TEL;HOME:123\r\n" ; |
156 | property.setName(QString::fromLatin1(str: "TEL" )); |
157 | property.setValue(QString::fromLatin1(str: "123" )); |
158 | property.insertParameter(name: QString::fromLatin1(str: "TYPE" ),value: QString::fromLatin1(str: "HOME" )); |
159 | QTest::newRow(dataTag: "With parameters, plain value" ) << property << expectedResult << codec; |
160 | |
161 | expectedResult = "EMAIL;ENCODING=QUOTED-PRINTABLE;HOME:john.citizen=40example.com\r\n" ; |
162 | property.setName(QString::fromLatin1(str: "EMAIL" )); |
163 | property.setValue(QString::fromLatin1(str: "john.citizen@example.com" )); |
164 | QTest::newRow(dataTag: "With parameters, special value" ) << property << expectedResult << codec; |
165 | |
166 | // AGENT property with parameter |
167 | expectedResult = |
168 | "AGENT;X-PARAMETER=VALUE:\r\n\ |
169 | BEGIN:VCARD\r\n\ |
170 | VERSION:2.1\r\n\ |
171 | FN:Secret Agent\r\n\ |
172 | END:VCARD\r\n\ |
173 | \r\n" ; |
174 | property.setParameters(QMultiHash<QString,QString>()); |
175 | property.setName(QString::fromLatin1(str: "AGENT" )); |
176 | property.setValue(QString()); |
177 | property.insertParameter(name: QString::fromLatin1(str: "X-PARAMETER" ),value: QString::fromLatin1(str: "VALUE" )); |
178 | QVersitDocument document(QVersitDocument::VCard21Type); |
179 | document.setComponentType(QStringLiteral("VCARD" )); |
180 | QVersitProperty embeddedProperty; |
181 | embeddedProperty.setName(QString(QString::fromLatin1(str: "FN" ))); |
182 | embeddedProperty.setValue(QString::fromLatin1(str: "Secret Agent" )); |
183 | document.addProperty(property: embeddedProperty); |
184 | property.setValue(QVariant::fromValue(value: document)); |
185 | QTest::newRow(dataTag: "AGENT property" ) << property << expectedResult << codec; |
186 | |
187 | // Value is base64 encoded. |
188 | // Check that the extra folding and the line break are added |
189 | QByteArray value("value" ); |
190 | expectedResult = "Springfield.HOUSE.PHOTO;ENCODING=BASE64:\r\n " + value.toBase64() + "\r\n\r\n" ; |
191 | QStringList groups(QString::fromLatin1(str: "Springfield" )); |
192 | groups.append(t: QString::fromLatin1(str: "HOUSE" )); |
193 | property.setGroups(groups); |
194 | property.setParameters(QMultiHash<QString,QString>()); |
195 | property.setName(QString::fromLatin1(str: "PHOTO" )); |
196 | property.setValue(value); |
197 | QTest::newRow(dataTag: "base64 encoded" ) << property << expectedResult << codec; |
198 | |
199 | // Characters other than ASCII: |
200 | // Note: KATAKANA_NOKIA is defined as: QString::fromUtf8("\xe3\x83\x8e\xe3\x82\xad\xe3\x82\xa2") |
201 | // The expected behaviour is to convert to UTF8, then encode with quoted-printable. |
202 | // Because the result overflows one line, it should be split onto two lines using a |
203 | // quoted-printable soft line break (EQUALS-CR-LF). (Note: Versit soft line breaks |
204 | // (CR-LF-SPACE) are not supported by the native Symbian vCard importers). |
205 | expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E3=83=8E=E3=82=AD=E3=82=A2=E3=\r\n" |
206 | "=83=8E=E3=82=AD=E3=82=A2\r\n" ; |
207 | property = QVersitProperty(); |
208 | property.setName(QStringLiteral("ORG" )); |
209 | property.setValue(KATAKANA_NOKIA + KATAKANA_NOKIA); |
210 | QTest::newRow(dataTag: "non-ASCII 1" ) << property << expectedResult << codec; |
211 | |
212 | expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:a=E3=83=8E=E3=82=AD=E3=82=A2=E3=\r\n" |
213 | "=83=8E=E3=82=AD=E3=82=A2\r\n" ; |
214 | property = QVersitProperty(); |
215 | property.setName(QStringLiteral("ORG" )); |
216 | property.setValue("a" + KATAKANA_NOKIA + KATAKANA_NOKIA); |
217 | QTest::newRow(dataTag: "non-ASCII 2" ) << property << expectedResult << codec; |
218 | |
219 | expectedResult = "ORG;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:aa=E3=83=8E=E3=82=AD=E3=82=A2=\r\n" |
220 | "=E3=83=8E=E3=82=AD=E3=82=A2\r\n" ; |
221 | property = QVersitProperty(); |
222 | property.setName(QStringLiteral("ORG" )); |
223 | property.setValue("aa" + KATAKANA_NOKIA + KATAKANA_NOKIA); |
224 | QTest::newRow(dataTag: "non-ASCII 3" ) << property << expectedResult << codec; |
225 | |
226 | // In Shift-JIS codec. |
227 | QTextCodec* jisCodec = QTextCodec::codecForName(name: "Shift-JIS" ); |
228 | expectedResult = jisCodec->fromUnicode( |
229 | QStringLiteral("ORG:" ) + KATAKANA_NOKIA + QStringLiteral("\r\n" )); |
230 | property = QVersitProperty(); |
231 | property.setName(QStringLiteral("ORG" )); |
232 | property.setValue(KATAKANA_NOKIA); |
233 | QTest::newRow(dataTag: "JIS codec" ) << property << expectedResult << QByteArray("Shift-JIS" ); |
234 | } |
235 | |
236 | void tst_QVCard21Writer::testEncodeParameters() |
237 | { |
238 | QFETCH(StringHash, parameters); |
239 | QFETCH(QByteArray, expected); |
240 | |
241 | QByteArray encodedParameters; |
242 | QBuffer buffer(&encodedParameters); |
243 | mWriter->setDevice(&buffer); |
244 | buffer.open(openMode: QIODevice::WriteOnly); |
245 | |
246 | // No parameters |
247 | mWriter->encodeParameters(parameters); |
248 | if (encodedParameters != expected) { |
249 | qDebug() << "Encoded: " << encodedParameters; |
250 | qDebug() << "Expected: " << expected; |
251 | QVERIFY(false); |
252 | } |
253 | } |
254 | |
255 | void tst_QVCard21Writer::testEncodeParameters_data() |
256 | { |
257 | QTest::addColumn< QMultiHash<QString, QString> >(name: "parameters" ); |
258 | QTest::addColumn<QByteArray>(name: "expected" ); |
259 | |
260 | QMultiHash<QString,QString> parameters; |
261 | |
262 | QTest::newRow(dataTag: "No parameters" ) << parameters << QByteArray("" ); |
263 | |
264 | parameters.insert(QStringLiteral("TYPE" ), avalue: QString::fromLatin1(str: "HOME" )); |
265 | QTest::newRow(dataTag: "One TYPE parameter" ) << parameters << QByteArray(";HOME" ); |
266 | |
267 | // HOME should appear before VOICE because it is more "important" and some vCard |
268 | // parsers may ignore everything after the first TYPE |
269 | parameters.insert(QStringLiteral("TYPE" ), avalue: QString::fromLatin1(str: "VOICE" )); |
270 | QTest::newRow(dataTag: "Two TYPE parameters" ) << parameters << QByteArray(";HOME;VOICE" ); |
271 | |
272 | parameters.clear(); |
273 | parameters.insert(QStringLiteral("ENCODING" ), avalue: QString::fromLatin1(str: "8BIT" )); |
274 | QTest::newRow(dataTag: "One ENCODING parameter" ) << parameters << QByteArray(";ENCODING=8BIT" ); |
275 | |
276 | parameters.insert(akey: QString::fromLatin1(str: "X-PARAM" ),avalue: QString::fromLatin1(str: "VALUE" )); |
277 | QTest::newRow(dataTag: "Two parameters" ) << parameters << QByteArray(";ENCODING=8BIT;X-PARAM=VALUE" ); |
278 | |
279 | parameters.clear(); |
280 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("VOICE" )); |
281 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("CELL" )); |
282 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("MODEM" )); |
283 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("CAR" )); |
284 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("VIDEO" )); |
285 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("FAX" )); |
286 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("BBS" )); |
287 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("PAGER" )); |
288 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("HOME" )); |
289 | parameters.insert(QStringLiteral("TYPE" ), QStringLiteral("WORK" )); |
290 | // Ensure CELL and FAX are at the front because they are "more important" and some vCard |
291 | // parsers may ignore everything after the first TYPE |
292 | // Ensure WORK and HOME come next. |
293 | // Besides these conditions, there are no other ordering constraints. The data here is simply |
294 | // what the writer produces (as dictated by its internal data structures). |
295 | QTest::newRow(dataTag: "TYPE parameters order" ) << parameters |
296 | << QByteArray(";FAX;CELL;WORK;HOME;PAGER;BBS;VIDEO;CAR;MODEM;VOICE" ); |
297 | } |
298 | |
299 | void tst_QVCard21Writer::testEncodeGroupsAndName() |
300 | { |
301 | QVersitProperty property; |
302 | QByteArray result; |
303 | QBuffer buffer(&result); |
304 | mWriter->setDevice(&buffer); |
305 | buffer.open(openMode: QIODevice::WriteOnly); |
306 | |
307 | // No groups |
308 | |
309 | property.setName(QString::fromLatin1(str: "name" )); |
310 | QByteArray expected("NAME" ); |
311 | mWriter->encodeGroupsAndName(property); |
312 | QCOMPARE(result, expected); |
313 | |
314 | // One group |
315 | mWriter->writeCrlf(); // so it doesn't start folding |
316 | buffer.close(); |
317 | result.clear(); |
318 | buffer.open(openMode: QIODevice::WriteOnly); |
319 | property.setGroups(QStringList(QString::fromLatin1(str: "group" ))); |
320 | expected = "group.NAME" ; |
321 | mWriter->encodeGroupsAndName(property); |
322 | QCOMPARE(result, expected); |
323 | |
324 | // Two groups |
325 | mWriter->writeCrlf(); // so it doesn't start folding |
326 | buffer.close(); |
327 | result.clear(); |
328 | buffer.open(openMode: QIODevice::WriteOnly); |
329 | QStringList groups(QString::fromLatin1(str: "group1" )); |
330 | groups.append(t: QString::fromLatin1(str: "group2" )); |
331 | property.setGroups(groups); |
332 | expected = "group1.group2.NAME" ; |
333 | mWriter->encodeGroupsAndName(property); |
334 | QCOMPARE(result, expected); |
335 | } |
336 | |
337 | |
338 | void tst_QVCard21Writer::testQuotedPrintableEncode() |
339 | { |
340 | QByteArray encodedBytes; |
341 | |
342 | // Nothing to encode |
343 | QString nothingToEncode(QStringLiteral("nothing to encode" )); |
344 | QVERIFY(!mWriter->quotedPrintableEncode(nothingToEncode)); |
345 | |
346 | // Special characters |
347 | QString inputOutput(QStringLiteral("\n" )); |
348 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
349 | QCOMPARE(inputOutput, QStringLiteral("=0A" )); |
350 | inputOutput = QStringLiteral("\r" ); |
351 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
352 | QCOMPARE(inputOutput, QStringLiteral("=0D" )); |
353 | inputOutput = QStringLiteral("!" ); |
354 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
355 | QCOMPARE(inputOutput, QStringLiteral("=21" )); |
356 | inputOutput = QStringLiteral("\"" ); |
357 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
358 | QCOMPARE(inputOutput, QStringLiteral("=22" )); |
359 | inputOutput = QStringLiteral("#" ); |
360 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
361 | QCOMPARE(inputOutput, QStringLiteral("=23" )); |
362 | inputOutput = QStringLiteral("$" ); |
363 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
364 | QCOMPARE(inputOutput, QStringLiteral("=24" )); |
365 | inputOutput = QStringLiteral("=" ); |
366 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
367 | QCOMPARE(inputOutput, QStringLiteral("=3D" )); |
368 | inputOutput = QStringLiteral("@" ); |
369 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
370 | QCOMPARE(inputOutput, QStringLiteral("=40" )); |
371 | inputOutput = QStringLiteral("[" ); |
372 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
373 | QCOMPARE(inputOutput, QStringLiteral("=5B" )); |
374 | inputOutput = QStringLiteral("\\" ); |
375 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
376 | QCOMPARE(inputOutput, QStringLiteral("=5C" )); |
377 | inputOutput = QStringLiteral("]" ); |
378 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
379 | QCOMPARE(inputOutput, QStringLiteral("=5D" )); |
380 | inputOutput = QStringLiteral("^" ); |
381 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
382 | QCOMPARE(inputOutput, QStringLiteral("=5E" )); |
383 | inputOutput = QStringLiteral("`" ); |
384 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
385 | QCOMPARE(inputOutput, QStringLiteral("=60" )); |
386 | inputOutput = QStringLiteral("{" ); |
387 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
388 | QCOMPARE(inputOutput, QStringLiteral("=7B" )); |
389 | inputOutput = QStringLiteral("|" ); |
390 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
391 | QCOMPARE(inputOutput, QStringLiteral("=7C" )); |
392 | inputOutput = QStringLiteral("}" ); |
393 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
394 | QCOMPARE(inputOutput, QStringLiteral("=7D" )); |
395 | inputOutput = QStringLiteral("~" ); |
396 | QVERIFY(mWriter->quotedPrintableEncode(inputOutput)); |
397 | QCOMPARE(inputOutput, QStringLiteral("=7E" )); |
398 | } |
399 | #endif |
400 | |
401 | QTEST_MAIN(tst_QVCard21Writer) |
402 | |