cutelyst  4.4.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 
10 using namespace Cutelyst;
11 
12 QByteArray buildTableDivision(const QVector<int> &columnsSize)
13 {
14  QByteArray buffer;
15  QTextStream out(&buffer, QTextStream::WriteOnly);
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 
29 QByteArray 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
64  QTextStream out(&buffer, QTextStream::WriteOnly);
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 
113 QString 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 
163 ParamsMultiMap 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 
243 QString 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 
291 std::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_EXPORT 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