6#include "staticcompressed_p.h"
8#include <Cutelyst/Application>
9#include <Cutelyst/Context>
10#include <Cutelyst/Engine>
11#include <Cutelyst/Request>
12#include <Cutelyst/Response>
14#include <QCoreApplication>
15#include <QCryptographicHash>
20#include <QLoggingCategory>
21#include <QMimeDatabase>
22#include <QStandardPaths>
24#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
28#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
29# include <brotli/encode.h>
34Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
38 , d_ptr(new StaticCompressedPrivate)
41 d->includePaths.append(
parent->config(QStringLiteral(
"root")).toString());
51 d->includePaths.clear();
52 for (
const QString &path : paths) {
53 d->includePaths.append(
QDir(path));
67 const QVariantMap config =
68 app->
engine()->
config(QStringLiteral(
"Cutelyst_StaticCompressed_Plugin"));
69 const QString _defaultCacheDir =
73 config.value(QStringLiteral(
"cache_directory"), _defaultCacheDir).toString());
75 if (Q_UNLIKELY(!d->cacheDir.exists())) {
76 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
77 qCCritical(C_STATICCOMPRESSED,
78 "Failed to create cache directory for compressed static files at \"%s\".",
79 qPrintable(d->cacheDir.absolutePath()));
84 qCInfo(C_STATICCOMPRESSED,
85 "Compressed cache directory: %s",
86 qPrintable(d->cacheDir.absolutePath()));
90 .value(QStringLiteral(
"mime_types"), QStringLiteral(
"text/css,application/javascript"))
92 qCInfo(C_STATICCOMPRESSED,
"MIME Types: %s", qPrintable(_mimeTypes));
95 const QString _suffixes = config
96 .value(QStringLiteral(
"suffixes"),
97 QStringLiteral(
"js.map,css.map,min.js.map,min.css.map"))
99 qCInfo(C_STATICCOMPRESSED,
"Suffixes: %s", qPrintable(_suffixes));
102 d->checkPreCompressed = config.value(QStringLiteral(
"check_pre_compressed"),
true).toBool();
103 qCInfo(C_STATICCOMPRESSED,
104 "Check for pre-compressed files: %s",
105 d->checkPreCompressed ?
"true" :
"false");
107 d->onTheFlyCompression = config.value(QStringLiteral(
"on_the_fly_compression"),
true).toBool();
108 qCInfo(C_STATICCOMPRESSED,
109 "Compress static files on the fly: %s",
110 d->onTheFlyCompression ?
"true" :
"false");
112 QStringList supportedCompressions{QStringLiteral(
"deflate"), QStringLiteral(
"gzip")};
115 d->zlibCompressionLevel = config.value(QStringLiteral(
"zlib_compression_level"), 9).toInt(&ok);
116 if (!ok || (d->zlibCompressionLevel < -1) || (d->zlibCompressionLevel > 9)) {
117 d->zlibCompressionLevel = -1;
120#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
121 d->zopfliIterations = config.value(QStringLiteral(
"zopfli_iterations"), 15).toInt(&ok);
122 if (!ok || (d->zopfliIterations < 0)) {
123 d->zopfliIterations = 15;
125 d->useZopfli = config.value(QStringLiteral(
"use_zopfli"),
false).toBool();
126 supportedCompressions << QStringLiteral(
"zopfli");
129#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
130 d->brotliQualityLevel =
131 config.value(QStringLiteral(
"brotli_quality_level"), BROTLI_DEFAULT_QUALITY).toInt(&ok);
132 if (!ok || (d->brotliQualityLevel < BROTLI_MIN_QUALITY) ||
133 (d->brotliQualityLevel > BROTLI_MAX_QUALITY)) {
134 d->brotliQualityLevel = BROTLI_DEFAULT_QUALITY;
136 supportedCompressions << QStringLiteral(
"brotli");
139 qCInfo(C_STATICCOMPRESSED,
140 "Supported compressions: %s",
141 qPrintable(supportedCompressions.
join(u
',')));
144 d->beforePrepareAction(c, skipMethod);
150void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
156 const QString path = c->req()->path();
159 for (
const QString &dir : dirs) {
161 if (!locateCompressedFile(c, path)) {
165 res->
setBody(QStringLiteral(
"File not found: ") + path);
173 const QRegularExpressionMatch match = _re.
match(path);
174 if (match.
hasMatch() && locateCompressedFile(c, path)) {
179bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
181 for (
const QDir &includePath : includePaths) {
182 const QString path = includePath.absoluteFilePath(relPath);
183 const QFileInfo fileInfo(path);
184 if (fileInfo.exists()) {
186 const QDateTime currentDateTime = fileInfo.lastModified();
192 static QMimeDatabase db;
195 QString contentEncoding;
196 QString compressedPath;
197 QString _mimeTypeName;
206 _mimeTypeName = QStringLiteral(
"application/json");
213 const QString acceptEncoding =
214 c->req()->
header(QStringLiteral(
"Accept-Encoding"));
215 qCDebug(C_STATICCOMPRESSED) <<
"Accept-Encoding:" << acceptEncoding;
217#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
219 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
220 if (!compressedPath.
isEmpty()) {
221 qCDebug(C_STATICCOMPRESSED,
222 "Serving brotli compressed data from \"%s\".",
223 qPrintable(compressedPath));
224 contentEncoding = QStringLiteral(
"br");
230 locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
231 if (!compressedPath.
isEmpty()) {
232 qCDebug(C_STATICCOMPRESSED,
233 "Serving %s compressed data from \"%s\".",
234 useZopfli ?
"zopfli" :
"gzip",
235 qPrintable(compressedPath));
236 contentEncoding = QStringLiteral(
"gzip");
238 }
else if (acceptEncoding.
contains(QLatin1String(
"deflate"),
240 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
241 if (!compressedPath.
isEmpty()) {
242 qCDebug(C_STATICCOMPRESSED,
243 "Serving deflate compressed data from \"%s\".",
244 qPrintable(compressedPath));
245 contentEncoding = QStringLiteral(
"deflate");
251 QFile *file = !compressedPath.
isEmpty() ?
new QFile(compressedPath) : new QFile(path);
253 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
254 Headers &headers = res->headers();
261 if (!_mimeTypeName.isEmpty()) {
262 headers.setContentType(_mimeTypeName);
263 }
else if (mimeType.
isValid()) {
264 headers.setContentType(mimeType.name());
266 headers.setContentLength(file->
size());
268 headers.setLastModified(currentDateTime);
270 headers.setHeader(QStringLiteral(
"CACHE_CONTROL"), QStringLiteral(
"public"));
272 if (!contentEncoding.
isEmpty()) {
274 headers.setContentEncoding(contentEncoding);
277 headers.pushHeader(QStringLiteral(
"Vary"), QStringLiteral(
"Accept-Encoding"));
283 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
288 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
292QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
293 const QDateTime &origLastModified,
294 Compression compression)
const
296 QString compressedPath;
300 switch (compression) {
303 suffix = QStringLiteral(
".gz");
305#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
307 suffix = QStringLiteral(
".br");
311 suffix = QStringLiteral(
".deflate");
314 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
318 if (checkPreCompressed) {
319 const QFileInfo origCompressed(origPath + suffix);
320 if (origCompressed.exists()) {
321 compressedPath = origCompressed.absoluteFilePath();
322 return compressedPath;
326 if (onTheFlyCompression) {
328 const QString path = cacheDir.absoluteFilePath(
332 const QFileInfo info(path);
334 if (info.exists() && (info.lastModified() > origLastModified)) {
335 compressedPath = path;
337 QLockFile lock(path + QLatin1String(
".lock"));
338 if (lock.tryLock(10)) {
339 switch (compression) {
340#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
342 if (compressBrotli(origPath, path)) {
343 compressedPath = path;
348#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
349 if (compressZopfli(origPath, path)) {
350 compressedPath = path;
355 if (compressGzip(origPath, path, origLastModified)) {
356 compressedPath = path;
360 if (compressDeflate(origPath, path)) {
361 compressedPath = path;
372 return compressedPath;
376static const quint32 crc_32_tab[] = {
377 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
378 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
379 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
380 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
381 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
382 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
383 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
384 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
385 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
386 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
387 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
388 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
389 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
390 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
391 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
392 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
393 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
394 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
395 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
396 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
397 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
398 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
399 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
400 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
401 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
402 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
403 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
404 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
405 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
406 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
407 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
408 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
409 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
410 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
411 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
412 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
413 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
414 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
415 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
416 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
417 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
418 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
419 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
423quint32 updateCRC32(
unsigned char ch, quint32 crc)
425 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
428quint32 crc32buf(
const QByteArray &data)
430 return ~std::accumulate(data.
begin(),
433 [](quint32 oldcrc32,
char buf) {
return updateCRC32(buf, oldcrc32); });
436bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
437 const QString &outputPath,
438 const QDateTime &origLastModified)
const
440 qCDebug(C_STATICCOMPRESSED,
441 "Compressing \"%s\" with gzip to \"%s\".",
442 qPrintable(inputPath),
443 qPrintable(outputPath));
445 QFile input(inputPath);
447 qCWarning(C_STATICCOMPRESSED)
448 <<
"Can not open input file to compress with gzip:" << inputPath;
452 const QByteArray data = input.readAll();
453 if (Q_UNLIKELY(data.
isEmpty())) {
454 qCWarning(C_STATICCOMPRESSED)
455 <<
"Can not read input file or input file is empty:" << inputPath;
460 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
463 QFile output(outputPath);
465 qCWarning(C_STATICCOMPRESSED)
466 <<
"Can not open output file to compress with gzip:" << outputPath;
470 if (Q_UNLIKELY(compressedData.
isEmpty())) {
471 qCWarning(C_STATICCOMPRESSED)
472 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
473 if (output.exists()) {
474 if (Q_UNLIKELY(!output.remove())) {
475 qCWarning(C_STATICCOMPRESSED)
476 <<
"Can not remove invalid compressed gzip file:" << outputPath;
484 compressedData.
remove(0, 6);
485 compressedData.
chop(4);
490 headerStream << quint16(0x1f8b) << quint16(0x0800)
494#elif defined Q_OS_WIN
496#elif defined Q_OS_MACOS
506 footerStream << crc32buf(data) << quint32(data.
size());
508 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
509 qCCritical(C_STATICCOMPRESSED,
510 "Failed to write compressed gzip file \"%s\": %s",
511 qPrintable(inputPath),
512 qPrintable(output.errorString()));
519bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
520 const QString &outputPath)
const
522 qCDebug(C_STATICCOMPRESSED,
523 "Compressing \"%s\" with deflate to \"%s\".",
524 qPrintable(inputPath),
525 qPrintable(outputPath));
527 QFile input(inputPath);
529 qCWarning(C_STATICCOMPRESSED)
530 <<
"Can not open input file to compress with deflate:" << inputPath;
534 const QByteArray data = input.readAll();
535 if (Q_UNLIKELY(data.
isEmpty())) {
536 qCWarning(C_STATICCOMPRESSED)
537 <<
"Can not read input file or input file is empty:" << inputPath;
542 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
545 QFile output(outputPath);
547 qCWarning(C_STATICCOMPRESSED)
548 <<
"Can not open output file to compress with deflate:" << outputPath;
552 if (Q_UNLIKELY(compressedData.
isEmpty())) {
553 qCWarning(C_STATICCOMPRESSED)
554 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
555 if (output.exists()) {
556 if (Q_UNLIKELY(!output.remove())) {
557 qCWarning(C_STATICCOMPRESSED)
558 <<
"Can not remove invalid compressed deflate file:" << outputPath;
566 compressedData.
remove(0, 6);
567 compressedData.
chop(4);
569 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
570 qCCritical(C_STATICCOMPRESSED,
571 "Failed to write compressed deflate file \"%s\": %s",
572 qPrintable(inputPath),
573 qPrintable(output.errorString()));
580#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
581bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
582 const QString &outputPath)
const
584 qCDebug(C_STATICCOMPRESSED,
585 "Compressing \"%s\" with zopfli to \"%s\".",
586 qPrintable(inputPath),
587 qPrintable(outputPath));
589 QFile input(inputPath);
591 qCWarning(C_STATICCOMPRESSED)
592 <<
"Can not open input file to compress with zopfli:" << inputPath;
596 const QByteArray data = input.readAll();
597 if (Q_UNLIKELY(data.
isEmpty())) {
598 qCWarning(C_STATICCOMPRESSED)
599 <<
"Can not read input file or input file is empty:" << inputPath;
604 ZopfliOptions options;
605 ZopfliInitOptions(&options);
606 options.numiterations = zopfliIterations;
608 unsigned char *out = 0;
611 ZopfliCompress(&options,
612 ZopfliFormat::ZOPFLI_FORMAT_GZIP,
613 reinterpret_cast<const unsigned char *
>(data.
constData()),
620 QFile output(outputPath);
622 qCWarning(C_STATICCOMPRESSED)
623 <<
"Can not open output file to compress with zopfli:" << outputPath;
625 if (Q_UNLIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
626 qCCritical(C_STATICCOMPRESSED,
627 "Failed to write compressed zopfli file \"%s\": %s",
628 qPrintable(inputPath),
629 qPrintable(output.errorString()));
630 if (output.exists()) {
631 if (Q_UNLIKELY(!output.remove())) {
632 qCWarning(C_STATICCOMPRESSED)
633 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
641 qCWarning(C_STATICCOMPRESSED)
642 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
651#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
652bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
653 const QString &outputPath)
const
655 qCDebug(C_STATICCOMPRESSED,
656 "Compressing \"%s\" with brotli to \"%s\".",
657 qPrintable(inputPath),
658 qPrintable(outputPath));
660 QFile input(inputPath);
662 qCWarning(C_STATICCOMPRESSED)
663 <<
"Can not open input file to compress with brotli:" << inputPath;
667 const QByteArray data = input.readAll();
668 if (Q_UNLIKELY(data.
isEmpty())) {
669 qCWarning(C_STATICCOMPRESSED)
670 <<
"Can not read input file or input file is empty:" << inputPath;
678 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.
size()));
679 if (Q_LIKELY(outSize > 0)) {
680 const uint8_t *in = (
const uint8_t *) data.
constData();
682 out = (uint8_t *) malloc(
sizeof(uint8_t) * (outSize + 1));
683 if (Q_LIKELY(out !=
nullptr)) {
684 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel,
685 BROTLI_DEFAULT_WINDOW,
691 if (Q_LIKELY(status == BROTLI_TRUE)) {
692 QFile output(outputPath);
694 if (Q_LIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) > -1)) {
699 "Failed to write brotli compressed data to output file \"%s\": %s",
700 qPrintable(outputPath),
701 qPrintable(output.errorString()));
702 if (output.exists()) {
703 if (Q_UNLIKELY(!output.remove())) {
704 qCWarning(C_STATICCOMPRESSED)
705 <<
"Can not remove invalid compressed brotli file:"
711 qCWarning(C_STATICCOMPRESSED,
712 "Failed to open output file for brotli compression: %s",
713 qPrintable(outputPath));
716 qCWarning(C_STATICCOMPRESSED,
717 "Failed to compress \"%s\" with brotli.",
718 qPrintable(inputPath));
722 qCWarning(C_STATICCOMPRESSED,
723 "Can not allocate needed output buffer of size %lu for brotli compression.",
724 sizeof(uint8_t) * (outSize + 1));
727 qCWarning(C_STATICCOMPRESSED,
728 "Needed output buffer too large to compress input of size %lu with brotli.",
729 static_cast<size_t>(data.
size()));
736#include "moc_staticcompressed.cpp"
The Cutelyst Application.
Engine * engine() const noexcept
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
Response * res() const noexcept
Response * response() const noexcept
QVariantMap config(const QString &entity) const
user configuration for the application
Plugin(Application *parent)
QString header(const QString &key) const
Headers headers() const noexcept
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
void setContentType(const QString &type)
virtual ~StaticCompressed() override
void setIncludePaths(const QStringList &paths)
void setDirs(const QStringList &dirs)
StaticCompressed(Application *parent)
virtual bool setup(Application *app) override
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
bool isEmpty() const const
QByteArray & remove(int pos, int len)
QByteArray toHex() const const
QByteArray hash(const QByteArray &data, Algorithm method)
qint64 toSecsSinceEpoch() const const
virtual bool open(OpenMode mode) override
virtual qint64 size() const const override
QString errorString() const const
QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QRegularExpressionMatch match(const QString &subject, int offset, MatchType matchType, MatchOptions matchOptions) const const
bool hasMatch() const const
QString writableLocation(StandardLocation type)
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QString join(const QString &separator) const const