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 QtVersit module 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#include "qvcard21writer_p.h"
35
36#include <QtCore/qtextcodec.h>
37#include <QtCore/qvariant.h>
38
39#include "qversitproperty.h"
40
41#include <algorithm>
42
43QT_BEGIN_NAMESPACE_VERSIT
44
45/*! Constructs a writer. */
46QVCard21Writer::QVCard21Writer(QVersitDocument::VersitType type) : QVersitDocumentWriter(type)
47{
48}
49
50QTextEncoder* QVCard21Writer::utf8Encoder()
51{
52 static QTextEncoder* encoder = 0;
53 if (encoder == 0) {
54 encoder = QTextCodec::codecForName(name: "UTF-8")->makeEncoder();
55 // Hack so the encoder doesn't output a byte order mark
56 encoder->fromUnicode(str: QString());
57 }
58 return encoder;
59}
60
61/*! Destroys a writer. */
62QVCard21Writer::~QVCard21Writer()
63{
64}
65
66/*!
67 * Encodes the \a property and writes it to the device.
68 */
69void QVCard21Writer::encodeVersitProperty(const QVersitProperty& property)
70{
71 encodeGroupsAndName(property);
72 QMultiHash<QString,QString> parameters = property.parameters();
73 QVariant variant(property.variantValue());
74
75 QString renderedValue;
76 QByteArray renderedBytes;
77
78 /* Structured values need to have their components backslash-escaped (in vCard 2.1, semicolons
79 must be escaped for compound values and commas must be escaped for list values). */
80 if (variant.type() == QVariant::StringList) {
81 QStringList values = property.variantValue().toStringList();
82 QString separator;
83 if (property.valueType() == QVersitProperty::CompoundType) {
84 separator = QStringLiteral(";");
85 } else {
86 if (property.valueType() != QVersitProperty::ListType) {
87 qWarning(msg: "Variant value is a QStringList but the property's value type is neither "
88 "CompoundType or ListType");
89 }
90 // Assume it's a ListType
91 separator = QStringLiteral(",");
92 }
93 QString replacement = QLatin1Char('\\') + separator;
94 QRegExp separatorRegex = QRegExp(separator);
95
96 // Check first if any of the values need to be UTF-8 encoded (if so, all of them must be
97 // UTF-8 encoded)
98 bool forceUtf8 = requiresUtf8(values);
99
100 bool first = true;
101 foreach (QString value, values) {
102 if (!(value.isEmpty() && property.valueType() == QVersitProperty::ListType)) {
103 encodeVersitValue(parameters, value, forceUtf8);
104 if (!first) {
105 renderedValue += separator;
106 }
107 renderedValue += value.replace(rx: separatorRegex, after: replacement);
108 first = false;
109 }
110 }
111 } else if (variant.type() == QVariant::String) {
112 renderedValue = variant.toString();
113 encodeVersitValue(parameters, value&: renderedValue, forceUtf8: false);
114 } else if (variant.type() == QVariant::ByteArray) {
115 parameters.replace(QStringLiteral("ENCODING"), QStringLiteral("BASE64"));
116 if (mCodecIsAsciiCompatible) // optimize by not converting to unicode
117 renderedBytes = variant.toByteArray().toBase64();
118 else
119 renderedValue = QLatin1String(variant.toByteArray().toBase64().data());
120 }
121
122 // Encode parameters
123 encodeParameters(parameters);
124
125 // Encode value
126 writeString(QStringLiteral(":"));
127 if (variant.canConvert<QVersitDocument>()) {
128 writeCrlf();
129 QVersitDocument embeddedDocument = variant.value<QVersitDocument>();
130 encodeVersitDocument(document: embeddedDocument);
131 } else if (variant.type() == QVariant::String || variant.type() == QVariant::StringList) {
132 // Some devices don't support vCard-style line folding if the property is
133 // quoted-printable-encoded. Therefore, we use QP soft linebreaks if the property is being
134 // QP-encoded, and normal vCard folding otherwise.
135 if (parameters.contains(QStringLiteral("ENCODING"), QStringLiteral("QUOTED-PRINTABLE")))
136 writeStringQp(value: renderedValue);
137 else
138 writeString(value: renderedValue);
139 } else if (variant.type() == QVariant::ByteArray) {
140 // One extra folding before the value and
141 // one extra line break after the value are needed in vCard 2.1
142 writeCrlf();
143 writeString(QStringLiteral(" "));
144 if (renderedBytes.isEmpty())
145 writeString(value: renderedValue);
146 else
147 writeBytes(value: renderedBytes);
148 writeCrlf();
149 }
150 writeCrlf();
151}
152
153/*! Returns true if and only if the current codec is incapable of encoding any of the \a values */
154bool QVCard21Writer::requiresUtf8(const QStringList& values) {
155 foreach (const QString& value, values) {
156 if (!mCodec->canEncode(value)
157 // if codec is ASCII and there is a character > U+007F in value, encode it as UTF-8
158 || (mCodecIsAscii && containsNonAscii(str: value))) {
159 return true;
160 }
161 }
162 return false;
163}
164
165/*! Performs Quoted-Printable encoding and charset encoding on \a value as per vCard 2.1 spec.
166 Returns true if the value will need to be encoded with UTF-8, false if mCodec is sufficient. */
167void QVCard21Writer::encodeVersitValue(QMultiHash<QString,QString>& parameters, QString& value,
168 bool forceUtf8)
169{
170 // Add the CHARSET parameter, if necessary and encode in UTF-8 later
171 if (forceUtf8
172 || !mCodec->canEncode(value)
173 // if codec is ASCII and there is a character > U+007F in value, encode it as UTF-8
174 || (mCodecIsAscii && containsNonAscii(str: value))) {
175 parameters.replace(QStringLiteral("CHARSET"), QStringLiteral("UTF-8"));
176 value = QString::fromLatin1(str: utf8Encoder()->fromUnicode(str: value));
177 }
178
179 // Quoted-Printable encode the value and add Quoted-Printable parameter, if necessary
180 if (quotedPrintableEncode(text&: value))
181 parameters.replace(QStringLiteral("ENCODING"), QStringLiteral("QUOTED-PRINTABLE"));
182}
183
184int sortIndexOfTypeValue(const QString& type) {
185 if ( type == QStringLiteral("CELL")
186 || type == QStringLiteral("FAX")) {
187 return 0;
188 } else if (type == QStringLiteral("HOME")
189 || type == QStringLiteral("WORK")) {
190 return 1;
191 } else {
192 return 2;
193 }
194}
195
196bool typeValueLessThan(const QString& a, const QString& b) {
197 return sortIndexOfTypeValue(type: a) < sortIndexOfTypeValue(type: b);
198}
199
200/*! Ensure CELL and FAX are at the front because they are "more important" and some vCard
201 parsers may ignore everything after the first TYPE */
202void sortTypeValues(QStringList* values)
203{
204 std::sort(first: values->begin(), last: values->end(), comp: typeValueLessThan);
205}
206
207/*!
208 * Encodes the \a parameters and writes it to the device.
209 */
210void QVCard21Writer::encodeParameters(const QMultiHash<QString,QString>& parameters)
211{
212 // Sort the parameter names to yield deterministic ordering
213 QList<QString> names = parameters.uniqueKeys();
214 std::sort(first: names.begin(), last: names.end());
215 foreach (const QString& name, names) {
216 QStringList values = parameters.values(akey: name);
217 if (name == QStringLiteral("TYPE")) {
218 // TYPE parameters should be sorted
219 sortTypeValues(values: &values);
220 }
221 foreach (const QString& value, values) {
222 writeString(QStringLiteral(";"));
223 if (name.length() > 0 && name != QStringLiteral("TYPE")) {
224 writeString(value: name);
225 writeString(QStringLiteral("="));
226 }
227 writeString(value);
228 }
229 }
230}
231
232bool QVCard21Writer::containsNonAscii(const QString& str)
233{
234 for (int i = 0; i < str.length(); i++) {
235 if (str[i].unicode() > 127)
236 return true;
237 }
238 return false;
239}
240
241/*!
242 * Encodes special characters in \a text
243 * using Quoted-Printable encoding (RFC 1521).
244 * Returns true if at least one character was encoded.
245 */
246bool QVCard21Writer::quotedPrintableEncode(QString& text)
247{
248 bool encoded = false;
249 for (int i=0; i<text.length(); i++) {
250 QChar current = text.at(i);
251 if (shouldBeQuotedPrintableEncoded(chr: current)) {
252 QString encodedStr(QString::fromLatin1(str: "=%1").
253 arg(a: current.unicode(), fieldWidth: 2, base: 16, fillChar: QLatin1Char('0')).toUpper());
254 text.replace(i, len: 1, after: encodedStr);
255 i += 2;
256 encoded = true;
257 }
258 }
259 return encoded;
260}
261
262
263/*!
264 * Checks whether the \a chr should be Quoted-Printable encoded (RFC 1521).
265 */
266bool QVCard21Writer::shouldBeQuotedPrintableEncoded(QChar chr)
267{
268 int c = chr.unicode();
269 return (c < 32 ||
270 c == '!' || c == '"' || c == '#' || c == '$' ||
271 c == '=' || c == '@' || c == '[' || c == '\\' ||
272 c == ']' || c == '^' || c == '`' ||
273 (c > 122 && c < 256));
274}
275
276QT_END_NAMESPACE_VERSIT
277

source code of qtpim/src/versit/qvcard21writer.cpp