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