diff options
author | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2024-02-26 08:49:00 +0100 |
---|---|---|
committer | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2024-04-03 07:42:57 +0000 |
commit | 603fb3a47ac3919c4f260d8c29e5132a49bfee12 (patch) | |
tree | 5429548387c2c2a061103972551616f30f9cfa6b | |
parent | 643667dbbfc5f1f154091c0bbfc6b8d039d88ba3 (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.txt | 2 | ||||
-rw-r--r-- | src/plugins/imageformats/svg/qsvgiohandler.cpp | 24 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument.cpp | 53 | ||||
-rw-r--r-- | src/svg/qsvgtinydocument_p.h | 19 | ||||
-rw-r--r-- | tests/auto/qsvgplugin/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/auto/qsvgplugin/simple_Utf32BE.svg.gz | bin | 0 -> 185 bytes | |||
-rw-r--r-- | tests/auto/qsvgplugin/simple_Utf8.svgz | bin | 0 -> 141 bytes | |||
-rw-r--r-- | tests/auto/qsvgplugin/tst_qsvgplugin.cpp | 2 |
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 Binary files differnew file mode 100644 index 0000000..569a73f --- /dev/null +++ b/tests/auto/qsvgplugin/simple_Utf32BE.svg.gz diff --git a/tests/auto/qsvgplugin/simple_Utf8.svgz b/tests/auto/qsvgplugin/simple_Utf8.svgz Binary files differnew file mode 100644 index 0000000..a51f38f --- /dev/null +++ b/tests/auto/qsvgplugin/simple_Utf8.svgz 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() |