summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEirik Aavitsland <eirik.aavitsland@qt.io>2024-02-26 08:49:00 +0100
committerEirik Aavitsland <eirik.aavitsland@qt.io>2024-04-03 07:42:57 +0000
commit603fb3a47ac3919c4f260d8c29e5132a49bfee12 (patch)
tree5429548387c2c2a061103972551616f30f9cfa6b
parent643667dbbfc5f1f154091c0bbfc6b8d039d88ba3 (diff)
Improve svg file detection/canRead()
A number of times it has been reported as inpractical that the svg image handler has accepted any XML file, and any gzip compressed file, as a potential svg file. This commit fixes that by making the canRead() test stricter. It also cleans up the code by centralizing the tests for uncompressed and compressed files in one function. Fixes: QTBUG-119910 Task-number: QTBUG-12635 Task-number: QTBUG-16804 Task-number: QTBUG-63873 Change-Id: I3bd5437251750068d3d17d89531bbaae8db249cc Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r--src/plugins/imageformats/svg/CMakeLists.txt2
-rw-r--r--src/plugins/imageformats/svg/qsvgiohandler.cpp24
-rw-r--r--src/svg/qsvgtinydocument.cpp53
-rw-r--r--src/svg/qsvgtinydocument_p.h19
-rw-r--r--tests/auto/qsvgplugin/CMakeLists.txt4
-rw-r--r--tests/auto/qsvgplugin/simple_Utf32BE.svg.gzbin0 -> 185 bytes
-rw-r--r--tests/auto/qsvgplugin/simple_Utf8.svgzbin0 -> 141 bytes
-rw-r--r--tests/auto/qsvgplugin/tst_qsvgplugin.cpp2
8 files changed, 70 insertions, 34 deletions
diff --git a/src/plugins/imageformats/svg/CMakeLists.txt b/src/plugins/imageformats/svg/CMakeLists.txt
index 140e281..ec69a02 100644
--- a/src/plugins/imageformats/svg/CMakeLists.txt
+++ b/src/plugins/imageformats/svg/CMakeLists.txt
@@ -14,5 +14,5 @@ qt_internal_add_plugin(QSvgPlugin
LIBRARIES
Qt::Core
Qt::Gui
- Qt::Svg
+ Qt::SvgPrivate
)
diff --git a/src/plugins/imageformats/svg/qsvgiohandler.cpp b/src/plugins/imageformats/svg/qsvgiohandler.cpp
index 570c982..1212f80 100644
--- a/src/plugins/imageformats/svg/qsvgiohandler.cpp
+++ b/src/plugins/imageformats/svg/qsvgiohandler.cpp
@@ -6,6 +6,7 @@
#ifndef QT_NO_SVGRENDERER
#include "qsvgrenderer.h"
+#include "private/qsvgtinydocument_p.h"
#include "qimage.h"
#include "qpixmap.h"
#include "qpainter.h"
@@ -84,25 +85,6 @@ QSvgIOHandler::~QSvgIOHandler()
delete d;
}
-static bool isPossiblySvg(QIODevice *device, bool *isCompressed = nullptr)
-{
- constexpr int bufSize = 64;
- char buf[bufSize];
- const qint64 readLen = device->peek(buf, bufSize);
- if (readLen < 8)
- return false;
-# ifndef QT_NO_COMPRESS
- if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
- if (isCompressed)
- *isCompressed = true;
- return true;
- }
-# endif
- QTextStream str(QByteArray::fromRawData(buf, readLen));
- QByteArray ba = str.read(16).trimmed().toLatin1();
- return ba.startsWith("<?xml") || ba.startsWith("<svg") || ba.startsWith("<!--") || ba.startsWith("<!DOCTYPE svg");
-}
-
bool QSvgIOHandler::canRead() const
{
if (!device())
@@ -111,7 +93,7 @@ bool QSvgIOHandler::canRead() const
return true; // Will happen if we have been asked for the size
bool isCompressed = false;
- if (isPossiblySvg(device(), &isCompressed)) {
+ if (QSvgTinyDocument::isLikelySvg(device(), &isCompressed)) {
setFormat(isCompressed ? "svgz" : "svg");
return true;
}
@@ -237,7 +219,7 @@ bool QSvgIOHandler::supportsOption(ImageOption option) const
bool QSvgIOHandler::canRead(QIODevice *device)
{
- return isPossiblySvg(device);
+ return QSvgTinyDocument::isLikelySvg(device);
}
QT_END_NAMESPACE
diff --git a/src/svg/qsvgtinydocument.cpp b/src/svg/qsvgtinydocument.cpp
index 246df7b..94f21ca 100644
--- a/src/svg/qsvgtinydocument.cpp
+++ b/src/svg/qsvgtinydocument.cpp
@@ -21,6 +21,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
QSvgTinyDocument::QSvgTinyDocument(QtSvg::Options options)
: QSvgStructureNode(0)
, m_widthPercent(false)
@@ -37,6 +39,19 @@ QSvgTinyDocument::~QSvgTinyDocument()
{
}
+static bool hasSvgHeader(const QByteArray &buf)
+{
+ QTextStream s(buf); // Handle multi-byte encodings
+ QString h = s.readAll();
+ QStringView th = QStringView(h).trimmed();
+ bool matched = false;
+ if (th.startsWith("<svg"_L1) || th.startsWith("<!DOCTYPE svg"_L1))
+ matched = true;
+ else if (th.startsWith("<?xml"_L1) || th.startsWith("<!--"_L1))
+ matched = th.contains("<!DOCTYPE svg"_L1) || th.contains("<svg"_L1);
+ return matched;
+}
+
#ifndef QT_NO_COMPRESS
static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent = true);
# ifdef QT_BUILD_INTERNAL
@@ -124,8 +139,7 @@ static QByteArray qt_inflateSvgzDataFrom(QIODevice *device, bool doCheckContent)
if (doCheckContent) {
// Quick format check, equivalent to QSvgIOHandler::canRead()
- QByteArray buf = destination.left(16);
- if (!buf.contains("<?xml") && !buf.contains("<svg") && !buf.contains("<!--") && !buf.contains("<!DOCTYPE svg")) {
+ if (!hasSvgHeader(destination)) {
inflateEnd(&zlibStream);
qCWarning(lcSvgHandler, "Error while inflating gzip file: SVG format check failed");
return QByteArray();
@@ -521,4 +535,39 @@ void QSvgTinyDocument::setFramesPerSecond(int num)
m_fps = num;
}
+bool QSvgTinyDocument::isLikelySvg(QIODevice *device, bool *isCompressed)
+{
+ constexpr int bufSize = 4096;
+ char buf[bufSize];
+ char inflateBuf[bufSize];
+ bool useInflateBuf = false;
+ int readLen = device->peek(buf, bufSize);
+ if (readLen < 8)
+ return false;
+#ifndef QT_NO_COMPRESS
+ if (quint8(buf[0]) == 0x1f && quint8(buf[1]) == 0x8b) {
+ // Indicates gzip compressed content, i.e. svgz
+ z_stream zlibStream;
+ zlibStream.avail_in = readLen;
+ zlibStream.next_out = reinterpret_cast<Bytef *>(inflateBuf);
+ zlibStream.avail_out = bufSize;
+ zlibStream.next_in = reinterpret_cast<Bytef *>(buf);
+ zlibStream.zalloc = Z_NULL;
+ zlibStream.zfree = Z_NULL;
+ zlibStream.opaque = Z_NULL;
+ if (inflateInit2(&zlibStream, MAX_WBITS + 16) != Z_OK)
+ return false;
+ int zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
+ inflateEnd(&zlibStream);
+ if ((zlibResult != Z_OK && zlibResult != Z_STREAM_END) || zlibStream.total_out < 8)
+ return false;
+ readLen = zlibStream.total_out;
+ if (isCompressed)
+ *isCompressed = true;
+ useInflateBuf = true;
+ }
+#endif
+ return hasSvgHeader(QByteArray::fromRawData(useInflateBuf ? inflateBuf : buf, readLen));
+}
+
QT_END_NAMESPACE
diff --git a/src/svg/qsvgtinydocument_p.h b/src/svg/qsvgtinydocument_p.h
index 9501a95..a3f5489 100644
--- a/src/svg/qsvgtinydocument_p.h
+++ b/src/svg/qsvgtinydocument_p.h
@@ -39,23 +39,24 @@ public:
static QSvgTinyDocument *load(const QString &file, QtSvg::Options options = {});
static QSvgTinyDocument *load(const QByteArray &contents, QtSvg::Options options = {});
static QSvgTinyDocument *load(QXmlStreamReader *contents, QtSvg::Options options = {});
+ static bool isLikelySvg(QIODevice *device, bool *isCompressed = nullptr);
public:
QSvgTinyDocument(QtSvg::Options options);
~QSvgTinyDocument();
Type type() const override;
- QSize size() const;
+ inline QSize size() const;
void setWidth(int len, bool percent);
void setHeight(int len, bool percent);
- int width() const;
- int height() const;
- bool widthPercent() const;
- bool heightPercent() const;
+ inline int width() const;
+ inline int height() const;
+ inline bool widthPercent() const;
+ inline bool heightPercent() const;
- bool preserveAspectRatio() const;
+ inline bool preserveAspectRatio() const;
void setPreserveAspectRatio(bool on);
- QRectF viewBox() const;
+ inline QRectF viewBox() const;
void setViewBox(const QRectF &rect);
QtSvg::Options options() const;
@@ -79,10 +80,10 @@ public:
QSvgPaintStyleProperty *namedStyle(const QString &id) const;
void restartAnimation();
- int currentElapsed() const;
+ inline int currentElapsed() const;
bool animated() const;
void setAnimated(bool a);
- int animationDuration() const;
+ inline int animationDuration() const;
int currentFrame() const;
void setCurrentFrame(int);
void setFramesPerSecond(int num);
diff --git a/tests/auto/qsvgplugin/CMakeLists.txt b/tests/auto/qsvgplugin/CMakeLists.txt
index ffeef84..e9a516c 100644
--- a/tests/auto/qsvgplugin/CMakeLists.txt
+++ b/tests/auto/qsvgplugin/CMakeLists.txt
@@ -17,7 +17,7 @@ qt_internal_add_test(tst_qsvgplugin
LIBRARIES
Qt::Gui
Qt::GuiPrivate
- Qt::Svg
+ Qt::SvgPrivate
Qt::Widgets
)
@@ -39,10 +39,12 @@ set(resources_resource_files
"wide_size_viewbox.svg"
"wide_viewbox.svg"
"simple_Utf8.svg"
+ "simple_Utf8.svgz"
"simple_Utf16LE.svg"
"simple_Utf16BE.svg"
"simple_Utf32LE.svg"
"simple_Utf32BE.svg"
+ "simple_Utf32BE.svg.gz"
"invalid_xml.svg"
"xml_not_svg.svg"
"invalid_then_valid.svg"
diff --git a/tests/auto/qsvgplugin/simple_Utf32BE.svg.gz b/tests/auto/qsvgplugin/simple_Utf32BE.svg.gz
new file mode 100644
index 0000000..569a73f
--- /dev/null
+++ b/tests/auto/qsvgplugin/simple_Utf32BE.svg.gz
Binary files differ
diff --git a/tests/auto/qsvgplugin/simple_Utf8.svgz b/tests/auto/qsvgplugin/simple_Utf8.svgz
new file mode 100644
index 0000000..a51f38f
--- /dev/null
+++ b/tests/auto/qsvgplugin/simple_Utf8.svgz
Binary files differ
diff --git a/tests/auto/qsvgplugin/tst_qsvgplugin.cpp b/tests/auto/qsvgplugin/tst_qsvgplugin.cpp
index 762d373..0c9796e 100644
--- a/tests/auto/qsvgplugin/tst_qsvgplugin.cpp
+++ b/tests/auto/qsvgplugin/tst_qsvgplugin.cpp
@@ -138,10 +138,12 @@ void tst_QSvgPlugin::encodings_data()
QTest::addColumn<QString>("filename");
QTest::newRow("utf-8") << QFINDTESTDATA("simple_Utf8.svg");
+ QTest::newRow("utf-8_z") << QFINDTESTDATA("simple_Utf8.svgz");
QTest::newRow("utf-16LE") << QFINDTESTDATA("simple_Utf16LE.svg");
QTest::newRow("utf-16BE") << QFINDTESTDATA("simple_Utf16BE.svg");
QTest::newRow("utf-32LE") << QFINDTESTDATA("simple_Utf32LE.svg");
QTest::newRow("utf-32BE") << QFINDTESTDATA("simple_Utf32BE.svg");
+ QTest::newRow("utf-32BE_z") << QFINDTESTDATA("simple_Utf32BE.svg.gz");
}
void tst_QSvgPlugin::encodings()