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