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);
82 config.value(u
"log_failed_ip"_s, d->defaultConfig.value(u
"log_failed_ip"_s,
false))
85 const QString _defaultCacheDir =
87 d->cacheDir.setPath(config
88 .value(u
"cache_directory"_s,
89 d->defaultConfig.value(u
"cache_directory"_s, _defaultCacheDir))
92 if (Q_UNLIKELY(!d->cacheDir.exists())) {
93 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
94 qCCritical(C_STATICCOMPRESSED)
95 <<
"Failed to create cache directory for compressed static files at"
96 << d->cacheDir.absolutePath();
101 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
105 .value(u
"mime_types"_s,
106 d->defaultConfig.value(u
"mime_types"_s,
107 u
"text/css,application/javascript,text/javascript"_s))
109 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
116 d->defaultConfig.value(u
"suffixes"_s, u
"js.map,css.map,min.js.map,min.css.map"_s))
118 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
121 d->checkPreCompressed = config
122 .value(u
"check_pre_compressed"_s,
123 d->defaultConfig.value(u
"check_pre_compressed"_s,
true))
125 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
127 d->onTheFlyCompression = config
128 .value(u
"on_the_fly_compression"_s,
129 d->defaultConfig.value(u
"on_the_fly_compression"_s,
true))
131 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
133 QStringList supportedCompressions{u
"deflate"_s, u
"gzip"_s};
134 d->loadZlibConfig(config);
136#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
137 d->loadZopfliConfig(config);
138 qCInfo(C_STATICCOMPRESSED) <<
"Use Zopfli:" << d->useZopfli;
141#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
142 d->loadBrotliConfig(config);
143 supportedCompressions << u
"br"_s;
146#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
147 if (Q_UNLIKELY(!d->loadZstdConfig(config))) {
150 supportedCompressions << u
"zstd"_s;
154#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
157#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
165 .value(u
"compression_format_order"_s,
166 d->defaultConfig.value(u
"compression_format_order"_s,
167 defaultCompressionFormatOrder.
join(u
',')))
170 if (Q_UNLIKELY(_compressionFormatOrder.
empty())) {
171 _compressionFormatOrder = defaultCompressionFormatOrder;
172 qCWarning(C_STATICCOMPRESSED)
173 <<
"Invalid or empty value for compression_format_order. Has to be a string list "
174 "containing supported values. Using default value"
175 << defaultCompressionFormatOrder.
join(u
',');
177 for (
const auto &cfo : std::as_const(_compressionFormatOrder)) {
179 if (supportedCompressions.
contains(order)) {
180 d->compressionFormatOrder << order;
183 if (Q_UNLIKELY(d->compressionFormatOrder.empty())) {
184 d->compressionFormatOrder = defaultCompressionFormatOrder;
185 qCWarning(C_STATICCOMPRESSED)
186 <<
"Invalid or empty value for compression_format_order. Has to be a string list "
187 "containing supported values. Using default value"
188 << defaultCompressionFormatOrder.
join(u
',');
191 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.
join(u
',');
192 qCInfo(C_STATICCOMPRESSED) <<
"Compression format order:"
193 << d->compressionFormatOrder.join(u
',');
194 qCInfo(C_STATICCOMPRESSED) <<
"Include paths:" << d->includePaths;
197 d->beforePrepareAction(c, skipMethod);
203void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
212 bool found = std::ranges::any_of(dirs, [&](
const QString &dir) {
214 if (!locateCompressedFile(c, path)) {
218 res->
setBody(u
"File not found: "_s + path);
234 const QRegularExpression _re = re;
235 const QRegularExpressionMatch match = _re.
match(path);
236 if (match.
hasMatch() && locateCompressedFile(c, path)) {
241bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
243 for (
const QDir &includePath : includePaths) {
244 qCDebug(C_STATICCOMPRESSED)
245 <<
"Trying to find" << relPath <<
"in" << includePath.absolutePath();
246 const QString path = includePath.absoluteFilePath(relPath);
247 const QFileInfo fileInfo(path);
248 if (fileInfo.exists()) {
250 const QDateTime currentDateTime = fileInfo.lastModified();
256 static QMimeDatabase db;
259 QByteArray contentEncoding;
260 QString compressedPath;
261 QByteArray _mimeTypeName;
270 _mimeTypeName =
"application/json"_ba;
277 const auto acceptEncoding = c->
req()->
header(
"Accept-Encoding");
279 for (
const QString &format : std::as_const(compressionFormatOrder)) {
280 if (!acceptEncoding.contains(format.toLatin1())) {
283#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
284 if (format == u
"br") {
285 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
286 if (compressedPath.
isEmpty()) {
289 qCDebug(C_STATICCOMPRESSED)
290 <<
"Serving brotli compressed data from" << compressedPath;
291 contentEncoding =
"br"_ba;
296#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
297 if (format == u
"zstd") {
298 compressedPath = locateCacheFile(path, currentDateTime, Zstd);
299 if (compressedPath.
isEmpty()) {
302 qCDebug(C_STATICCOMPRESSED)
303 <<
"Serving zstd compressed data from" << compressedPath;
304 contentEncoding =
"zstd"_ba;
309 if (format == u
"gzip") {
310 compressedPath = locateCacheFile(
311 path, currentDateTime, useZopfli ? ZopfliGzip : Gzip);
312 if (compressedPath.
isEmpty()) {
315 qCDebug(C_STATICCOMPRESSED)
316 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
317 <<
"compressed gzip data from" << compressedPath;
318 contentEncoding =
"gzip"_ba;
321 }
else if (format == u
"deflate") {
322 compressedPath = locateCacheFile(
323 path, currentDateTime, useZopfli ? ZopfliDeflate : Deflate);
324 if (compressedPath.
isEmpty()) {
327 qCDebug(C_STATICCOMPRESSED)
328 <<
"Serving" << (useZopfli ?
"zopfli" :
"default")
329 <<
"compressed deflate data from" << compressedPath;
330 contentEncoding =
"deflate"_ba;
340 QFile *file = !compressedPath.
isEmpty() ?
new QFile(compressedPath) : new QFile(path);
342 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
343 Headers &headers = res->headers();
350 if (!_mimeTypeName.isEmpty()) {
351 headers.setContentType(_mimeTypeName);
352 }
else if (mimeType.
isValid()) {
353 headers.setContentType(mimeType.name().toLatin1());
355 headers.setContentLength(file->
size());
357 headers.setLastModified(currentDateTime);
359 headers.setCacheControl(
"public"_ba);
361 if (!contentEncoding.
isEmpty()) {
363 headers.setContentEncoding(contentEncoding);
365 qCDebug(C_STATICCOMPRESSED)
366 <<
"Encoding:" << headers.contentEncoding() <<
"Size:" << file->size()
367 <<
"Original Size:" << fileInfo.size();
370 headers.pushHeader(
"Vary"_ba,
"Accept-Encoding"_ba);
376 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->
errorString();
382 if (C_STATICCOMPRESSED().isWarningEnabled()) {
384 qCWarning(C_STATICCOMPRESSED).nospace().noquote()
385 <<
"File not found: \"" << relPath <<
'"' <<
" [client "
388 qCWarning(C_STATICCOMPRESSED).nospace().noquote()
389 <<
"File not found: \"" << relPath <<
'"';
396QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
397 const QDateTime &origLastModified,
398 Compression compression)
const
400 QString compressedPath;
404 switch (compression) {
409#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
414#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
421 suffix = u
".deflate"_s;
424 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
428 if (checkPreCompressed) {
429 const QFileInfo origCompressed(origPath + suffix);
430 if (origCompressed.exists()) {
431 compressedPath = origCompressed.absoluteFilePath();
432 return compressedPath;
436 if (onTheFlyCompression) {
438 const QString path = cacheDir.absoluteFilePath(
442 const QFileInfo info(path);
444 if (info.exists() && (info.lastModified() > origLastModified)) {
445 compressedPath = path;
447 QLockFile lock(path + u
".lock");
448 if (lock.tryLock(std::chrono::milliseconds{10})) {
449 switch (compression) {
450#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
452 if (compressZstd(origPath, path)) {
453 compressedPath = path;
457#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
459 if (compressBrotli(origPath, path)) {
460 compressedPath = path;
465#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
466 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_GZIP)) {
467 compressedPath = path;
472 if (compressGzip(origPath, path, origLastModified)) {
473 compressedPath = path;
477#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
478 if (compressZopfli(origPath, path, ZopfliFormat::ZOPFLI_FORMAT_ZLIB)) {
479 compressedPath = path;
484 if (compressDeflate(origPath, path)) {
485 compressedPath = path;
496 return compressedPath;
499void StaticCompressedPrivate::loadZlibConfig(
const QVariantMap &conf)
502 zlib.compressionLevel =
503 conf.value(u
"zlib_compression_level"_s,
504 defaultConfig.value(u
"zlib_compression_level"_s, zlib.compressionLevelDefault))
507 if (!ok || zlib.compressionLevel < zlib.compressionLevelMin ||
508 zlib.compressionLevel > zlib.compressionLevelMax) {
509 qCWarning(C_STATICCOMPRESSED).nospace()
510 <<
"Invalid value set for zlib_compression_level. Value hat to be between "
511 << zlib.compressionLevelMin <<
" and " << zlib.compressionLevelMax
512 <<
" inclusive. Using default value " << zlib.compressionLevelDefault;
513 zlib.compressionLevel = zlib.compressionLevelDefault;
517static constexpr std::array<quint32, 256> crc32Tab = []() {
518 std::array<quint32, 256> tab{0};
519 for (std::size_t n = 0; n < 256; n++) {
520 auto c =
static_cast<quint32
>(n);
521 for (
int k = 0; k < 8; k++) {
523 c = 0xedb88320L ^ (c >> 1);
533quint32 updateCRC32(
unsigned char ch, quint32 crc)
536 return crc32Tab[(crc ^ ch) & 0xff] ^ (crc >> 8);
539quint32 crc32buf(
const QByteArray &data)
541 return ~std::accumulate(data.
begin(),
544 [](quint32 oldcrc32,
char buf) {
545 return updateCRC32(
static_cast<unsigned char>(buf), oldcrc32);
549bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
550 const QString &outputPath,
551 const QDateTime &origLastModified)
const
553 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
555 QFile input(inputPath);
557 qCWarning(C_STATICCOMPRESSED)
558 <<
"Can not open input file to compress with gzip:" << inputPath;
562 const QByteArray data = input.readAll();
563 if (Q_UNLIKELY(data.
isEmpty())) {
564 qCWarning(C_STATICCOMPRESSED)
565 <<
"Can not read input file or input file is empty:" << inputPath;
570 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
573 QFile output(outputPath);
575 qCWarning(C_STATICCOMPRESSED)
576 <<
"Can not open output file to compress with gzip:" << outputPath;
580 if (Q_UNLIKELY(compressedData.
isEmpty())) {
581 qCWarning(C_STATICCOMPRESSED)
582 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
583 if (output.exists()) {
584 if (Q_UNLIKELY(!output.remove())) {
585 qCWarning(C_STATICCOMPRESSED)
586 <<
"Can not remove invalid compressed gzip file:" << outputPath;
594 compressedData.
remove(0, 6);
595 compressedData.
chop(4);
601 headerStream << quint8(0x1f) << quint8(0x8b)
608#elif defined Q_OS_MACOS
610#elif defined Q_OS_WIN
619 auto crc = crc32buf(data);
620 auto inSize = data.
size();
623 footerStream << static_cast<quint8>(crc % 256) <<
static_cast<quint8
>((crc >> 8) % 256)
624 <<
static_cast<quint8
>((crc >> 16) % 256) <<
static_cast<quint8
>((crc >> 24) % 256)
625 <<
static_cast<quint8
>(inSize % 256) <<
static_cast<quint8
>((inSize >> 8) % 256)
626 <<
static_cast<quint8
>((inSize >> 16) % 256)
627 <<
static_cast<quint8
>((inSize >> 24) % 256);
629 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
630 qCCritical(C_STATICCOMPRESSED).nospace()
631 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
638bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
639 const QString &outputPath)
const
641 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
643 QFile input(inputPath);
645 qCWarning(C_STATICCOMPRESSED)
646 <<
"Can not open input file to compress with deflate:" << inputPath;
650 const QByteArray data = input.readAll();
651 if (Q_UNLIKELY(data.
isEmpty())) {
652 qCWarning(C_STATICCOMPRESSED)
653 <<
"Can not read input file or input file is empty:" << inputPath;
658 QByteArray compressedData = qCompress(data, zlib.compressionLevel);
661 QFile output(outputPath);
663 qCWarning(C_STATICCOMPRESSED)
664 <<
"Can not open output file to compress with deflate:" << outputPath;
668 if (Q_UNLIKELY(compressedData.
isEmpty())) {
669 qCWarning(C_STATICCOMPRESSED)
670 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
671 if (output.exists()) {
672 if (Q_UNLIKELY(!output.remove())) {
673 qCWarning(C_STATICCOMPRESSED)
674 <<
"Can not remove invalid compressed deflate file:" << outputPath;
681 compressedData.
remove(0, 4);
683 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
684 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file "
685 << inputPath <<
": " << output.errorString();
692#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
693void StaticCompressedPrivate::loadZopfliConfig(
const QVariantMap &conf)
695 useZopfli = conf.value(u
"use_zopfli"_s, defaultConfig.value(u
"use_zopfli"_s,
false)).toBool();
697 ZopfliInitOptions(&zopfli.options);
699 zopfli.options.numiterations =
700 conf.value(u
"zopfli_iterations"_s,
701 defaultConfig.value(u
"zopfli_iterations"_s, zopfli.iterationsDefault))
703 if (!ok || zopfli.options.numiterations < zopfli.iterationsMin) {
704 qCWarning(C_STATICCOMPRESSED).nospace()
705 <<
"Invalid value set for zopfli_iterations. Value has to to be an integer value "
706 "greater than or equal to "
707 << zopfli.iterationsMin <<
". Using default value " << zopfli.iterationsDefault;
708 zopfli.options.numiterations = zopfli.iterationsDefault;
713bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
714 const QString &outputPath,
715 ZopfliFormat format)
const
717 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
719 QFile input(inputPath);
721 qCWarning(C_STATICCOMPRESSED)
722 <<
"Can not open input file to compress with zopfli:" << inputPath;
726 const QByteArray data = input.readAll();
727 if (Q_UNLIKELY(data.
isEmpty())) {
728 qCWarning(C_STATICCOMPRESSED)
729 <<
"Can not read input file or input file is empty:" << inputPath;
735 unsigned char *out{
nullptr};
738 ZopfliCompress(&zopfli.options,
740 reinterpret_cast<const unsigned char *
>(data.
constData()),
745 if (Q_UNLIKELY(outSize <= 0)) {
746 qCWarning(C_STATICCOMPRESSED)
747 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
752 QFile output{outputPath};
754 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
755 <<
"for zopfli compression:" << output.
errorString();
760 if (Q_UNLIKELY(output.
write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
762 if (Q_UNLIKELY(!output.
remove())) {
763 qCWarning(C_STATICCOMPRESSED)
764 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
767 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zopfli compressed data to output file"
779#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
780void StaticCompressedPrivate::loadBrotliConfig(
const QVariantMap &conf)
783 brotli.qualityLevel =
784 conf.value(u
"brotli_quality_level"_s,
785 defaultConfig.value(u
"brotli_quality_level"_s, brotli.qualityLevelDefault))
788 if (!ok || brotli.qualityLevel < BROTLI_MIN_QUALITY ||
789 brotli.qualityLevel > BROTLI_MAX_QUALITY) {
790 qCWarning(C_STATICCOMPRESSED).nospace()
791 <<
"Invalid value for brotli_quality_level. "
792 "Has to be an integer value between "
793 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
794 <<
" inclusive. Using default value " << brotli.qualityLevelDefault;
795 brotli.qualityLevel = brotli.qualityLevelDefault;
799bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
800 const QString &outputPath)
const
802 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
804 QFile input(inputPath);
806 qCWarning(C_STATICCOMPRESSED)
807 <<
"Can not open input file to compress with brotli:" << inputPath;
811 const QByteArray data = input.readAll();
812 if (Q_UNLIKELY(data.
isEmpty())) {
813 qCWarning(C_STATICCOMPRESSED)
814 <<
"Can not read input file or input file is empty:" << inputPath;
820 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.
size()));
821 if (Q_UNLIKELY(outSize == 0)) {
822 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size"
823 << data.
size() <<
"with brotli";
826 QByteArray outData{
static_cast<qsizetype
>(outSize), Qt::Uninitialized};
828 const auto in =
reinterpret_cast<const uint8_t *
>(data.
constData());
829 auto out =
reinterpret_cast<uint8_t *
>(outData.
data());
831 const BROTLI_BOOL status = BrotliEncoderCompress(brotli.qualityLevel,
832 BROTLI_DEFAULT_WINDOW,
838 if (Q_UNLIKELY(status != BROTLI_TRUE)) {
839 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
843 outData.
resize(
static_cast<qsizetype
>(outSize));
845 QFile output{outputPath};
847 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
848 <<
"for brotli compression:" << output.
errorString();
852 if (Q_UNLIKELY(output.
write(outData) < 0)) {
854 if (Q_UNLIKELY(!output.
remove())) {
855 qCWarning(C_STATICCOMPRESSED)
856 <<
"Can not remove invalid compressed brotli file:" << outputPath;
859 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write brotli compressed data to output file"
868#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZSTD
869bool StaticCompressedPrivate::loadZstdConfig(
const QVariantMap &conf)
871 zstd.ctx = ZSTD_createCCtx();
873 qCCritical(C_STATICCOMPRESSED) <<
"Failed to create Zstandard compression context";
879 zstd.compressionLevel =
880 conf.value(u
"zstd_compression_level"_s,
881 defaultConfig.value(u
"zstd_compression_level"_s, zstd.compressionLevelDefault))
883 if (!ok || zstd.compressionLevel < ZSTD_minCLevel() ||
884 zstd.compressionLevel > ZSTD_maxCLevel()) {
885 qCWarning(C_STATICCOMPRESSED).nospace()
886 <<
"Invalid value for zstd_compression_level. Has to be an integer value between "
887 << ZSTD_minCLevel() <<
" and " << ZSTD_maxCLevel() <<
" inclusive. Using default value "
888 << zstd.compressionLevelDefault;
889 zstd.compressionLevel = zstd.compressionLevelDefault;
895bool StaticCompressedPrivate::compressZstd(
const QString &inputPath,
896 const QString &outputPath)
const
898 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zstd to" << outputPath;
900 QFile input{inputPath};
902 qCWarning(C_STATICCOMPRESSED)
903 <<
"Can not open input file to compress with zstd:" << inputPath;
907 const QByteArray inData = input.
readAll();
908 if (Q_UNLIKELY(inData.
isEmpty())) {
909 qCWarning(C_STATICCOMPRESSED)
910 <<
"Can not read input file or input file is empty:" << inputPath;
916 const size_t outBufSize = ZSTD_compressBound(
static_cast<size_t>(inData.
size()));
917 if (Q_UNLIKELY(ZSTD_isError(outBufSize) == 1)) {
918 qCWarning(C_STATICCOMPRESSED)
919 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outBufSize);
922 QByteArray outData{
static_cast<qsizetype
>(outBufSize), Qt::Uninitialized};
924 auto outDataP =
static_cast<void *
>(outData.
data());
925 auto inDataP =
static_cast<const void *
>(inData.
constData());
927 const size_t outSize = ZSTD_compressCCtx(
928 zstd.ctx, outDataP, outBufSize, inDataP, inData.
size(), zstd.compressionLevel);
929 if (Q_UNLIKELY(ZSTD_isError(outSize) == 1)) {
930 qCWarning(C_STATICCOMPRESSED)
931 <<
"Failed to compress" << inputPath <<
"with zstd:" << ZSTD_getErrorName(outSize);
935 outData.
resize(
static_cast<qsizetype
>(outSize));
937 QFile output{outputPath};
939 qCWarning(C_STATICCOMPRESSED) <<
"Failed to open output file" << outputPath
940 <<
"for zstd compression:" << output.
errorString();
944 if (Q_UNLIKELY(output.
write(outData) < 0)) {
946 if (Q_UNLIKELY(!output.
remove())) {
947 qCWarning(C_STATICCOMPRESSED)
948 <<
"Can not remove invalid compressed zstd file:" << outputPath;
951 qCWarning(C_STATICCOMPRESSED) <<
"Failed to write zstd compressed data to output file"
960#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
Plugin(Application *parent)
QString addressString() const
QByteArray header(QAnyStringView key) const noexcept
Headers headers() const noexcept
void setContentType(const QByteArray &type)
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
~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)
void resize(qsizetype newSize, char c)
qsizetype size() const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, QCryptographicHash::Algorithm method)
qint64 toSecsSinceEpoch() const const
bool exists(const QString &fileName)
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
virtual qint64 size() const const override
virtual void close() override
QString errorString() const const
qint64 write(const QByteArray &data)
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) &&
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QByteArray toUtf8() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const