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;
12using 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}
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.