1 /*
2 * Copyright (C) 2008-2021 The QXmpp developers
3 *
4 * Authors:
5 * Manjeet Dahiya
6 * Jeremy Lainé
7 *
8 * Source:
9 * https://github.com/qxmpp-project/qxmpp
10 *
11 * This file is a part of QXmpp library.
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
22 *
23 */
24
25 #include "QXmppUtils.h"
26
27 #include "QXmppLogger.h"
28
29 #include <QBuffer>
30 #include <QByteArray>
31 #include <QCryptographicHash>
32 #include <QDateTime>
33 #include <QDebug>
34 #include <QDomElement>
35 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
36 #include <QRandomGenerator>
37 #endif
38 #include <QRegularExpression>
39 #include <QString>
40 #include <QStringList>
41 #include <QUuid>
42 #include <QXmlStreamWriter>
43
44 // adapted from public domain source by Ross Williams and Eric Durbin
45 // FIXME : is this valid for big-endian machines?
46 static quint32 crctable[256] = {
47 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL,
48 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L,
49 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L,
50 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L,
51 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL,
52 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L,
53 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL,
54 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L,
55 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L,
56 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL,
57 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L,
58 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L,
59 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L,
60 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL,
61 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L,
62 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL,
63 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL,
64 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L,
65 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L,
66 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L,
67 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL,
68 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L,
69 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL,
70 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L,
71 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L,
72 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL,
73 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L,
74 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L,
75 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L,
76 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL,
77 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L,
78 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL,
79 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL,
80 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L,
81 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L,
82 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L,
83 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL,
84 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L,
85 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL,
86 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L,
87 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L,
88 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL,
89 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L,
90 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L,
91 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L,
92 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL,
93 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L,
94 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL,
95 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL,
96 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L,
97 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L,
98 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L,
99 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL,
100 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L,
101 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL,
102 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L,
103 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L,
104 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL,
105 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L,
106 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L,
107 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L,
108 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL,
109 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L,
110 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL
111 };
112
113 ///
114 /// Parses a date-time from a string according to
115 /// \xep{0082}: XMPP Date and Time Profiles.
116 ///
datetimeFromString(const QString & str)117 QDateTime QXmppUtils::datetimeFromString(const QString &str)
118 {
119 // Qt::ISODate parses milliseconds, but doesn't output them
120 return QDateTime::fromString(str, Qt::ISODate).toUTC();
121 }
122
123 ///
124 /// Serializes a date-time to a string according to
125 /// \xep{0082}: XMPP Date and Time Profiles.
126 ///
datetimeToString(const QDateTime & dt)127 QString QXmppUtils::datetimeToString(const QDateTime &dt)
128 {
129 if (dt.time().msec())
130 return dt.toUTC().toString(Qt::ISODateWithMs);
131 return dt.toUTC().toString(Qt::ISODate);
132 }
133
134 ///
135 /// Parses a timezone offset (in seconds) from a string according to
136 /// \xep{0082}: XMPP Date and Time Profiles.
137 ///
timezoneOffsetFromString(const QString & str)138 int QXmppUtils::timezoneOffsetFromString(const QString &str)
139 {
140 static const QRegularExpression timezoneRegex(QStringLiteral("(Z|([+-])([0-9]{2}):([0-9]{2}))"));
141
142 const auto match = timezoneRegex.match(str);
143 if (!match.hasMatch())
144 return 0;
145
146 // No offset from UTC
147 if (match.captured(1) == u'Z')
148 return 0;
149
150 // Calculate offset
151 const int offset = match.captured(3).toInt() * 3600 +
152 match.captured(4).toInt() * 60;
153
154 if (match.captured(2) == u'-')
155 return -offset;
156 return offset;
157 }
158
159 ///
160 /// Serializes a timezone offset (in seconds) to a string according to
161 /// \xep{0082}: XMPP Date and Time Profiles.
162 ///
timezoneOffsetToString(int secs)163 QString QXmppUtils::timezoneOffsetToString(int secs)
164 {
165 if (!secs)
166 return QStringLiteral("Z");
167
168 const QTime tzoTime = QTime(0, 0, 0).addSecs(qAbs(secs));
169 return (secs < 0 ? QStringLiteral("-") : QStringLiteral("+")) + tzoTime.toString(QStringLiteral("hh:mm"));
170 }
171
172 /// Returns the domain for the given \a jid.
173
jidToDomain(const QString & jid)174 QString QXmppUtils::jidToDomain(const QString &jid)
175 {
176 return jidToBareJid(jid).split(QStringLiteral("@")).last();
177 }
178
179 /// Returns the resource for the given \a jid.
180
jidToResource(const QString & jid)181 QString QXmppUtils::jidToResource(const QString &jid)
182 {
183 const int pos = jid.indexOf(QChar('/'));
184 if (pos < 0)
185 return QString();
186 return jid.mid(pos + 1);
187 }
188
189 /// Returns the user for the given \a jid.
190
jidToUser(const QString & jid)191 QString QXmppUtils::jidToUser(const QString &jid)
192 {
193 const int pos = jid.indexOf(QChar('@'));
194 if (pos < 0)
195 return QString();
196 return jid.left(pos);
197 }
198
199 /// Returns the bare jid (i.e. without resource) for the given \a jid.
200
jidToBareJid(const QString & jid)201 QString QXmppUtils::jidToBareJid(const QString &jid)
202 {
203 const int pos = jid.indexOf(QChar('/'));
204 if (pos < 0)
205 return jid;
206 return jid.left(pos);
207 }
208
209 /// Calculates the CRC32 checksum for the given input.
210
generateCrc32(const QByteArray & in)211 quint32 QXmppUtils::generateCrc32(const QByteArray &in)
212 {
213 quint32 result = 0xffffffff;
214 for (char n : in)
215 result = (result >> 8) ^ (crctable[(result & 0xff) ^ (quint8)n]);
216 return result ^= 0xffffffff;
217 }
218
generateHmac(QCryptographicHash::Algorithm algorithm,const QByteArray & key,const QByteArray & text)219 static QByteArray generateHmac(QCryptographicHash::Algorithm algorithm, const QByteArray &key, const QByteArray &text)
220 {
221 QCryptographicHash hasher(algorithm);
222
223 const int B = 64;
224 QByteArray kpad = key + QByteArray(B - key.size(), 0);
225
226 QByteArray ba;
227 for (int i = 0; i < B; ++i)
228 ba += kpad[i] ^ 0x5c;
229
230 QByteArray tmp;
231 for (int i = 0; i < B; ++i)
232 tmp += kpad[i] ^ 0x36;
233 hasher.addData(tmp);
234 hasher.addData(text);
235 ba += hasher.result();
236
237 hasher.reset();
238 hasher.addData(ba);
239 return hasher.result();
240 }
241
242 /// Generates the MD5 HMAC for the given \a key and \a text.
243
generateHmacMd5(const QByteArray & key,const QByteArray & text)244 QByteArray QXmppUtils::generateHmacMd5(const QByteArray &key, const QByteArray &text)
245 {
246 return generateHmac(QCryptographicHash::Md5, key, text);
247 }
248
249 /// Generates the SHA1 HMAC for the given \a key and \a text.
250
generateHmacSha1(const QByteArray & key,const QByteArray & text)251 QByteArray QXmppUtils::generateHmacSha1(const QByteArray &key, const QByteArray &text)
252 {
253 return generateHmac(QCryptographicHash::Sha1, key, text);
254 }
255
256 /// Generates a random integer x between 0 and N-1.
257 ///
258 /// \param N
259
generateRandomInteger(int N)260 int QXmppUtils::generateRandomInteger(int N)
261 {
262 Q_ASSERT(N > 0 && N <= RAND_MAX);
263 int val;
264 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
265 while (N <= (val = QRandomGenerator::global()->generate() / (RAND_MAX / N))) {
266 }
267 #else
268 while (N <= (val = qrand() / (RAND_MAX / N))) {
269 }
270 #endif
271 return val;
272 }
273
274 /// Returns a random byte array of the specified size.
275 ///
276 /// \param length
277
generateRandomBytes(int length)278 QByteArray QXmppUtils::generateRandomBytes(int length)
279 {
280 QByteArray bytes(length, 'm');
281 for (int i = 0; i < length; ++i)
282 bytes[i] = (char)generateRandomInteger(256);
283 return bytes;
284 }
285
286 ///
287 /// Creates a new stanza id in the UUID format.
288 ///
289 /// \since QXmpp 1.3
290 ///
generateStanzaUuid()291 QString QXmppUtils::generateStanzaUuid()
292 {
293 #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
294 return QUuid::createUuid().toString(QUuid::WithoutBraces);
295 #else
296 return QUuid::createUuid().toString().mid(1, 36);
297 #endif
298 }
299
300 ///
301 /// Returns a random alphanumerical string of the specified size.
302 ///
303 /// Since QXmpp 1.3 this will generate a UUID, if the specified \p length is 36
304 /// which is also the new default value. The returned string is still 36
305 /// characters long, but will contain dashes (as specified in the UUID format).
306 ///
307 /// \note It is recommended to use UUIDs for cases where IDs must be unique and
308 /// are possibly stored permanently. This can be done using
309 /// QXmppUtils::generateStanzaUuid(). However, since that function is only
310 /// available since QXmpp 1.3, you may also want to continue to use this
311 /// function because of compatibility reasons.
312 ///
313 /// \param length
314 ///
generateStanzaHash(int length)315 QString QXmppUtils::generateStanzaHash(int length)
316 {
317 if (length == 36)
318 return QXmppUtils::generateStanzaUuid();
319
320 const QString somechars = QStringLiteral("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
321 const int N = somechars.size();
322 QString hashResult;
323 for (int idx = 0; idx < length; ++idx)
324 hashResult += somechars[generateRandomInteger(N)];
325 return hashResult;
326 }
327
helperToXmlAddAttribute(QXmlStreamWriter * stream,const QString & name,const QString & value)328 void helperToXmlAddAttribute(QXmlStreamWriter *stream, const QString &name,
329 const QString &value)
330 {
331 if (!value.isEmpty())
332 stream->writeAttribute(name, value);
333 }
334
helperToXmlAddTextElement(QXmlStreamWriter * stream,const QString & name,const QString & value)335 void helperToXmlAddTextElement(QXmlStreamWriter *stream, const QString &name,
336 const QString &value)
337 {
338 if (!value.isEmpty())
339 stream->writeTextElement(name, value);
340 else
341 stream->writeEmptyElement(name);
342 }
343