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>
15#include <QCoreApplication>
16#include <QCryptographicHash>
21#include <QLoggingCategory>
22#include <QMimeDatabase>
23#include <QStandardPaths>
25#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
29#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
30# include <brotli/encode.h>
35Q_LOGGING_CATEGORY(C_STATICCOMPRESSED,
"cutelyst.plugin.staticcompressed", QtWarningMsg)
39 , d_ptr(new StaticCompressedPrivate)
42 d->includePaths.append(parent->config(u
"root"_qs).toString());
50 d->includePaths.clear();
51 for (
const QString &path : paths) {
52 d->includePaths.append(QDir(path));
66 const QVariantMap config = app->
engine()->
config(u
"Cutelyst_StaticCompressed_Plugin"_qs);
67 const QString _defaultCacheDir =
68 QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
69 QLatin1String(
"/compressed-static");
70 d->cacheDir.setPath(config.value(u
"cache_directory"_qs, _defaultCacheDir).toString());
72 if (Q_UNLIKELY(!d->cacheDir.exists())) {
73 if (!d->cacheDir.mkpath(d->cacheDir.absolutePath())) {
74 qCCritical(C_STATICCOMPRESSED)
75 <<
"Failed to create cache directory for compressed static files at"
76 << d->cacheDir.absolutePath();
81 qCInfo(C_STATICCOMPRESSED) <<
"Compressed cache directory:" << d->cacheDir.absolutePath();
83 const QString _mimeTypes =
84 config.value(u
"mime_types"_qs, u
"text/css,application/javascript"_qs).toString();
85 qCInfo(C_STATICCOMPRESSED) <<
"MIME Types:" << _mimeTypes;
86 d->mimeTypes = _mimeTypes.split(u
',', Qt::SkipEmptyParts);
88 const QString _suffixes =
89 config.value(u
"suffixes"_qs, u
"js.map,css.map,min.js.map,min.css.map"_qs).toString();
90 qCInfo(C_STATICCOMPRESSED) <<
"Suffixes:" << _suffixes;
91 d->suffixes = _suffixes.split(u
',', Qt::SkipEmptyParts);
93 d->checkPreCompressed = config.value(u
"check_pre_compressed"_qs,
true).toBool();
94 qCInfo(C_STATICCOMPRESSED) <<
"Check for pre-compressed files:" << d->checkPreCompressed;
96 d->onTheFlyCompression = config.value(u
"on_the_fly_compression"_qs,
true).toBool();
97 qCInfo(C_STATICCOMPRESSED) <<
"Compress static files on the fly:" << d->onTheFlyCompression;
99 QStringList supportedCompressions{u
"deflate"_qs, u
"gzip"_qs};
102 d->zlibCompressionLevel = config
103 .value(u
"zlib_compression_level"_qs,
104 StaticCompressedPrivate::zlibCompressionLevelDefault)
107 qCWarning(C_STATICCOMPRESSED).nospace()
108 <<
"Invalid value set for zlib_compression_level. "
109 "Has to to be an integer value between "
110 << StaticCompressedPrivate::zlibCompressionLevelMin <<
" and "
111 << StaticCompressedPrivate::zlibCompressionLevelMax
112 <<
" inclusive. Using default value "
113 << StaticCompressedPrivate::zlibCompressionLevelDefault;
116 if (d->zlibCompressionLevel < StaticCompressedPrivate::zlibCompressionLevelMin ||
117 d->zlibCompressionLevel > StaticCompressedPrivate::zlibCompressionLevelMax) {
118 qCWarning(C_STATICCOMPRESSED).nospace()
119 <<
"Invalid value " << d->zlibCompressionLevel
120 <<
" set for zlib_compression_level. Value hat to be between "
121 << StaticCompressedPrivate::zlibCompressionLevelMin <<
" and "
122 << StaticCompressedPrivate::zlibCompressionLevelMax
123 <<
" inclusive. Using default value "
124 << StaticCompressedPrivate::zlibCompressionLevelDefault;
125 d->zlibCompressionLevel = StaticCompressedPrivate::zlibCompressionLevelDefault;
128#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
129 d->zopfliIterations =
130 config.value(u
"zopfli_iterations"_qs, StaticCompressedPrivate::zopfliIterationsDefault)
133 qCWarning(C_STATICCOMPRESSED).nospace()
134 <<
"Invalid value for zopfli_iterations. "
135 "Has to be an integer value greater than or equal to "
136 << StaticCompressedPrivate::zopfliIterationsMin <<
". Using default value "
137 << StaticCompressedPrivate::zopfliIterationsDefault;
138 d->zopfliIterations = StaticCompressedPrivate::zopfliIterationsDefault;
141 if (d->zopfliIterations < StaticCompressedPrivate::zopfliIterationsMin) {
142 qCWarning(C_STATICCOMPRESSED).nospace()
143 <<
"Invalid value " << d->zopfliIterations
144 <<
" set for zopfli_iterations. Value has to to be greater than or equal to "
145 << StaticCompressedPrivate::zopfliIterationsMin <<
". Using default value "
146 << StaticCompressedPrivate::zopfliIterationsDefault;
147 d->zopfliIterations = StaticCompressedPrivate::zopfliIterationsDefault;
149 d->useZopfli = config.value(u
"use_zopfli"_qs,
false).toBool();
150 supportedCompressions << u
"zopfli"_qs;
153#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
154 d->brotliQualityLevel =
155 config.value(u
"brotli_quality_level"_qs, StaticCompressedPrivate::brotliQualityLevelDefault)
158 qCWarning(C_STATICCOMPRESSED).nospace()
159 <<
"Invalid value for brotli_quality_level. "
160 "Has to be an integer value between "
161 << BROTLI_MIN_QUALITY <<
" and " << BROTLI_MAX_QUALITY
162 <<
" inclusive. Using default value "
163 << StaticCompressedPrivate::brotliQualityLevelDefault;
164 d->brotliQualityLevel = StaticCompressedPrivate::brotliQualityLevelDefault;
167 if (d->brotliQualityLevel < BROTLI_MIN_QUALITY || d->brotliQualityLevel > BROTLI_MAX_QUALITY) {
168 qCWarning(C_STATICCOMPRESSED).nospace()
169 <<
"Invalid value " << d->brotliQualityLevel
170 <<
" set for brotli_quality_level. Value has to be between " << BROTLI_MIN_QUALITY
171 <<
" and " << BROTLI_MAX_QUALITY <<
" inclusive. Using default value "
172 << StaticCompressedPrivate::brotliQualityLevelDefault;
173 d->brotliQualityLevel = StaticCompressedPrivate::brotliQualityLevelDefault;
175 supportedCompressions << u
"brotli"_qs;
178 qCInfo(C_STATICCOMPRESSED) <<
"Supported compressions:" << supportedCompressions.join(u
',');
181 d->beforePrepareAction(c, skipMethod);
187void StaticCompressedPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
193 const QString path = c->req()->path();
194 const QRegularExpression _re = re;
196 for (
const QString &dir : dirs) {
197 if (path.startsWith(dir)) {
198 if (!locateCompressedFile(c, path)) {
202 res->
setBody(u
"File not found: "_qs + path);
210 const QRegularExpressionMatch match = _re.match(path);
211 if (match.hasMatch() && locateCompressedFile(c, path)) {
216bool StaticCompressedPrivate::locateCompressedFile(
Context *c,
const QString &relPath)
const
218 for (
const QDir &includePath : includePaths) {
219 const QString path = includePath.absoluteFilePath(relPath);
220 const QFileInfo fileInfo(path);
221 if (fileInfo.exists()) {
223 const QDateTime currentDateTime = fileInfo.lastModified();
229 static QMimeDatabase db;
231 const QMimeType mimeType = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension);
232 QByteArray contentEncoding;
233 QString compressedPath;
234 QByteArray _mimeTypeName;
236 if (mimeType.isValid()) {
240 if (mimeType.isDefault()) {
241 if (path.endsWith(u
"css.map", Qt::CaseInsensitive) ||
242 path.endsWith(u
"js.map", Qt::CaseInsensitive)) {
243 _mimeTypeName =
"application/json"_qba;
247 if (mimeTypes.contains(mimeType.name(), Qt::CaseInsensitive) ||
248 suffixes.contains(fileInfo.completeSuffix(), Qt::CaseInsensitive)) {
250 const auto acceptEncoding = c->req()->
header(
"Accept-Encoding");
251 qCDebug(C_STATICCOMPRESSED) <<
"Accept-Encoding:" << acceptEncoding;
253#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
254 if (acceptEncoding.contains(
"br")) {
255 compressedPath = locateCacheFile(path, currentDateTime, Brotli);
256 if (!compressedPath.isEmpty()) {
257 qCDebug(C_STATICCOMPRESSED)
258 <<
"Serving brotli compressed data from" << compressedPath;
259 contentEncoding =
"br"_qba;
263 if (acceptEncoding.contains(
"gzip")) {
265 locateCacheFile(path, currentDateTime, useZopfli ? Zopfli : Gzip);
266 if (!compressedPath.isEmpty()) {
267 qCDebug(C_STATICCOMPRESSED)
268 <<
"Serving" << (useZopfli ?
"zopfli" :
"gzip")
269 <<
"compressed data from" << compressedPath;
270 contentEncoding =
"gzip"_qba;
272 }
else if (acceptEncoding.contains(
"deflate")) {
273 compressedPath = locateCacheFile(path, currentDateTime, Deflate);
274 if (!compressedPath.isEmpty()) {
275 qCDebug(C_STATICCOMPRESSED)
276 <<
"Serving deflate compressed data from" << compressedPath;
277 contentEncoding =
"deflate"_qba;
283 QFile *file = !compressedPath.isEmpty() ?
new QFile(compressedPath) : new QFile(path);
284 if (file->open(QFile::ReadOnly)) {
285 qCDebug(C_STATICCOMPRESSED) <<
"Serving" << path;
293 if (!_mimeTypeName.isEmpty()) {
295 }
else if (mimeType.isValid()) {
304 if (!contentEncoding.isEmpty()) {
309 headers.
pushHeader(
"Vary"_qba,
"Accept-Encoding"_qba);
315 qCWarning(C_STATICCOMPRESSED) <<
"Could not serve" << path << file->errorString();
321 qCWarning(C_STATICCOMPRESSED) <<
"File not found" << relPath;
325QString StaticCompressedPrivate::locateCacheFile(
const QString &origPath,
326 const QDateTime &origLastModified,
327 Compression compression)
const
329 QString compressedPath;
333 switch (compression) {
338#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
344 suffix = u
".deflate"_qs;
347 Q_ASSERT_X(
false,
"locate cache file",
"invalid compression type");
351 if (checkPreCompressed) {
352 const QFileInfo origCompressed(origPath + suffix);
353 if (origCompressed.exists()) {
354 compressedPath = origCompressed.absoluteFilePath();
355 return compressedPath;
359 if (onTheFlyCompression) {
361 const QString path = cacheDir.absoluteFilePath(
363 QCryptographicHash::hash(origPath.toUtf8(), QCryptographicHash::Md5).toHex()) +
365 const QFileInfo info(path);
367 if (info.exists() && (info.lastModified() > origLastModified)) {
368 compressedPath = path;
370 QLockFile lock(path + QLatin1String(
".lock"));
371 if (lock.tryLock(std::chrono::milliseconds{10})) {
372 switch (compression) {
373#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
375 if (compressBrotli(origPath, path)) {
376 compressedPath = path;
381#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
382 if (compressZopfli(origPath, path)) {
383 compressedPath = path;
388 if (compressGzip(origPath, path, origLastModified)) {
389 compressedPath = path;
393 if (compressDeflate(origPath, path)) {
394 compressedPath = path;
405 return compressedPath;
409static const quint32 crc_32_tab[] = {
410 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
411 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
412 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
413 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
414 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
415 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
416 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
417 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
418 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
419 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
420 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
421 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
422 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
423 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
424 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
425 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
426 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
427 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
428 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
429 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
430 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
431 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
432 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
433 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
434 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
435 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
436 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
437 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
438 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
439 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
440 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
441 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
442 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
443 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
444 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
445 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
446 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
447 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
448 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
449 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
450 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
451 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
452 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
456quint32 updateCRC32(
unsigned char ch, quint32 crc)
458 return (crc_32_tab[((crc) ^ (quint8(ch))) & 0xff] ^ ((crc) >> 8));
461quint32 crc32buf(
const QByteArray &data)
463 return ~std::accumulate(data.begin(),
466 [](quint32 oldcrc32,
char buf) {
return updateCRC32(buf, oldcrc32); });
469bool StaticCompressedPrivate::compressGzip(
const QString &inputPath,
470 const QString &outputPath,
471 const QDateTime &origLastModified)
const
473 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with gzip to" << outputPath;
475 QFile input(inputPath);
476 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
477 qCWarning(C_STATICCOMPRESSED)
478 <<
"Can not open input file to compress with gzip:" << inputPath;
482 const QByteArray data = input.readAll();
483 if (Q_UNLIKELY(data.isEmpty())) {
484 qCWarning(C_STATICCOMPRESSED)
485 <<
"Can not read input file or input file is empty:" << inputPath;
490 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
493 QFile output(outputPath);
494 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
495 qCWarning(C_STATICCOMPRESSED)
496 <<
"Can not open output file to compress with gzip:" << outputPath;
500 if (Q_UNLIKELY(compressedData.isEmpty())) {
501 qCWarning(C_STATICCOMPRESSED)
502 <<
"Failed to compress file with gzip, compressed data is empty:" << inputPath;
503 if (output.exists()) {
504 if (Q_UNLIKELY(!output.remove())) {
505 qCWarning(C_STATICCOMPRESSED)
506 <<
"Can not remove invalid compressed gzip file:" << outputPath;
514 compressedData.remove(0, 6);
515 compressedData.chop(4);
518 QDataStream headerStream(&header, QIODevice::WriteOnly);
520 headerStream << quint16(0x1f8b) << quint16(0x0800)
521 << quint32(origLastModified.toSecsSinceEpoch())
524#elif defined Q_OS_WIN
526#elif defined Q_OS_MACOS
535 QDataStream footerStream(&footer, QIODevice::WriteOnly);
536 footerStream << crc32buf(data) << quint32(data.size());
538 if (Q_UNLIKELY(output.write(header + compressedData + footer) < 0)) {
539 qCCritical(C_STATICCOMPRESSED).nospace()
540 <<
"Failed to write compressed gzip file " << inputPath <<
": " << output.errorString();
547bool StaticCompressedPrivate::compressDeflate(
const QString &inputPath,
548 const QString &outputPath)
const
550 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with deflate to" << outputPath;
552 QFile input(inputPath);
553 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
554 qCWarning(C_STATICCOMPRESSED)
555 <<
"Can not open input file to compress with deflate:" << inputPath;
559 const QByteArray data = input.readAll();
560 if (Q_UNLIKELY(data.isEmpty())) {
561 qCWarning(C_STATICCOMPRESSED)
562 <<
"Can not read input file or input file is empty:" << inputPath;
567 QByteArray compressedData = qCompress(data, zlibCompressionLevel);
570 QFile output(outputPath);
571 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
572 qCWarning(C_STATICCOMPRESSED)
573 <<
"Can not open output file to compress with deflate:" << outputPath;
577 if (Q_UNLIKELY(compressedData.isEmpty())) {
578 qCWarning(C_STATICCOMPRESSED)
579 <<
"Failed to compress file with deflate, compressed data is empty:" << inputPath;
580 if (output.exists()) {
581 if (Q_UNLIKELY(!output.remove())) {
582 qCWarning(C_STATICCOMPRESSED)
583 <<
"Can not remove invalid compressed deflate file:" << outputPath;
591 compressedData.remove(0, 6);
592 compressedData.chop(4);
594 if (Q_UNLIKELY(output.write(compressedData) < 0)) {
595 qCCritical(C_STATICCOMPRESSED).nospace() <<
"Failed to write compressed deflate file "
596 << inputPath <<
": " << output.errorString();
603#ifdef CUTELYST_STATICCOMPRESSED_WITH_ZOPFLI
604bool StaticCompressedPrivate::compressZopfli(
const QString &inputPath,
605 const QString &outputPath)
const
607 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with zopfli to" << outputPath;
609 QFile input(inputPath);
610 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
611 qCWarning(C_STATICCOMPRESSED)
612 <<
"Can not open input file to compress with zopfli:" << inputPath;
616 const QByteArray data = input.readAll();
617 if (Q_UNLIKELY(data.isEmpty())) {
618 qCWarning(C_STATICCOMPRESSED)
619 <<
"Can not read input file or input file is empty:" << inputPath;
624 ZopfliOptions options;
625 ZopfliInitOptions(&options);
626 options.numiterations = zopfliIterations;
628 unsigned char *out{
nullptr};
631 ZopfliCompress(&options,
632 ZopfliFormat::ZOPFLI_FORMAT_GZIP,
633 reinterpret_cast<const unsigned char *
>(data.constData()),
640 QFile output(outputPath);
641 if (Q_UNLIKELY(!output.open(QIODevice::WriteOnly))) {
642 qCWarning(C_STATICCOMPRESSED)
643 <<
"Can not open output file to compress with zopfli:" << outputPath;
645 if (Q_UNLIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) < 0)) {
646 qCCritical(C_STATICCOMPRESSED).nospace()
647 <<
"Failed to write compressed zopfi file " << inputPath <<
": "
648 << output.errorString();
649 if (output.exists()) {
650 if (Q_UNLIKELY(!output.remove())) {
651 qCWarning(C_STATICCOMPRESSED)
652 <<
"Can not remove invalid compressed zopfli file:" << outputPath;
660 qCWarning(C_STATICCOMPRESSED)
661 <<
"Failed to compress file with zopfli, compressed data is empty:" << inputPath;
670#ifdef CUTELYST_STATICCOMPRESSED_WITH_BROTLI
671bool StaticCompressedPrivate::compressBrotli(
const QString &inputPath,
672 const QString &outputPath)
const
674 qCDebug(C_STATICCOMPRESSED) <<
"Compressing" << inputPath <<
"with brotli to" << outputPath;
676 QFile input(inputPath);
677 if (Q_UNLIKELY(!input.open(QIODevice::ReadOnly))) {
678 qCWarning(C_STATICCOMPRESSED)
679 <<
"Can not open input file to compress with brotli:" << inputPath;
683 const QByteArray data = input.readAll();
684 if (Q_UNLIKELY(data.isEmpty())) {
685 qCWarning(C_STATICCOMPRESSED)
686 <<
"Can not read input file or input file is empty:" << inputPath;
694 size_t outSize = BrotliEncoderMaxCompressedSize(
static_cast<size_t>(data.size()));
695 if (Q_LIKELY(outSize > 0)) {
696 const uint8_t *in = (
const uint8_t *) data.constData();
697 uint8_t *out = (uint8_t *) malloc(
sizeof(uint8_t) * (outSize + 1));
698 if (Q_LIKELY(out !=
nullptr)) {
699 BROTLI_BOOL status = BrotliEncoderCompress(brotliQualityLevel,
700 BROTLI_DEFAULT_WINDOW,
706 if (Q_LIKELY(status == BROTLI_TRUE)) {
707 QFile output(outputPath);
708 if (Q_LIKELY(output.open(QIODevice::WriteOnly))) {
709 if (Q_LIKELY(output.write(
reinterpret_cast<const char *
>(out), outSize) > -1)) {
712 qCWarning(C_STATICCOMPRESSED).nospace()
713 <<
"Failed to write brotli compressed data to output file "
714 << outputPath <<
": " << output.errorString();
715 if (output.exists()) {
716 if (Q_UNLIKELY(!output.remove())) {
717 qCWarning(C_STATICCOMPRESSED)
718 <<
"Can not remove invalid compressed brotli file:"
724 qCWarning(C_STATICCOMPRESSED)
725 <<
"Failed to open output file for brotli compression:" << outputPath;
728 qCWarning(C_STATICCOMPRESSED) <<
"Failed to compress" << inputPath <<
"with brotli";
732 qCWarning(C_STATICCOMPRESSED)
733 <<
"Can not allocate needed output buffer of size"
734 << (
sizeof(uint8_t) * (outSize + 1)) <<
"for brotli compression.";
737 qCWarning(C_STATICCOMPRESSED) <<
"Needed output buffer too large to compress input of size"
738 << data.size() <<
"with brotli";
745#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
Headers headers() const noexcept
QByteArray header(QByteArrayView key) const noexcept
void setContentType(const QByteArray &type)
void setStatus(quint16 status) noexcept
void setBody(QIODevice *body)
Headers & headers() noexcept
Deliver static files compressed on the fly or precompressed.
~StaticCompressed() override
void setIncludePaths(const QStringList &paths)
void setDirs(const QStringList &dirs)
bool setup(Application *app) override
The Cutelyst namespace holds all public Cutelyst API.