cutelyst  5.0.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
validatorfilesize.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2018-2025 Matthias Fehring <mf@huessenbergnetz.de>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 
6 #include "validatorfilesize_p.h"
7 
8 #include <cmath>
9 #include <limits>
10 
11 using namespace Cutelyst;
12 using namespace Qt::StringLiterals;
13 
15  Option option,
16  const QVariant &min,
17  const QVariant &max,
18  const ValidatorMessages &messages,
19  const QString &defValKey)
20  : ValidatorRule(*new ValidatorFileSizePrivate(field, option, min, max, messages, defValKey))
21 {
22 }
23 
25 
27  double min,
28  double max,
30  const QLocale &locale,
31  double *fileSize)
32 {
33  bool valid = true;
34 
35  const QString str = value.simplified();
36  QString digitPart;
37  QString symbolPart;
38  bool decimalPointFound = false;
39  const QString decimalPoint(locale.decimalPoint());
40  int multiplier = 0;
41  bool binary = false;
42  bool byteSignFound = false;
43  ValidatorFileSizePrivate::StartsWith startsWith{ValidatorFileSizePrivate::StartsWith::NotSet};
44 
45  for (const QChar &ch : str) {
46  if (valid) {
47  const char16_t uc = ch.toUpper().unicode();
48  if (((uc >= ValidatorRulePrivate::ascii_0) && (uc <= ValidatorRulePrivate::ascii_9)) ||
49  (ch == decimalPoint)) {
50  if (startsWith == ValidatorFileSizePrivate::StartsWith::NotSet) {
51  startsWith = ValidatorFileSizePrivate::StartsWith::DigitPart;
52  }
53  if (ch == decimalPoint) {
54  if (decimalPointFound) {
55  qCDebug(C_VALIDATOR).nospace()
56  << "ValidatorFileSize: Validation failed for " << value << ": "
57  << "two decimal seperators in a row";
58  valid = false;
59  break;
60  } else {
61  decimalPointFound = true;
62  }
63  }
64  if ((symbolPart.isEmpty() &&
65  (startsWith == ValidatorFileSizePrivate::StartsWith::DigitPart)) ||
66  (!symbolPart.isEmpty() &&
67  (startsWith == ValidatorFileSizePrivate::StartsWith::SymbolPart))) {
68  digitPart.append(ch);
69  } else {
70  qCDebug(C_VALIDATOR).nospace() << "ValidatorFileSize: Validation failed for "
71  << value << ": " << "symbol inside digit part";
72  valid = false;
73  break;
74  }
75  } else if ((uc != ValidatorRulePrivate::asciiTab) &&
76  (uc != ValidatorRulePrivate::asciiSpace)) { // not a digit or decimal point
77  // and not a space or tab
78  if (startsWith == ValidatorFileSizePrivate::StartsWith::NotSet) {
79  startsWith = ValidatorFileSizePrivate::StartsWith::SymbolPart;
80  }
81  if ((digitPart.isEmpty() &&
82  (startsWith == ValidatorFileSizePrivate::StartsWith::SymbolPart)) ||
83  (!digitPart.isEmpty() &&
84  (startsWith == ValidatorFileSizePrivate::StartsWith::DigitPart))) {
85  switch (uc) {
86  case ValidatorFileSizePrivate::ascii_K:
87  {
88  if (multiplier > 0) {
89  valid = false;
90  qCDebug(C_VALIDATOR).nospace()
91  << "ValdatorFileSize: Validation failed for " << value << ": "
92  << "unit symbol K already found";
93  } else {
94  multiplier = 1;
95  symbolPart.append(ch);
96  }
97  } break;
98  case ValidatorFileSizePrivate::ascii_M:
99  {
100  if (multiplier > 0) {
101  valid = false;
102  qCDebug(C_VALIDATOR).nospace()
103  << "ValdatorFileSize: Validation failed for " << value << ": "
104  << "unit symbol M already found";
105  } else {
106  multiplier = 2;
107  symbolPart.append(ch);
108  }
109  } break;
110  case ValidatorFileSizePrivate::ascii_G:
111  {
112  if (multiplier > 0) {
113  valid = false;
114  qCDebug(C_VALIDATOR).nospace()
115  << "ValdatorFileSize: Validation failed for " << value << ": "
116  << "unit symbol G already found";
117  } else {
118  multiplier = 3;
119  symbolPart.append(ch);
120  }
121  } break;
122  case ValidatorFileSizePrivate::ascii_T:
123  {
124  if (multiplier > 0) {
125  valid = false;
126  qCDebug(C_VALIDATOR).nospace()
127  << "ValdatorFileSize: Validation failed for " << value << ": "
128  << "unit symbol T already found";
129  } else {
130  multiplier = 4;
131  symbolPart.append(ch);
132  }
133  } break;
134  case ValidatorFileSizePrivate::ascii_P:
135  {
136  if (multiplier > 0) {
137  valid = false;
138  qCDebug(C_VALIDATOR).nospace()
139  << "ValdatorFileSize: Validation failed for " << value << ": "
140  << "unit symbol P already found";
141  } else {
142  multiplier = 5;
143  symbolPart.append(ch);
144  }
145  } break;
146  case ValidatorFileSizePrivate::ascii_E:
147  {
148  if (multiplier > 0) {
149  valid = false;
150  qCDebug(C_VALIDATOR).nospace()
151  << "ValdatorFileSize: Validation failed for " << value << ": "
152  << "unit symbol E already found";
153  } else {
154  multiplier = 6;
155  symbolPart.append(ch);
156  }
157  } break;
158  case ValidatorRulePrivate::ascii_Z:
159  {
160  if (multiplier > 0) {
161  valid = false;
162  qCDebug(C_VALIDATOR).nospace()
163  << "ValdatorFileSize: Validation failed for " << value << ": "
164  << "unit symbol Z already found";
165  } else {
166  multiplier = 7;
167  symbolPart.append(ch);
168  }
169  } break;
170  case ValidatorFileSizePrivate::ascii_Y:
171  {
172  if (multiplier > 0) {
173  valid = false;
174  qCDebug(C_VALIDATOR).nospace()
175  << "ValdatorFileSize: Validation failed for " << value << ": "
176  << "unit symbol Y already found";
177  } else {
178  multiplier = 8;
179  symbolPart.append(ch);
180  }
181  } break;
182  case ValidatorFileSizePrivate::ascii_I:
183  {
184  if ((multiplier == 0) || binary) {
185  valid = false;
186  qCDebug(C_VALIDATOR).nospace()
187  << "ValdatorFileSize: Validation failed for " << value << ": "
188  << "binary indicator I already found or no unit symbol given "
189  "before";
190  } else {
191  binary = true;
192  symbolPart.append(ch);
193  }
194  } break;
195  case ValidatorFileSizePrivate::ascii_B:
196  {
197  if (byteSignFound) {
198  valid = false;
199  qCDebug(C_VALIDATOR).nospace()
200  << "ValdatorFileSize: Validation failed for " << value << ": "
201  << "byte symbol B already found";
202  } else {
203  byteSignFound = true;
204  symbolPart.append(ch);
205  }
206  } break;
207  case ValidatorRulePrivate::asciiTab:
208  case ValidatorRulePrivate::asciiSpace:
209  break;
210  default:
211  valid = false;
212  qCDebug(C_VALIDATOR).nospace()
213  << "ValdatorFileSize: Validation failed for " << value << ": "
214  << "invalid character in symbol part";
215  break;
216  }
217  } else {
218  valid = false;
219  break;
220  }
221  }
222  } else {
223  break;
224  }
225  }
226 
227  if ((option == OnlyBinary) && !binary) {
228  valid = false;
229  } else if ((option == OnlyDecimal) && binary) {
230  valid = false;
231  } else if (option == ForceBinary) {
232  binary = true;
233  } else if (option == ForceDecimal) {
234  binary = false;
235  }
236 
237  if (valid) {
238  bool ok = false;
239  double size = locale.toDouble(digitPart, &ok);
240  if (!ok) {
241  valid = false;
242  } else {
243  if (multiplier > 0) {
244  const double _mult =
245  binary ? std::exp2(multiplier * 10) : std::pow(10.0, multiplier * 3);
246  size *= _mult;
247  }
248  if ((min >= 1.0) && (size < min)) {
249  valid = false;
250  }
251  if ((max >= 1.0) && (size > max)) {
252  valid = false;
253  }
254  if (valid && fileSize) {
255  *fileSize = size;
256  }
257  }
258  }
259 
260  return valid;
261 }
262 
264 {
265  ValidatorReturnType result;
266 
267  Q_D(const ValidatorFileSize);
268 
269  const QString v = value(params);
270 
271  if (!v.isEmpty()) {
272 
273  double min = -1;
274  double max = -1;
275  bool ok = true;
276  if (d->min.isValid()) {
277  min = ValidatorFileSizePrivate::extractDouble(c, params, d->min, &ok);
278  if (!ok) {
279  qCWarning(C_VALIDATOR).noquote()
280  << debugString(c) << "Invalid minimum size comparison data";
282  c, static_cast<int>(ValidatorRulePrivate::ErrorType::InvalidMin));
283  }
284  }
285 
286  if (ok && d->max.isValid()) {
287  max = ValidatorFileSizePrivate::extractDouble(c, params, d->max, &ok);
288  if (!ok) {
289  qCWarning(C_VALIDATOR).noquote()
290  << debugString(c) << "Invalid maximum size comparison data";
292  c, static_cast<int>(ValidatorRulePrivate::ErrorType::InvalidMax));
293  }
294  }
295 
296  if (ok) {
297  double size = 0;
298  if (ValidatorFileSize::validate(v, min, max, d->option, c->locale(), &size)) {
299  if (size < static_cast<double>(std::numeric_limits<qulonglong>::max())) {
300  // best solution would be something like std::ullround, but there is only
301  // std:llround that returns a signed long long instead of unsigend, so still
302  // using this approach works best
303  // NOLINTNEXTLINE(bugprone-incorrect-roundings)
304  result.value.setValue<qulonglong>(static_cast<qulonglong>(size + 0.5));
305  } else {
306  result.value.setValue(size);
307  }
308  } else {
309  result.errorMessage = validationError(c);
310  qCWarning(C_VALIDATOR).noquote()
311  << debugString(c) << v << "is not a valid data size string";
312  }
313  }
314 
315  } else {
316  defaultValue(c, &result);
317  }
318 
319  return result;
320 }
321 
323 {
324  cb(validate(c, params));
325 }
326 
328 {
329  Q_D(const ValidatorFileSize);
330  Q_UNUSED(errorData)
331  const QString _label = label(c);
332  if (d->min.isValid() || d->max.isValid()) {
333  if (_label.isEmpty()) {
334  //% "Invalid file size or file size not within the allowed limits."
335  return c->qtTrId("cutelyst-valfilesize-genvalerr-minmax");
336  } else {
337  //% "The value in the “%1” field is either not a valid file size or "
338  //% "not within the allowed limits."
339  return c->qtTrId("cutelyst-valfilesize-genvalerr-minmax-label").arg(_label);
340  }
341  } else {
342  if (_label.isEmpty()) {
343  //% "Invalid file size."
344  return c->qtTrId("cutelyst-valfilesize-genvalerr");
345  } else {
346  //% "The “%1” field does not contain a valid file size."
347  return c->qtTrId("cutelyst-valfilesize-genvalerr-label").arg(_label);
348  }
349  }
350 }
351 
353 {
354  const QString _label = label(c);
355 
356  const auto errorType = static_cast<ValidatorRulePrivate::ErrorType>(errorData.toInt());
357 
358  if (_label.isEmpty()) {
359  switch (errorType) {
360  case ValidatorRulePrivate::ErrorType::InvalidMin:
361  //% "The minimum file size comparison value is not valid."
362  return c->qtTrId("cutelyst-valfilesize-genvaldataerr-min");
363  case ValidatorRulePrivate::ErrorType::InvalidMax:
364  //% "The maximum file size comparison value is not valid."
365  return c->qtTrId("cutelyst-valfilesize-genvaldataerr-max");
366  case ValidatorRulePrivate::ErrorType::InvalidType:
367  // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while)
368  Q_UNREACHABLE();
369  return {};
370  }
371  } else {
372  switch (errorType) {
373  case ValidatorRulePrivate::ErrorType::InvalidMin:
374  //% "The minimum file size comparison value for the “%1” field is not valid."
375  return c->qtTrId("cutelyst-valfilesize-genvaldataerr-min-label").arg(_label);
376  case ValidatorRulePrivate::ErrorType::InvalidMax:
377  //% "The maximum file size comparison value for the “%1” field is not valid."
378  return c->qtTrId("cutelyst-valfilesize-genvaldataerr-max-label").arg(_label);
379  case ValidatorRulePrivate::ErrorType::InvalidType:
380  // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while)
381  Q_UNREACHABLE();
382  return {};
383  }
384  }
385 
386 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
387  // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while)
388  Q_UNREACHABLE_RETURN({});
389 #else
390  return {};
391 #endif
392 }
393 
395 {
396  Q_ASSERT(c);
397  const QString pattern =
399  ? u"^\\d+[%1]?\\d*\\s*[KkMmGgTt]?[Ii]?[Bb]?"_s.arg(c->locale().decimalPoint())
400  : u"[KkMmGgTt]?[Ii]?[Bb]?\\s*\\d+[%1]?\\d*"_s.arg(c->locale().decimalPoint());
401  c->setStash(stashKey, pattern);
402 }
QString & append(QChar ch)
QString toUpper() const const
QString genericValidationDataError(Context *c, const QVariant &errorData) const override
ValidatorFileSize(const QString &field, Option option=NoOption, const QVariant &min={}, const QVariant &max={}, const ValidatorMessages &messages={}, const QString &defValKey={})
Stores custom error messages and the input field label.
QString simplified() const const
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:213
QString decimalPoint() const const
LeftToRight
Option
Options for ValidatorFileSize.
static void inputPattern(Context *c, const QString &stashKey=QStringLiteral("fileSizePattern"))
The Cutelyst Context.
Definition: context.h:42
void defaultValue(Context *c, ValidatorReturnType *result) const
int toInt(bool *ok) const const
bool isEmpty() const const
Qt::LayoutDirection textDirection() const const
The Cutelyst namespace holds all public Cutelyst API.
QString debugString(const Context *c) const
Base class for all validator rules.
QLocale locale() const noexcept
Definition: context.cpp:461
Checks if the input field contains a valid file size string like 1.5 GB.
QString value(const ParamsMultiMap &params) const
QString label(const Context *c) const
std::function< void(ValidatorReturnType &&result)> ValidatorRtFn
Void callback function for validator rules that processes the ValidatorReturnType.
Definition: validatorrule.h:82
const QChar * unicode() const const
QString validationError(Context *c, const QVariant &errorData={}) const
double toDouble(QStringView s, bool *ok) const const
static bool validate(const QString &value, double min=-1, double max=-1, Option option=NoOption, const QLocale &locale=QLocale(), double *fileSize=nullptr)
Returns true if value is a valid file size string.
QString validationDataError(Context *c, const QVariant &errorData={}) const
QString qtTrId(const char *id, int n=-1) const
Definition: context.h:658
Contains the result of a single input parameter validation.
Definition: validatorrule.h:52
~ValidatorFileSize() override
Deconstructs the file size validator.
QString arg(Args &&... args) const const
void validateCb(Context *c, const ParamsMultiMap &params, ValidatorRtFn cb) const override
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
void setValue(QVariant &&value)