| 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 | |