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