cutelyst  5.0.1
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 
12 using namespace Cutelyst;
13 
14 namespace {
15 
16 QByteArray buildTableDivision(const QVector<int> &columnsSize)
17 {
18  QByteArray buffer;
19  QTextStream out(&buffer, QTextStream::WriteOnly);
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 
35 QByteArray 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
69  QTextStream out(&buffer, QTextStream::WriteOnly);
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 
118 QString 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 
170 ParamsMultiMap 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 
252 QString 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 
302 std::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 }
QString & append(QChar ch)
QByteArray & fill(char ch, qsizetype size)
qsizetype size() const const
bool isEmpty() const const
QString fromUtf8(QByteArrayView str)
qsizetype length() const const
qsizetype size() const const
void clear()
bool empty() const const
reference emplace_back(Args &&... args)
bool isEmpty() const const
bool isEmpty() const const
iterator insert(const Key &key, const T &value)
The Cutelyst namespace holds all public Cutelyst API.
QString fromLatin1(QByteArrayView str)
QByteArray toLatin1() const const
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
Definition: utils.cpp:302
char * data()