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 | |
43 | QT_BEGIN_NAMESPACE_VERSIT |
44 | |
45 | /*! Constructs a writer. */ |
46 | QVCard21Writer::QVCard21Writer(QVersitDocument::VersitType type) : QVersitDocumentWriter(type) |
47 | { |
48 | } |
49 | |
50 | QTextEncoder* 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. */ |
62 | QVCard21Writer::~QVCard21Writer() |
63 | { |
64 | } |
65 | |
66 | /*! |
67 | * Encodes the \a property and writes it to the device. |
68 | */ |
69 | void 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 */ |
154 | bool 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. */ |
167 | void 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 | |
184 | int 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 | |
196 | bool 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 */ |
202 | void 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 | */ |
210 | void 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 | |
232 | bool 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 | */ |
246 | bool 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 | */ |
266 | bool 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 | |
276 | QT_END_NAMESPACE_VERSIT |
277 | |