cutelyst 5.1.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
185 auto processKeyPair = [&] {
186 if (hasKey) {
187 if ((data - from) == 0) {
188 if (!key.isEmpty()) {
189 ret.insert(key, {});
190 }
191 } else {
192 ret.insert(key,
193 skipUtf8 ? QString::fromLatin1(from, data - from)
194 : QString::fromUtf8(from, data - from));
195 }
196 } else if ((data - from) > 0) {
197 ret.insert(skipUtf8 ? QString::fromLatin1(from, data - from)
198 : QString::fromUtf8(from, data - from),
199 {});
200 }
201 };
202
203 for (int i = 0; i < len; ++i) {
204 const char c = inputPtr[i];
205 if (c == '%' && i + 2 < len) {
206 int a = static_cast<int>(static_cast<unsigned char>(inputPtr[++i]));
207 int b = static_cast<int>(static_cast<unsigned char>(inputPtr[++i]));
208
209 if (a >= '0' && a <= '9') {
210 a -= '0';
211 } else if (a >= 'a' && a <= 'f') {
212 a = a - 'a' + 10;
213 } else if (a >= 'A' && a <= 'F') {
214 a = a - 'A' + 10;
215 }
216
217 if (b >= '0' && b <= '9') {
218 b -= '0';
219 } else if (b >= 'a' && b <= 'f') {
220 b = b - 'a' + 10;
221 } else if (b >= 'A' && b <= 'F') {
222 b = b - 'A' + 10;
223 }
224
225 *data++ = (char) ((a << 4) | b);
226 skipUtf8 = false;
227 } else if (c == '+') {
228 *data++ = ' ';
229 } else if (c == '=') {
230 key = skipUtf8 ? QString::fromLatin1(from, data - from)
231 : QString::fromUtf8(from, data - from);
232 from = data;
233 hasKey = true;
234 skipUtf8 = true; // reset
235 } else if (c == '&') {
236 processKeyPair();
237 key.clear();
238 hasKey = false;
239 from = data;
240 skipUtf8 = true; // reset
241 } else {
242 *data++ = c;
243 }
244 }
245
246 processKeyPair();
247
248 return ret;
249}
250
251QString Utils::decodePercentEncoding(QByteArray *ba)
252{
253 if (ba->isEmpty()) {
254 return {};
255 }
256
257 char *data = ba->data();
258 const char *inputPtr = data;
259
260 int len = ba->length();
261 bool skipUtf8 = true;
262 int outlen = 0;
263 for (int i = 0; i < len; ++i, ++outlen) {
264 const char c = inputPtr[i];
265 if (c == '%' && i + 2 < len) {
266 int a = static_cast<int>(static_cast<unsigned char>(inputPtr[++i]));
267 int b = static_cast<int>(static_cast<unsigned char>(inputPtr[++i]));
268
269 if (a >= '0' && a <= '9') {
270 a -= '0';
271 } else if (a >= 'a' && a <= 'f') {
272 a = a - 'a' + 10;
273 } else if (a >= 'A' && a <= 'F') {
274 a = a - 'A' + 10;
275 }
276
277 if (b >= '0' && b <= '9') {
278 b -= '0';
279 } else if (b >= 'a' && b <= 'f') {
280 b = b - 'a' + 10;
281 } else if (b >= 'A' && b <= 'F') {
282 b = b - 'A' + 10;
283 }
284
285 *data++ = (char) ((a << 4) | b);
286 skipUtf8 = false;
287 } else if (c == '+') {
288 *data++ = ' ';
289 } else {
290 *data++ = c;
291 }
292 }
293
294 if (skipUtf8) {
295 return QString::fromLatin1(ba->data(), outlen);
296 } else {
297 return QString::fromUtf8(ba->data(), outlen);
298 }
299}
300
301std::chrono::microseconds Utils::durationFromString(QStringView str, bool *ok)
302{
304 QString digitPart;
305 QString unitPart;
306 bool valid = true;
307 // NOLINTBEGIN(bugprone-branch-clone)
308 for (const QChar ch : str) {
309 if (ch >= u'0' && ch <= u'9') {
310 if (digitPart.isEmpty() && unitPart.isEmpty()) {
311 // we are at the beginning of a new part
312 digitPart.append(ch);
313 } else if (digitPart.isEmpty() && !unitPart.isEmpty()) {
314 // wrong order
315 valid = false;
316 break;
317 } else if (!digitPart.isEmpty() && unitPart.isEmpty()) {
318 // we are still in the digit part
319 digitPart.append(ch);
320 } else if (!digitPart.isEmpty() && !unitPart.isEmpty()) {
321 // we start a new part
322 parts.emplace_back(digitPart, unitPart);
323 digitPart.clear();
324 unitPart.clear();
325 digitPart.append(ch);
326 }
327 } else if ((ch >= u'a' && ch <= u'z') || ch == u'M') {
328 if (digitPart.isEmpty() && unitPart.isEmpty()) {
329 // something is wrong with a digitless unit
330 valid = false;
331 break;
332 } else if (digitPart.isEmpty() && !unitPart.isEmpty()) {
333 // it should not be possible to be here
334 valid = false;
335 break;
336 } else if (!digitPart.isEmpty() && unitPart.isEmpty()) {
337 // we start adding the unit
338 unitPart.append(ch);
339 } else if (!digitPart.isEmpty() && !unitPart.isEmpty()) {
340 // normal operation
341 unitPart.append(ch);
342 }
343 }
344 }
345 // NOLINTEND(bugprone-branch-clone)
346
347 if (!valid) {
348 if (ok) {
349 *ok = false;
350 }
351 return std::chrono::microseconds::zero();
352 }
353
354 if (!digitPart.isEmpty()) {
355 parts.emplace_back(digitPart, unitPart);
356 }
357
358 if (parts.empty()) {
359 if (ok) {
360 *ok = false;
361 }
362 return std::chrono::microseconds::zero();
363 }
364
365 std::chrono::microseconds ms = std::chrono::microseconds::zero();
366
367 for (const std::pair<QString, QString> &p : parts) {
368 bool _ok = false;
369 const qulonglong dur = p.first.toULongLong(&_ok);
370 if (!_ok) {
371 valid = false;
372 break;
373 }
374
375 if (p.second == u"usec" || p.second == u"us") {
376 ms += std::chrono::microseconds{dur};
377 } else if (p.second == u"msec" || p.second == u"ms") {
378 ms += std::chrono::milliseconds{dur};
379 } else if (p.second == u"seconds" || p.second == u"second" || p.second == u"sec" ||
380 p.second == u"s" || p.second.isEmpty()) {
381 ms += std::chrono::seconds{dur};
382 } else if (p.second == u"minutes" || p.second == u"minute" || p.second == u"min" ||
383 p.second == u"m") {
384 ms += std::chrono::minutes{dur};
385 } else if (p.second == u"hours" || p.second == u"hour" || p.second == u"hr" ||
386 p.second == u"h") {
387 ms += std::chrono::hours{dur};
388 } else if (p.second == u"days" || p.second == u"day" || p.second == u"d") {
389 ms += std::chrono::days{dur};
390 } else if (p.second == u"weeks" || p.second == u"week" || p.second == u"w") {
391 ms += std::chrono::weeks{dur};
392 } else if (p.second == u"months" || p.second == u"month" || p.second == u"M") {
393 ms += std::chrono::months{dur};
394 } else if (p.second == u"years" || p.second == u"year" || p.second == u"y") {
395 ms += std::chrono::years{dur};
396 } else {
397 valid = false;
398 break;
399 }
400 }
401
402 if (!valid) {
403 if (ok) {
404 *ok = false;
405 }
406 return std::chrono::microseconds::zero();
407 }
408
409 if (ok) {
410 *ok = true;
411 }
412
413 return ms;
414}
QMultiMap< QString, QString > ParamsMultiMap
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
Definition utils.cpp:301
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< T >::reference emplace_back(Args &&... args)
bool empty() const const
bool isEmpty() const const
qsizetype size() const const
QMultiMap< Key, T >::iterator insert(QMultiMap< Key, T >::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