cutelyst 4.3.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
utils.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2015-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "utils.h"
6
7#include <QTextStream>
8#include <QVector>
9
10using namespace Cutelyst;
11
12QByteArray buildTableDivision(const QVector<int> &columnsSize)
13{
14 QByteArray buffer;
16 for (int i = 0; i < columnsSize.size(); ++i) {
17 if (i) {
18 out << '+';
19 } else {
20 out << '.';
21 }
22 out << QByteArray().fill('-', columnsSize[i] + 2).data();
23 }
24 out << '.';
25
26 return buffer;
27}
28
29QByteArray Utils::buildTable(const QVector<QStringList> &table,
30 const QStringList &headers,
31 const QString &title)
32{
33 QByteArray buffer;
34 QVector<int> columnsSize;
35
36 if (!headers.isEmpty()) {
37 for (const QString &header : headers) {
38 columnsSize.push_back(header.size());
39 }
40 } else {
41 for (const QStringList &rows : table) {
42 if (columnsSize.empty()) {
43 for (const QString &row : rows) {
44 columnsSize.push_back(row.size());
45 }
46 } else if (rows.size() != columnsSize.size()) {
47 qFatal("Incomplete table");
48 }
49 }
50 }
51
52 for (const QStringList &row : table) {
53 if (row.size() > columnsSize.size()) {
54 qFatal("Incomplete table");
55 break;
56 }
57
58 for (int i = 0; i < row.size(); ++i) {
59 columnsSize[i] = qMax(columnsSize[i], row[i].size());
60 }
61 }
62
63 // printing
65
66 out.setFieldAlignment(QTextStream::AlignLeft);
67 QByteArray div = buildTableDivision(columnsSize);
68
69 if (!title.isEmpty()) {
70 out << title << '\n';
71 }
72
73 // Top line
74 out << div << '\n';
75
76 if (!headers.isEmpty()) {
77 // header titles
78 for (int i = 0; i < headers.size(); ++i) {
79 out << "| ";
80
81 out.setFieldWidth(columnsSize[i]);
82 out << headers[i];
83
84 out.setFieldWidth(0);
85 out << ' ';
86 }
87 out << '|' << '\n';
88
89 // header bottom line
90 out << div << '\n';
91 }
92
93 for (const QStringList &row : table) {
94 // content table
95 for (int i = 0; i < row.size(); ++i) {
96 out << "| ";
97
98 out.setFieldWidth(columnsSize[i]);
99 out << row[i];
100
101 out.setFieldWidth(0);
102 out << ' ';
103 }
104 out << '|' << '\n';
105 }
106
107 // table bottom line
108 out << div;
109
110 return buffer;
111}
112
113QString Utils::decodePercentEncoding(QString *s)
114{
115 if (s->isEmpty()) {
116 return *s;
117 }
118
119 QByteArray ba = s->toLatin1();
120
121 char *data = ba.data();
122 const char *inputPtr = data;
123
124 const int len = ba.count();
125 bool skipUtf8 = true;
126 int outlen = 0;
127 for (int i = 0; i < len; ++i, ++outlen) {
128 const char c = inputPtr[i];
129 if (c == '%' && i + 2 < len) {
130 int a = inputPtr[++i];
131 int b = inputPtr[++i];
132
133 if (a >= '0' && a <= '9')
134 a -= '0';
135 else if (a >= 'a' && a <= 'f')
136 a = a - 'a' + 10;
137 else if (a >= 'A' && a <= 'F')
138 a = a - 'A' + 10;
139
140 if (b >= '0' && b <= '9')
141 b -= '0';
142 else if (b >= 'a' && b <= 'f')
143 b = b - 'a' + 10;
144 else if (b >= 'A' && b <= 'F')
145 b = b - 'A' + 10;
146
147 *data++ = (char) ((a << 4) | b);
148 skipUtf8 = false;
149 } else if (c == '+') {
150 *data++ = ' ';
151 } else {
152 *data++ = c;
153 }
154 }
155
156 if (skipUtf8) {
157 return *s;
158 }
159
160 return QString::fromUtf8(ba.data(), outlen);
161}
162
163ParamsMultiMap Utils::decodePercentEncoding(char *data, int len)
164{
165 ParamsMultiMap ret;
166 if (len <= 0) {
167 return ret;
168 }
169
170 QString key;
171
172 const char *inputPtr = data;
173
174 bool hasKey = false;
175 bool skipUtf8 = true;
176 char *from = data;
177 int outlen = 0;
178
179 auto processKeyPair = [&] {
180 if (hasKey) {
181 if ((data - from) == 0) {
182 if (!key.isEmpty()) {
183 ret.insert(key, {});
184 }
185 } else {
186 ret.insert(key,
187 skipUtf8 ? QString::fromLatin1(from, data - from)
188 : QString::fromUtf8(from, data - from));
189 }
190 } else if ((data - from) > 0) {
191 ret.insert(skipUtf8 ? QString::fromLatin1(from, data - from)
192 : QString::fromUtf8(from, data - from),
193 {});
194 }
195 };
196
197 for (int i = 0; i < len; ++i, ++outlen) {
198 const char c = inputPtr[i];
199 if (c == '%' && i + 2 < len) {
200 int a = inputPtr[++i];
201 int b = inputPtr[++i];
202
203 if (a >= '0' && a <= '9')
204 a -= '0';
205 else if (a >= 'a' && a <= 'f')
206 a = a - 'a' + 10;
207 else if (a >= 'A' && a <= 'F')
208 a = a - 'A' + 10;
209
210 if (b >= '0' && b <= '9')
211 b -= '0';
212 else if (b >= 'a' && b <= 'f')
213 b = b - 'a' + 10;
214 else if (b >= 'A' && b <= 'F')
215 b = b - 'A' + 10;
216
217 *data++ = (char) ((a << 4) | b);
218 skipUtf8 = false;
219 } else if (c == '+') {
220 *data++ = ' ';
221 } else if (c == '=') {
222 key = skipUtf8 ? QString::fromLatin1(from, data - from)
223 : QString::fromUtf8(from, data - from);
224 from = data;
225 hasKey = true;
226 skipUtf8 = true; // reset
227 } else if (c == '&') {
228 processKeyPair();
229 key.clear();
230 hasKey = false;
231 from = data;
232 skipUtf8 = true; // reset
233 } else {
234 *data++ = c;
235 }
236 }
237
238 processKeyPair();
239
240 return ret;
241}
242
243QString Utils::decodePercentEncoding(QByteArray *ba)
244{
245 if (ba->isEmpty()) {
246 return {};
247 }
248
249 char *data = ba->data();
250 const char *inputPtr = data;
251
252 int len = ba->count();
253 bool skipUtf8 = true;
254 int outlen = 0;
255 for (int i = 0; i < len; ++i, ++outlen) {
256 const char c = inputPtr[i];
257 if (c == '%' && i + 2 < len) {
258 int a = inputPtr[++i];
259 int b = inputPtr[++i];
260
261 if (a >= '0' && a <= '9')
262 a -= '0';
263 else if (a >= 'a' && a <= 'f')
264 a = a - 'a' + 10;
265 else if (a >= 'A' && a <= 'F')
266 a = a - 'A' + 10;
267
268 if (b >= '0' && b <= '9')
269 b -= '0';
270 else if (b >= 'a' && b <= 'f')
271 b = b - 'a' + 10;
272 else if (b >= 'A' && b <= 'F')
273 b = b - 'A' + 10;
274
275 *data++ = (char) ((a << 4) | b);
276 skipUtf8 = false;
277 } else if (c == '+') {
278 *data++ = ' ';
279 } else {
280 *data++ = c;
281 }
282 }
283
284 if (skipUtf8) {
285 return QString::fromLatin1(ba->data(), outlen);
286 } else {
287 return QString::fromUtf8(ba->data(), outlen);
288 }
289}
290
291std::chrono::microseconds Utils::durationFromString(QStringView str, bool *ok)
292{
294 QString digitPart;
295 QString unitPart;
296 bool valid = true;
297 // NOLINTBEGIN(bugprone-branch-clone)
298 for (const QChar ch : str) {
299 if (ch >= u'0' && ch <= u'9') {
300 if (digitPart.isEmpty() && unitPart.isEmpty()) {
301 // we are at the beginning of a new part
302 digitPart.append(ch);
303 } else if (digitPart.isEmpty() && !unitPart.isEmpty()) {
304 // wrong order
305 valid = false;
306 break;
307 } else if (!digitPart.isEmpty() && unitPart.isEmpty()) {
308 // we are still in the digit part
309 digitPart.append(ch);
310 } else if (!digitPart.isEmpty() && !unitPart.isEmpty()) {
311 // we start a new part
312 parts.emplace_back(digitPart, unitPart);
313 digitPart.clear();
314 unitPart.clear();
315 digitPart.append(ch);
316 }
317 } else if ((ch >= u'a' && ch <= u'z') || ch == u'M') {
318 if (digitPart.isEmpty() && unitPart.isEmpty()) {
319 // something is wrong with a digitless unit
320 valid = false;
321 break;
322 } else if (digitPart.isEmpty() && !unitPart.isEmpty()) {
323 // it should not be possible to be here
324 valid = false;
325 break;
326 } else if (!digitPart.isEmpty() && unitPart.isEmpty()) {
327 // we start adding the unit
328 unitPart.append(ch);
329 } else if (!digitPart.isEmpty() && !unitPart.isEmpty()) {
330 // normal operation
331 unitPart.append(ch);
332 }
333 }
334 }
335 // NOLINTEND(bugprone-branch-clone)
336
337 if (!valid) {
338 if (ok) {
339 *ok = false;
340 }
341 return std::chrono::microseconds::zero();
342 }
343
344 if (!digitPart.isEmpty()) {
345 parts.emplace_back(digitPart, unitPart);
346 }
347
348 if (parts.empty()) {
349 if (ok) {
350 *ok = false;
351 }
352 return std::chrono::microseconds::zero();
353 }
354
355 std::chrono::microseconds ms = std::chrono::microseconds::zero();
356
357 for (const std::pair<QString, QString> &p : parts) {
358 bool _ok = false;
359 const qulonglong dur = p.first.toULongLong(&_ok);
360 if (!_ok) {
361 valid = false;
362 break;
363 }
364
365 if (p.second == u"usec" || p.second == u"us") {
366 ms += std::chrono::microseconds{dur};
367 } else if (p.second == u"msec" || p.second == u"ms") {
368 ms += std::chrono::milliseconds{dur};
369 } else if (p.second == u"seconds" || p.second == u"second" || p.second == u"sec" ||
370 p.second == u"s" || p.second.isEmpty()) {
371 ms += std::chrono::seconds{dur};
372 } else if (p.second == u"minutes" || p.second == u"minute" || p.second == u"min" ||
373 p.second == u"m") {
374 ms += std::chrono::minutes{dur};
375 } else if (p.second == u"hours" || p.second == u"hour" || p.second == u"hr" ||
376 p.second == u"h") {
377 ms += std::chrono::hours{dur};
378 } else if (p.second == u"days" || p.second == u"day" || p.second == u"d") {
379 ms += std::chrono::days{dur};
380 } else if (p.second == u"weeks" || p.second == u"week" || p.second == u"w") {
381 ms += std::chrono::weeks{dur};
382 } else if (p.second == u"months" || p.second == u"month" || p.second == u"M") {
383 ms += std::chrono::months{dur};
384 } else if (p.second == u"years" || p.second == u"year" || p.second == u"y") {
385 ms += std::chrono::years{dur};
386 } else {
387 valid = false;
388 break;
389 }
390 }
391
392 if (!valid) {
393 if (ok) {
394 *ok = false;
395 }
396 return std::chrono::microseconds::zero();
397 }
398
399 if (ok) {
400 *ok = true;
401 }
402
403 return ms;
404}
CUTELYST_LIBRARY std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
Definition utils.cpp:291
The Cutelyst namespace holds all public Cutelyst API.
qsizetype count() const const
char * data()
QByteArray & fill(char ch, qsizetype size)
bool isEmpty() const const
QList::reference emplace_back(Args &&... args)
bool empty() const const
bool isEmpty() const const
void push_back(QList::parameter_type value)
qsizetype size() const const
QMultiMap::iterator insert(QMultiMap::const_iterator pos, const Key &key, const T &value)
QString & append(QChar ch)
void clear()
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QByteArray toLatin1() const const