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