Cutelee 6.2.0
stringfilters.cpp
1/*
2 This file is part of the Cutelee template system.
3
4 Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either version
9 2.1 of the Licence, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library. If not, see <http://www.gnu.org/licenses/>.
18
19*/
20
21#include "stringfilters.h"
22
23#include "util.h"
24
25#include <QtCore/QRegularExpression>
26#include <QtCore/QVariant>
27#include <QtCore/QJsonArray>
28#include <QtCore/QJsonObject>
29#include <QtCore/QJsonDocument>
30
32 const QVariant &argument,
33 bool autoescape) const
34{
35 Q_UNUSED(argument)
36 Q_UNUSED(autoescape)
37 auto safeString = getSafeString(input);
38 safeString.get()
39 .replace(QLatin1Char('\\'), QStringLiteral("\\\\"))
40 .get()
41 .replace(QLatin1Char('\"'), QStringLiteral("\\\""))
42 .get()
43 .replace(QLatin1Char('\''), QStringLiteral("\\\'"));
44 return safeString;
45}
46
48 const QVariant &argument,
49 bool autoescape) const
50{
51 Q_UNUSED(argument)
52 Q_UNUSED(autoescape)
53 auto safeString = getSafeString(input);
54 if (safeString.get().isEmpty())
55 return QString();
56
57 return QVariant(safeString.get().at(0).toUpper()
58 + static_cast<QString>(
59 safeString.get().right(safeString.get().size() - 1)));
60}
61
62EscapeJsFilter::EscapeJsFilter() {}
63
64static QList<std::pair<QString, QString>> getJsEscapes()
65{
66 QList<std::pair<QString, QString>> jsEscapes;
67 jsEscapes << std::pair<QString, QString>(QChar::fromLatin1('\\'),
68 QStringLiteral("\\u005C"))
69 << std::pair<QString, QString>(QChar::fromLatin1('\''),
70 QStringLiteral("\\u0027"))
71 << std::pair<QString, QString>(QChar::fromLatin1('\"'),
72 QStringLiteral("\\u0022"))
73 << std::pair<QString, QString>(QChar::fromLatin1('>'),
74 QStringLiteral("\\u003E"))
75 << std::pair<QString, QString>(QChar::fromLatin1('<'),
76 QStringLiteral("\\u003C"))
77 << std::pair<QString, QString>(QChar::fromLatin1('&'),
78 QStringLiteral("\\u0026"))
79 << std::pair<QString, QString>(QChar::fromLatin1('='),
80 QStringLiteral("\\u003D"))
81 << std::pair<QString, QString>(QChar::fromLatin1('-'),
82 QStringLiteral("\\u002D"))
83 << std::pair<QString, QString>(QChar::fromLatin1(';'),
84 QStringLiteral("\\u003B"))
85 << std::pair<QString, QString>(QChar(0x2028), QStringLiteral("\\u2028"))
86 << std::pair<QString, QString>(QChar(0x2029),
87 QStringLiteral("\\u2029"));
88
89 for (auto i = 0; i < 32; ++i) {
90 jsEscapes << std::pair<QString, QString>(
91 QChar(i),
92 QStringLiteral("\\u00")
93 + QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
94 }
95 return jsEscapes;
96}
97
99 const QVariant &argument,
100 bool autoescape) const
101{
102 Q_UNUSED(argument)
103 Q_UNUSED(autoescape)
104 QString retString = getSafeString(input);
105
106 static const auto jsEscapes = getJsEscapes();
107
108 for (auto &escape : jsEscapes) {
109 retString = retString.replace(escape.first, escape.second);
110 }
111 return retString;
112}
113
115 const QVariant &argument,
116 bool autoescape) const
117{
118 Q_UNUSED(argument)
119 Q_UNUSED(autoescape)
120 auto safeString = getSafeString(input);
121
122 const QRegularExpression fixAmpersandsRegexp(
123 QStringLiteral("&(?!(\\w+|#\\d+);)"));
124
125 safeString.get().replace(fixAmpersandsRegexp, QStringLiteral("&amp;"));
126
127 return safeString;
128}
129
130QVariant CutFilter::doFilter(const QVariant &input, const QVariant &argument,
131 bool autoescape) const
132{
133 Q_UNUSED(autoescape)
134 auto retString = getSafeString(input);
135 auto argString = getSafeString(argument);
136
137 auto inputSafe = retString.isSafe();
138
139 retString.get().remove(argString);
140
141 if (inputSafe && argString.get() != QChar::fromLatin1(';'))
142 return SafeString(retString, true);
143 else
144 return retString;
145}
146
147QVariant SafeFilter::doFilter(const QVariant &input, const QVariant &argument,
148 bool autoescape) const
149{
150 Q_UNUSED(argument)
151 Q_UNUSED(autoescape)
152 return markSafe(getSafeString(input));
153}
154
156 const QVariant &argument,
157 bool autoescape) const
158{
159 Q_UNUSED(argument)
160 auto safeString = getSafeString(input);
161 auto lines = safeString.get().split(QLatin1Char('\n'));
162 auto width = QString::number(lines.size()).size();
163
164 const auto shouldEscape = (autoescape && !safeString.isSafe());
165 for (auto i = 0; i < lines.size(); ++i) {
166 lines[i]
167 = QStringLiteral("%1. %2")
168 .arg(i + 1, width)
169 .arg(shouldEscape ? QString(escape(lines.at(i))) : lines.at(i));
170 }
171
172 return SafeString(lines.join(QChar::fromLatin1('\n')), true);
173}
174
175QVariant LowerFilter::doFilter(const QVariant &input, const QVariant &argument,
176 bool autoescape) const
177{
178 Q_UNUSED(argument)
179 Q_UNUSED(autoescape)
180 return getSafeString(input).get().toLower();
181}
182
184 const QVariant &argument,
185 bool autoescape) const
186{
187 Q_UNUSED(autoescape)
188 SafeString a;
189 if (isSafeString(input))
190 a = getSafeString(input);
191 else if (input.userType() == qMetaTypeId<QVariantList>()) {
192 a = toString(input.value<QVariantList>());
193 }
194
195 return SafeString(getSafeString(argument).get().arg(a),
196 getSafeString(input).isSafe());
197}
198
199QVariant TitleFilter::doFilter(const QVariant &input, const QVariant &argument,
200 bool autoescape) const
201{
202 Q_UNUSED(argument)
203 Q_UNUSED(autoescape)
204
205 QString str = getSafeString(input);
206
207 auto it = str.begin();
208 const auto end = str.end();
209
210 auto toUpper = true;
211 for (; it != end; ++it) {
212 if (toUpper)
213 *it = it->toUpper();
214 else
215 *it = it->toLower();
216 toUpper = it->isSpace();
217 }
218
219 return str;
220}
221
223 const QVariant &argument,
224 bool autoescape) const
225{
226 Q_UNUSED(autoescape)
227 auto s = getSafeString(argument);
228
229 bool ok;
230 auto numWords = s.get().toInt(&ok);
231
232 if (!ok) {
233 return input.toString();
234 }
235
236 QString inputString = getSafeString(input);
237 auto words = inputString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
238
239 if (words.size() > numWords) {
240 words = words.mid(0, numWords);
241 if (!words.at(words.size() - 1).endsWith(QStringLiteral("..."))) {
242 words << QStringLiteral("...");
243 }
244 }
245 return words.join(QChar::fromLatin1(' '));
246}
247
248QVariant UpperFilter::doFilter(const QVariant &input, const QVariant &argument,
249 bool autoescape) const
250{
251 Q_UNUSED(argument)
252 Q_UNUSED(autoescape)
253 return getSafeString(input).get().toUpper();
254}
255
257 const QVariant &argument,
258 bool autoescape) const
259{
260 Q_UNUSED(argument)
261 Q_UNUSED(autoescape)
262 return QString::number(
263 getSafeString(input).get().split(QLatin1Char(' ')).size());
264}
265
266QVariant LJustFilter::doFilter(const QVariant &input, const QVariant &argument,
267 bool autoescape) const
268{
269 Q_UNUSED(autoescape)
270 return getSafeString(input).get().leftJustified(
271 getSafeString(argument).get().toInt());
272}
273
274QVariant RJustFilter::doFilter(const QVariant &input, const QVariant &argument,
275 bool autoescape) const
276{
277 Q_UNUSED(autoescape)
278 return getSafeString(input).get().rightJustified(
279 getSafeString(argument).get().toInt());
280}
281
282QVariant CenterFilter::doFilter(const QVariant &input, const QVariant &argument,
283 bool autoescape) const
284{
285 Q_UNUSED(autoescape)
286 QString value = getSafeString(input);
287 const auto valueWidth = value.size();
288 const auto width = getSafeString(argument).get().toInt();
289 const auto totalPadding = width - valueWidth;
290 const auto rightPadding = totalPadding >> 1;
291
292 return value.leftJustified(valueWidth + rightPadding).rightJustified(width);
293}
294
295QVariant EscapeFilter::doFilter(const QVariant &input, const QVariant &argument,
296 bool autoescape) const
297{
298 Q_UNUSED(argument)
299 Q_UNUSED(autoescape)
300 return markForEscaping(getSafeString(input));
301}
302
304 const QVariant &argument,
305 bool autoescape) const
306{
307 Q_UNUSED(argument)
308 Q_UNUSED(autoescape)
309 return markSafe(escape(getSafeString(input)));
310}
311
313 const QVariant &argument,
314 bool autoescape) const
315{
316 Q_UNUSED(autoescape)
317 const auto tags = getSafeString(argument).get().split(QLatin1Char(' '));
318 const auto tagRe
319 = QStringLiteral("(%1)").arg(tags.join(QChar::fromLatin1('|')));
320 const QRegularExpression startTag(
321 QStringLiteral("<%1(/?>|(\\s+[^>]*>))").arg(tagRe));
322 const QRegularExpression endTag(QStringLiteral("</%1>").arg(tagRe));
323
324 auto value = getSafeString(input);
325 const auto safeInput = value.isSafe();
326 value.get().remove(startTag);
327 value.get().remove(endTag);
328 if (safeInput)
329 return markSafe(value);
330 return value;
331}
332
334 const QVariant &argument,
335 bool autoescape) const
336{
337 Q_UNUSED(argument)
338 Q_UNUSED(autoescape)
339 static QRegularExpression tagRe(QStringLiteral("<[^>]*>"),
340 QRegularExpression::InvertedGreedinessOption);
341
342 QString value = getSafeString(input);
343 value.remove(tagRe);
344 return value;
345}
346
348 const QVariant &argument,
349 bool autoescape) const
350{
351 Q_UNUSED(autoescape)
352 QString _input = getSafeString(input);
353 auto width = argument.value<int>();
354 auto partList = _input.split(QLatin1Char(' '), Qt::SkipEmptyParts);
355 if (partList.isEmpty())
356 return QVariant();
357 auto output = partList.takeFirst();
358 auto pos = output.size() - output.lastIndexOf(QLatin1Char('\n')) - 1;
359 Q_FOREACH (const QString &part, partList) {
360 QStringList lines;
361 if (part.contains(QLatin1Char('\n'))) {
362 lines = part.split(QLatin1Char('\n'));
363 } else {
364 lines.append(part);
365 }
366 pos += lines.first().size() + 1;
367 if (pos > width) {
368 output.append(QLatin1Char('\n'));
369 pos += lines.last().size();
370 } else {
371 output.append(QLatin1Char(' '));
372 if (lines.size() > 1)
373 pos += lines.last().size();
374 }
375 output.append(part);
376 }
377 return output;
378}
379
381 const QVariant &argument,
382 bool autoescape) const
383{
384 Q_UNUSED(autoescape)
385 double inputDouble;
386 switch (input.userType()) {
387 case QMetaType::Int:
388 case QMetaType::UInt:
389 case QMetaType::LongLong:
390 case QMetaType::ULongLong:
391 case QMetaType::Double:
392 inputDouble = input.toDouble();
393 break;
394 default:
395 inputDouble = getSafeString(input).get().toDouble();
396 }
397
398 int precision;
399 if (argument.isValid())
400 precision = getSafeString(argument).get().toInt();
401 else
402 precision = 1;
403
404 return QString::number(inputDouble, 'f', precision);
405}
406
408 const QVariant &argument,
409 bool autoescape) const
410{
411 Q_UNUSED(argument)
412 Q_UNUSED(autoescape)
413 QVariantList list;
414 if (input.userType() == qMetaTypeId<QVariantList>()) {
415 const auto inputList = input.value<QVariantList>();
416 for (const auto &item : inputList) {
417 list << markSafe(getSafeString(item));
418 }
419 }
420 return list;
421}
422
424 const QVariant &argument,
425 bool autoescape) const
426{
427 Q_UNUSED(argument)
428 auto inputString = getSafeString(input);
429 static const QRegularExpression re(QStringLiteral("\n{2,}"));
430 QStringList output;
431
432 Q_FOREACH (const QString &bit, inputString.get().split(re)) {
433 auto _bit = SafeString(bit, inputString.isSafe());
434 if (autoescape)
435 _bit = conditionalEscape(_bit);
436 _bit.get().replace(QLatin1Char('\n'), QStringLiteral("<br />"));
437 output.append(QStringLiteral("<p>%1</p>").arg(_bit));
438 }
439 return markSafe(output.join(QStringLiteral("\n\n")));
440}
441
443 const QVariant &argument,
444 bool autoescape) const
445{
446 Q_UNUSED(argument)
447 auto inputString = getSafeString(input);
448 if (autoescape && isSafeString(input)) {
449 inputString = conditionalEscape(inputString);
450 }
451 return markSafe(
452 inputString.get().replace(QLatin1Char('\n'), QStringLiteral("<br />")));
453}
454
455static QString nofailStringToAscii(const QString &input)
456{
457 QString output;
458 output.reserve(input.size());
459
460 auto it = input.constBegin();
461 const auto end = input.constEnd();
462 static const QChar asciiEndPoint(128);
463 for (; it != end; ++it)
464 if (*it < asciiEndPoint)
465 output.append(*it);
466
467 return output;
468}
469
471 const QVariant &argument,
472 bool autoescape) const
473{
474 Q_UNUSED(argument)
475 Q_UNUSED(autoescape)
476 QString inputString = getSafeString(input);
477 inputString = inputString.normalized(QString::NormalizationForm_KD);
478 inputString = nofailStringToAscii(inputString);
479 inputString = inputString.trimmed()
480 .toLower()
481 .remove(QRegularExpression(QStringLiteral("[^\\w\\s-]")))
482 .replace(QRegularExpression(QStringLiteral("[-\\s]+")), QStringLiteral("-"));
483 return SafeString(inputString, true);
484}
485
487 const QVariant &argument,
488 bool autoescape) const
489{
490 QVariant ret;
491
492 Q_UNUSED(autoescape)
493 const auto arg = getSafeString(argument);
494 bool numberConvert = true;
495
496 qreal size = 0.0f;
497 if (input.canConvert<qreal>()) {
498 size = input.toReal(&numberConvert);
499 if (!numberConvert) {
500 qWarning("%s", "Failed to convert input file size into floating point value.");
501 }
502 } else {
503 size = getSafeString(input).get().toDouble(&numberConvert);
504 if (!numberConvert) {
505 qWarning("%s", "Failed to convert input file size into floating point value.");
506 }
507 }
508
509 int unitSystem = 10;
510 int precision = 2;
511 qreal multiplier = 1.0f;
512
513 if (!arg.get().isEmpty()) {
514 const auto argList = arg.get().split(QLatin1Char(','), Qt::SkipEmptyParts);
515 const auto numArgs = argList.size();
516 if (numArgs > 0) {
517 unitSystem = argList.at(0).toInt(&numberConvert);
518 if (!numberConvert) {
519 qWarning("%s", "Failed to convert filse size format unit system into integer. Falling back to default 10.");
520 unitSystem = 10;
521 }
522 }
523
524 if (numArgs > 1) {
525 precision = argList.at(1).toInt(&numberConvert);
526 if (!numberConvert) {
527 qWarning("%s", "Failed to convert file size format decimal precision into integer. Falling back to default 2.");
528 precision = 2;
529 }
530 }
531
532 if (numArgs > 2) {
533 multiplier = argList.at(2).toDouble(&numberConvert);
534 if (!numberConvert) {
535 qWarning("%s", "Failed to convert file size format multiplier into double value. Falling back to default 1.0");
536 multiplier = 1.0f;
537 } else {
538 if (multiplier == 0.0f) {
539 qWarning("%s", "It makes no sense to multiply the file size by zero. Using default value 1.0.");
540 multiplier = 1.0f;
541 }
542 }
543 }
544 }
545
546 const double sizeMult = size * multiplier;
547
548 if (unitSystem == 10) {
549 if ((sizeMult > -1000) && (sizeMult < 1000)) {
550 precision = 0;
551 }
552 } else if (unitSystem == 2) {
553 if ((sizeMult > - 1024) && (sizeMult < 1024)) {
554 precision = 0;
555 }
556 }
557
558 const std::pair<qreal,QString> sizePair = calcFileSize(size, unitSystem, multiplier);
559
560 const QString retString = QString::number(sizePair.first, 'f', precision) + QLatin1Char(' ') + sizePair.second;
561
562 ret.setValue(retString);
563
564 return ret;
565}
566
567QVariant TruncateCharsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
568{
569 Q_UNUSED(autoescape)
570 QString retString = getSafeString(input);
571 int count = getSafeString(argument).get().toInt();
572
573 if(retString.length() < count) return retString;
574 retString.truncate(count);
575 retString.append(QStringLiteral("..."));
576 return retString;
577}
578
579static QString escapeJson(const QString &input)
580{
581 QString esc;
582 const int len = input.length();
583 esc.reserve(static_cast<int>(len * 1.2));
584 for (int i = 0; i < len; ++i) {
585 const QChar ch = input.at(i);
586 if (ch == QLatin1Char('>')) {
587 esc += QStringLiteral("\\\\u003E");
588 } else if (ch == QLatin1Char('<')) {
589 esc += QStringLiteral("\\\\u003C");
590 } else if (ch == QLatin1Char('&')) {
591 esc += QStringLiteral("\\\\u0026");
592 } else {
593 esc += ch;
594 }
595 }
596 esc.squeeze();
597 return esc;
598}
599
600QVariant JsonScriptFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
601{
602 Q_UNUSED(autoescape)
603 if (input.isNull() || !input.isValid()) {
604 return QVariant();
605 }
606
607 const QString arg = escape(getSafeString(argument));
608
609 QJsonDocument json;
610 if (input.canConvert<QJsonDocument>()) {
611 json = input.toJsonDocument();
612 } else if (input.canConvert<QJsonObject>()) {
613 json.setObject(input.toJsonObject());
614 } else if (input.canConvert<QJsonArray>()) {
615 json.setArray(input.toJsonArray());
616 } else {
617 qWarning("%s", "Can not convert input data into QJsonObject or QJSonArray.");
618 return QVariant();
619 }
620
621 QString jsonString = QString::fromUtf8(json.toJson(QJsonDocument::Compact));
622 jsonString = escapeJson(jsonString);
623
624 const QString scriptString = u"<script id=\"" + arg + u"\" type=\"application/json\">" + jsonString + u"</script>";
625
626 return SafeString(scriptString, true);
627}
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
SafeString escape(const QString &input) const
Definition filter.cpp:10
SafeString conditionalEscape(const SafeString &input) const
Definition filter.cpp:22
A QString wrapper class for containing whether a string is safe or needs to be escaped.
Definition safestring.h:92
const NestedString & get() const
Definition safestring.h:325
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument=QVariant(), bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument=QVariant(), bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
bool isSafe() const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument=QVariant(), bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
QVariant doFilter(const QVariant &input, const QVariant &argument={}, bool autoescape={}) const override
std::pair< qreal, QString > calcFileSize(qreal size, int unitSystem=10, qreal multiplier=1.0)
Definition util.cpp:196
bool isSafeString(const QVariant &input)
Definition util.cpp:120
Cutelee::SafeString getSafeString(const QVariant &input)
Definition util.cpp:111
Cutelee::SafeString markSafe(const Cutelee::SafeString &input)
Definition util.cpp:93
Cutelee::SafeString markForEscaping(const Cutelee::SafeString &input)
Definition util.cpp:101
Utility functions used throughout Cutelee.