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>
16#include <QCoreApplication>
17#include <QCryptographicHash>
22#include <QLoggingCategory>
23#include <QMimeDatabase>
24#include <QStandardPaths>
26#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
27# include <brotli/encode.h>
33Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
37 , d_ptr(new StaticCompressedPrivate)
40 d->includePaths.append(parent->config(u
"root"_s).toString());
45 , d_ptr(new StaticCompressedPrivate)
48 d->includePaths.append(
parent->config(u
"root"_s).toString());
49 d->defaultConfig = defaultConfig;
57 d->includePaths.clear();
58 for (
const QString &path : paths) {
59 d->includePaths.append(
QDir(path));
72 d->serveDirsOnly = dirsOnly;
79 const QVariantMap config = app->
engine()->
config(u
"Cutelyst_StaticCompressed_Plugin"_s);
80 const QString _defaultCacheDir =
82 d->cacheDir.setPath(config
83 .value(u
"cache_directory"_s,
84 d->defaultConfig.value(u
"cache_directory"_s, _defaultCacheDir))
87 if (Q_UNLIKELY(!d->cacheDir.exists())) {
88 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
89 qCCritical(C_STATICCOMPRESSED)
90 <<
"Failed to create cache directory for compressed static files at"
91 << d->cacheDir.absolutePath();
96 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
100 .value(u
"mime_types"_s,
101 d->defaultConfig.value(u
"mime_types"_s,
102 u
"text/css,application/javascript,text/javascript"_s))
104 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
111 d->defaultConfig.value(u
"suffixes"_s, u
"js.map,css.map,min.js.map,min.css.map"_s))
113 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
116 d->checkPreCompressed = config
117 .
value(u
"check_pre_compressed"_s,
118 d->defaultConfig.value(u
"check_pre_compressed"_s,
true))
120 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
122 d->onTheFlyCompression = config
123 .value(u
"on_the_fly_compression"_s,
124 d->defaultConfig.value(u
"on_the_fly_compression"_s,
true))
126 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
128 QStringList supportedCompressions{u
"deflate"_s, u
"gzip"_s};
129 d->loadZlibConfig(config);
131#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
132 d->loadZopfliConfig(config);
133 qCInfo(C_STATICCOMPRESSED) <<
"Use Zopfli:" << d->useZopfli;
136#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
137 d->loadBrotliConfig(config);
138 supportedCompressions << u
"br"_s;
141#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
142 if (Q_UNLIKELY(!d->loadZstdConfig(config))) {
145 supportedCompressions << u
"zstd"_s;
149#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
152#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
160 .
value(u
"compression_format_order"_s,
161 d->defaultConfig.value(u
"compression_format_order"_s,
162 defaultCompressionFormatOrder.join(u
',')))
165 if (Q_UNLIKELY(_compressionFormatOrder.
empty())) {
166 _compressionFormatOrder = defaultCompressionFormatOrder;
167 qCWarning(C_STATICCOMPRESSED)
168 <<
"Invalid or empty value for compression_format_order. Has to be a string list "
169 "containing supported values. Using default value"
170 << defaultCompressionFormatOrder.
join(u
',');
172 for (
const auto &cfo : std::as_const(_compressionFormatOrder)) {
174 if (supportedCompressions.contains(order)) {
175 d->compressionFormatOrder << order;
178 if (Q_UNLIKELY(d->compressionFormatOrder.empty())) {
179 d->compressionFormatOrder = defaultCompressionFormatOrder;
180 qCWarning(C_STATICCOMPRESSED)
181 <<
"Invalid or empty value for compression_format_order. Has to be a string list "
182 "containing supported values. Using default value"
183 << defaultCompressionFormatOrder.join(u
',');
186 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
187 qCInfo(C_STATICCOMPRESSED) <<
"Compression format order:"
188 << d->compressionFormatOrder.join(u
',');
189 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
198void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
207 bool found = std::ranges::any_of(dirs, [&](
const QString &dir) {
209 if (!locateCompressedFile(c, path)) {
210 Response *res = c->response();
211 res->setStatus(Response::NotFound);
212 res->setContentType(
"text/html"_ba);
213 res->setBody(u
"File not found: "_s + path);
231 if (match.
hasMatch() && locateCompressedFile(c, path)) {
236bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
238 for (
const QDir &includePath : includePaths) {
239 qCDebug(C_STATICCOMPRESSED)
240 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
241 const QString path = includePath.absoluteFilePath(relPath);
243 if (fileInfo.exists()) {
245 const QDateTime currentDateTime = fileInfo.lastModified();
265 _mimeTypeName =
"application/json"_ba;
272 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
274 for (
const QString &format : std::as_const(compressionFormatOrder)) {
275 if (!acceptEncoding.contains(format.toLatin1())) {
278#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
279 if (format == u
"br") {
280 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
281 if (compressedPath.
isEmpty()) {
284 qCDebug(C_STATICCOMPRESSED)
285 <<
"Serving brotli compressed data from" << compressedPath;
286 contentEncoding =
"br"_ba;
291#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
292 if (format == u
"zstd") {
293 compressedPath = locateCacheFile(path, currentDateTime, Zstd);
294 if (compressedPath.
isEmpty()) {
297 qCDebug(C_STATICCOMPRESSED)
298 <<
"Serving zstd compressed data from" << compressedPath;
299 contentEncoding =
"zstd"_ba;
304 if (format == u
"gzip") {
305 compressedPath = locateCacheFile(
306 path, currentDateTime, useZopfli ? ZopfliGzip : Gzip);
307 if (compressedPath.
isEmpty()) {
310 qCDebug(C_STATICCOMPRESSED)
311 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
312 <<
"compressed gzip data from" << compressedPath;
313 contentEncoding =
"gzip"_ba;
316 }
else if (format == u
"deflate") {
317 compressedPath = locateCacheFile(
318 path, currentDateTime, useZopfli ? ZopfliDeflate : Deflate);
319 if (compressedPath.
isEmpty()) {
322 qCDebug(C_STATICCOMPRESSED)
323 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
324 <<
"compressed deflate data from" << compressedPath;
325 contentEncoding =
"deflate"_ba;
337 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
345 if (!_mimeTypeName.
isEmpty()) {
347 }
else if (mimeType.
isValid()) {
356 if (!contentEncoding.
isEmpty()) {
360 qCDebug(C_STATICCOMPRESSED)
362 <<
"Original Size:" << fileInfo.size();
365 headers.
pushHeader(
"Vary"_ba,
"Accept-Encoding"_ba);
371 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
377 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
381QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
383 Compression compression)
const
389 switch (compression) {
394#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
399#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
406 suffix = u
".deflate"_s;
409 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
413 if (checkPreCompressed) {
414 const QFileInfo origCompressed(origPath + suffix);
415 if (origCompressed.exists()) {
416 compressedPath = origCompressed.absoluteFilePath();
417 return compressedPath;
421 if (onTheFlyCompression) {
423 const QString path = cacheDir.absoluteFilePath(
429 if (info.exists() && (info.lastModified() > origLastModified)) {
430 compressedPath = path;
433 if (lock.tryLock(std::chrono::milliseconds{10})) {
434 switch (compression) {
435#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
437 if (compressZstd(origPath, path)) {
438 compressedPath = path;
442#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
444 if (compressBrotli(origPath, path)) {
445 compressedPath = path;
450#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
451 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_GZIP)) {
452 compressedPath = path;
457 if (compressGzip(origPath, path, origLastModified)) {
458 compressedPath = path;
462#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
463 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_ZLIB)) {
464 compressedPath = path;
469 if (compressDeflate(origPath, path)) {
470 compressedPath = path;
481 return compressedPath;
484void StaticCompressedPrivate::loadZlibConfig(
const QVariantMap &conf)
487 zlib.compressionLevel =
488 conf.value(u
"zlib_compression_level"_s,
489 defaultConfig.value(u
"zlib_compression_level"_s, zlib.compressionLevelDefault))
492 if (!ok || zlib.compressionLevel < zlib.compressionLevelMin ||
493 zlib.compressionLevel > zlib.compressionLevelMax) {
494 qCWarning(C_STATICCOMPRESSED).nospace()
495 <<
"Invalid value set for zlib_compression_level. Value hat to be between "
496 << zlib.compressionLevelMin <<
" and " << zlib.compressionLevelMax
497 <<
" inclusive. Using default value " << zlib.compressionLevelDefault;
498 zlib.compressionLevel = zlib.compressionLevelDefault;
502static constexpr std::array<quint32, 256> crc32Tab = []() {
503 std::array<quint32, 256> tab{0};
504 for (std::size_t n = 0; n < 256; n++) {
505 auto c =
static_cast<quint32
>(n);
506 for (
int k = 0; k < 8; k++) {
508 c = 0xedb88320L ^ (c >> 1);
518quint32 updateCRC32(
unsigned char ch, quint32 crc)
521 return crc32Tab[(crc ^ ch) & 0xff] ^ (crc >> 8);
526 return ~std::accumulate(data.
begin(),
529 [](quint32 oldcrc32,
char buf) {
530 return updateCRC32(
static_cast<unsigned char>(buf), oldcrc32);
534bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
538 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
540 QFile input(inputPath);
542 qCWarning(C_STATICCOMPRESSED)
543 <<
"Can not open input file to compress with gzip:" << inputPath;
548 if (Q_UNLIKELY(data.
isEmpty())) {
549 qCWarning(C_STATICCOMPRESSED)
550 <<
"Can not read input file or input file is empty:" << inputPath;
555 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
558 QFile output(outputPath);
560 qCWarning(C_STATICCOMPRESSED)
561 <<
"Can not open output file to compress with gzip:" << outputPath;
565 if (Q_UNLIKELY(compressedData.
isEmpty())) {
566 qCWarning(C_STATICCOMPRESSED)
567 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
568 if (output.exists()) {
569 if (Q_UNLIKELY(!output.remove())) {
570 qCWarning(C_STATICCOMPRESSED)
571 <<
"Can not remove invalid compressed gzip file:" << outputPath;
579 compressedData.
remove(0, 6);
580 compressedData.
chop(4);
586 headerStream << quint8(0x1f) << quint8(0x8b)
593#elif defined Q_OS_MACOS
595#elif defined Q_OS_WIN
604 auto crc = crc32buf(data);
605 auto inSize = data.
size();
608 footerStream << static_cast<quint8>(crc % 256) <<
static_cast<quint8
>((crc >> 8) % 256)
609 <<
static_cast<quint8
>((crc >> 16) % 256) <<
static_cast<quint8
>((crc >> 24) % 256)
610 <<
static_cast<quint8
>(inSize % 256) <<
static_cast<quint8
>((inSize >> 8) % 256)
611 <<
static_cast<quint8
>((inSize >> 16) % 256)
612 <<
static_cast<quint8
>((inSize >> 24) % 256);
614 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
615 qCCritical(C_STATICCOMPRESSED).nospace()
616 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
623bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
624 const QString &outputPath)
const
626 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
628 QFile input(inputPath);
630 qCWarning(C_STATICCOMPRESSED)
631 <<
"Can not open input file to compress with deflate:" << inputPath;
636 if (Q_UNLIKELY(data.
isEmpty())) {
637 qCWarning(C_STATICCOMPRESSED)
638 <<
"Can not read input file or input file is empty:" << inputPath;
643 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
646 QFile output(outputPath);
648 qCWarning(C_STATICCOMPRESSED)
649 <<
"Can not open output file to compress with deflate:" << outputPath;
653 if (Q_UNLIKELY(compressedData.
isEmpty())) {
654 qCWarning(C_STATICCOMPRESSED)
655 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
656 if (output.exists()) {
657 if (Q_UNLIKELY(!output.remove())) {
658 qCWarning(C_STATICCOMPRESSED)
659 <<
"Can not remove invalid compressed deflate file:" << outputPath;
666 compressedData.
remove(0, 4);
668 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
669 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file "
670 << inputPath <<
": " << output.errorString();
677#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
678void StaticCompressedPrivate::loadZopfliConfig(
const QVariantMap &conf)
680 useZopfli = conf.value(u
"use_zopfli"_s, defaultConfig.value(u
"use_zopfli"_s,
false)).toBool();
682 ZopfliInitOptions(&zopfli.options);
684 zopfli.options.numiterations =
685 conf.value(u
"zopfli_iterations"_s,
686 defaultConfig.value(u
"zopfli_iterations"_s, zopfli.iterationsDefault))
688 if (!ok || zopfli.options.numiterations < zopfli.iterationsMin) {
689 qCWarning(C_STATICCOMPRESSED).nospace()
690 <<
"Invalid value set for zopfli_iterations. Value has to to be an integer value "
691 "greater than or equal to "
692 << zopfli.iterationsMin <<
". Using default value " << zopfli.iterationsDefault;
693 zopfli.options.numiterations = zopfli.iterationsDefault;
698bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
700 ZopfliFormat format)
const
702 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
704 QFile input(inputPath);
706 qCWarning(C_STATICCOMPRESSED)
707 <<
"Can not open input file to compress with zopfli:" << inputPath;
712 if (Q_UNLIKELY(data.
isEmpty())) {
713 qCWarning(C_STATICCOMPRESSED)
714 <<
"Can not read input file or input file is empty:" << inputPath;
720 unsigned char *out{
nullptr};
723 ZopfliCompress(&zopfli.options,
725 reinterpret_cast<const unsigned char *
>(data.
constData()),
730 if (Q_UNLIKELY(outSize <= 0)) {
731 qCWarning(C_STATICCOMPRESSED)
732 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
737 QFile output{outputPath};
739 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
740 <<
"for zopfli compression:" << output.errorString();
745 if (Q_UNLIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
746 if (output.exists()) {
747 if (Q_UNLIKELY(!output.remove())) {
748 qCWarning(C_STATICCOMPRESSED)
749 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
752 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zopfli compressed data to output file"
753 << outputPath <<
":" << output.errorString();
764#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
765void StaticCompressedPrivate::loadBrotliConfig(
const QVariantMap &conf)
768 brotli.qualityLevel =
769 conf.value(u
"brotli_quality_level"_s,
770 defaultConfig.value(u
"brotli_quality_level"_s, brotli.qualityLevelDefault))
773 if (!ok || brotli.qualityLevel < BROTLI_MIN_QUALITY ||
774 brotli.qualityLevel > BROTLI_MAX_QUALITY) {
775 qCWarning(C_STATICCOMPRESSED).nospace()
776 <<
"Invalid value for brotli_quality_level. "
777 "Has to be an integer value between "
778 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
779 <<
" inclusive. Using default value " << brotli.qualityLevelDefault;
780 brotli.qualityLevel = brotli.qualityLevelDefault;
784bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
785 const QString &outputPath)
const
787 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
789 QFile input(inputPath);
791 qCWarning(C_STATICCOMPRESSED)
792 <<
"Can not open input file to compress with brotli:" << inputPath;
797 if (Q_UNLIKELY(data.
isEmpty())) {
798 qCWarning(C_STATICCOMPRESSED)
799 <<
"Can not read input file or input file is empty:" << inputPath;
805 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.
size()));
806 if (Q_UNLIKELY(outSize == 0)) {
807 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size"
808 << data.
size() <<
"with brotli";
811 QByteArray outData{
static_cast<qsizetype
>(outSize), Qt::Uninitialized};
813 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
814 auto out =
reinterpret_cast<uint8_t *
>(outData.data());
816 const BROTLI_BOOL status = BrotliEncoderCompress(brotli.qualityLevel,
817 BROTLI_DEFAULT_WINDOW,
823 if (Q_UNLIKELY(status != BROTLI_TRUE)) {
824 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
828 outData.
resize(
static_cast<qsizetype
>(outSize));
830 QFile output{outputPath};
832 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
833 <<
"for brotli compression:" << output.errorString();
837 if (Q_UNLIKELY(output.write(outData) < 0)) {
838 if (output.exists()) {
839 if (Q_UNLIKELY(!output.remove())) {
840 qCWarning(C_STATICCOMPRESSED)
841 <<
"Can not remove invalid compressed brotli file:" << outputPath;
844 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write brotli compressed data to output file"
845 << outputPath <<
":" << output.errorString();
853#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
854bool StaticCompressedPrivate::loadZstdConfig(
const QVariantMap &conf)
856 zstd.ctx = ZSTD_createCCtx();
858 qCCritical(C_STATICCOMPRESSED) <<
"Failed to create Zstandard compression context";
864 zstd.compressionLevel =
865 conf.value(u
"zstd_compression_level"_s,
866 defaultConfig.value(u
"zstd_compression_level"_s, zstd.compressionLevelDefault))
868 if (!ok || zstd.compressionLevel < ZSTD_minCLevel() ||
869 zstd.compressionLevel > ZSTD_maxCLevel()) {
870 qCWarning(C_STATICCOMPRESSED).nospace()
871 <<
"Invalid value for zstd_compression_level. Has to be an integer value between "
872 << ZSTD_minCLevel() <<
" and " << ZSTD_maxCLevel() <<
" inclusive. Using default value "
873 << zstd.compressionLevelDefault;
874 zstd.compressionLevel = zstd.compressionLevelDefault;
880bool StaticCompressedPrivate::compressZstd(
const QString &inputPath,
881 const QString &outputPath)
const
883 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zstd to" << outputPath;
885 QFile input{inputPath};
887 qCWarning(C_STATICCOMPRESSED)
888 <<
"Can not open input file to compress with zstd:" << inputPath;
893 if (Q_UNLIKELY(inData.
isEmpty())) {
894 qCWarning(C_STATICCOMPRESSED)
895 <<
"Can not read input file or input file is empty:" << inputPath;
901 const size_t outBufSize = ZSTD_compressBound(
static_cast<size_t>(inData.
size()));
902 if (Q_UNLIKELY(ZSTD_isError(outBufSize) == 1)) {
903 qCWarning(C_STATICCOMPRESSED)
904 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outBufSize);
907 QByteArray outData{
static_cast<qsizetype
>(outBufSize), Qt::Uninitialized};
909 auto outDataP =
static_cast<void *
>(outData.data());
910 auto inDataP =
static_cast<const void *
>(inData.
constData());
912 const size_t outSize = ZSTD_compressCCtx(
913 zstd.ctx, outDataP, outBufSize, inDataP, inData.
size(), zstd.compressionLevel);
914 if (Q_UNLIKELY(ZSTD_isError(outSize) == 1)) {
915 qCWarning(C_STATICCOMPRESSED)
916 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outSize);
920 outData.resize(
static_cast<qsizetype
>(outSize));
922 QFile output{outputPath};
924 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
925 <<
"for zstd compression:" << output.errorString();
929 if (Q_UNLIKELY(output.write(outData) < 0)) {
930 if (output.exists()) {
931 if (Q_UNLIKELY(!output.remove())) {
932 qCWarning(C_STATICCOMPRESSED)
933 <<
"Can not remove invalid compressed zstd file:" << outputPath;
936 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zstd compressed data to output file"
937 << outputPath <<
":" << output.errorString();
945#include "moc_staticcompressed.cpp"
The Cutelyst application.
Engine * engine() const noexcept
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
Response * res() const noexcept
QVariantMap config(const QString &entity) const
Base class for Cutelyst Plugins.
QByteArray header(QAnyStringView key) const noexcept
Headers headers() const noexcept
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
Headers & headers() noexcept
Serve static files compressed on the fly or pre-compressed.
~StaticCompressed() override
void setServeDirsOnly(bool dirsOnly)
void setIncludePaths(const QStringList &paths)
void setDirs(const QStringList &dirs)
StaticCompressed(Application *parent)
bool setup(Application *app) override
The Cutelyst namespace holds all public Cutelyst API.
QByteArray::iterator begin()
const char * constData() const const
QByteArray::iterator end()
bool isEmpty() const const
QByteArray & remove(qsizetype pos, qsizetype len)
qsizetype size() const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, QCryptographicHash::Algorithm method)
qint64 toSecsSinceEpoch() const const
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
virtual qint64 size() const const override
QString errorString() const const
T value(qsizetype i) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
bool hasMatch() const const
QString writableLocation(QStandardPaths::StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
void resize(qsizetype newSize, QChar fillChar)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
QString join(QChar separator) const const