fix(win): normalise malformed macOS clipboard DIB
This commit is contained in:
parent
9a3a299c8d
commit
e856cce65b
3 changed files with 113 additions and 2 deletions
|
|
@ -10,6 +10,47 @@
|
|||
|
||||
#include "base/Log.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
bool normaliseMalformedMacDib(const std::string &data, std::string &normalisedData)
|
||||
{
|
||||
if (data.size() < sizeof(BITMAPINFOHEADER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto *header = reinterpret_cast<const BITMAPINFOHEADER *>(data.data());
|
||||
if (header->biWidth <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto width = static_cast<size_t>(header->biWidth);
|
||||
const auto height = static_cast<size_t>(std::abs(static_cast<int64_t>(header->biHeight)));
|
||||
if (height == 0 || width > (std::numeric_limits<size_t>::max() - sizeof(BITMAPINFOHEADER)) / 4 / height) {
|
||||
return false;
|
||||
}
|
||||
const auto expectedSize = sizeof(BITMAPINFOHEADER) + width * height * 4;
|
||||
|
||||
// macOS can describe an INFOHEADER-sized 32-bit pixel payload as a V5 DIB.
|
||||
// Windows then interprets the first pixels as V5 colour masks. The pixel
|
||||
// bytes are ordinary BGRA, so publish a canonical BI_RGB DIB instead.
|
||||
if (header->biSize <= sizeof(BITMAPINFOHEADER) || header->biPlanes != 1 || header->biBitCount != 32 ||
|
||||
header->biCompression != BI_BITFIELDS || expectedSize != data.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
normalisedData = data.substr(0, sizeof(BITMAPINFOHEADER));
|
||||
qToLittleEndian<quint32>(sizeof(BITMAPINFOHEADER), reinterpret_cast<quint8 *>(&normalisedData[0]));
|
||||
qToLittleEndian<quint32>(BI_RGB, reinterpret_cast<quint8 *>(&normalisedData[0]) + 16);
|
||||
normalisedData += data.substr(sizeof(BITMAPINFOHEADER));
|
||||
LOG_INFO("normalised malformed macOS clipboard image to BI_RGB");
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// MSWindowsClipboardBitmapConverter
|
||||
//
|
||||
|
|
@ -27,13 +68,19 @@ UINT MSWindowsClipboardBitmapConverter::getWin32Format() const
|
|||
HANDLE
|
||||
MSWindowsClipboardBitmapConverter::fromIClipboard(const std::string &data) const
|
||||
{
|
||||
std::string normalisedData;
|
||||
const auto *clipboardData = &data;
|
||||
if (normaliseMalformedMacDib(data, normalisedData)) {
|
||||
clipboardData = &normalisedData;
|
||||
}
|
||||
|
||||
// copy to memory handle
|
||||
HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, data.size());
|
||||
HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, clipboardData->size());
|
||||
if (gData != nullptr) {
|
||||
// get a pointer to the allocated memory
|
||||
char *dst = (char *)GlobalLock(gData);
|
||||
if (dst != nullptr) {
|
||||
memcpy(dst, data.data(), data.size());
|
||||
memcpy(dst, clipboardData->data(), clipboardData->size());
|
||||
GlobalUnlock(gData);
|
||||
} else {
|
||||
GlobalFree(gData);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
#include "MSWindowsClipboardTests.h"
|
||||
|
||||
#include "platform/MSWindowsClipboard.h"
|
||||
#include "platform/MSWindowsClipboardBitmapConverter.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
void MSWindowsClipboardTests::initTestCase()
|
||||
{
|
||||
|
|
@ -136,4 +139,63 @@ void MSWindowsClipboardTests::isOwnedByDeskflow()
|
|||
QVERIFY(clipboard.isOwnedByDeskflow());
|
||||
}
|
||||
|
||||
void MSWindowsClipboardTests::normalisesMalformedMacBitmap()
|
||||
{
|
||||
// A 1x1 top-down macOS DIB that incorrectly declares a V5 header.
|
||||
constexpr qsizetype headerSize = sizeof(BITMAPINFOHEADER);
|
||||
std::string dib(headerSize + 4, '\0');
|
||||
auto *raw = reinterpret_cast<quint8 *>(&dib[0]);
|
||||
qToLittleEndian<quint32>(sizeof(BITMAPV5HEADER), raw); // claims V5 but only has an INFOHEADER
|
||||
qToLittleEndian<quint32>(1, raw + 4);
|
||||
qToLittleEndian<quint32>(-1, raw + 8);
|
||||
qToLittleEndian<quint16>(1, raw + 12);
|
||||
qToLittleEndian<quint16>(32, raw + 14);
|
||||
qToLittleEndian<quint32>(BI_BITFIELDS, raw + 16);
|
||||
raw[headerSize + 3] = 0xff;
|
||||
|
||||
MSWindowsClipboardBitmapConverter converter;
|
||||
const auto handle = converter.fromIClipboard(dib);
|
||||
QVERIFY(handle != nullptr);
|
||||
QCOMPARE(GlobalSize(handle), SIZE_T(headerSize + 4));
|
||||
const auto *result = static_cast<const quint8 *>(GlobalLock(handle));
|
||||
QVERIFY(result != nullptr);
|
||||
QCOMPARE(qFromLittleEndian<quint32>(result), quint32(headerSize));
|
||||
QCOMPARE(qFromLittleEndian<quint32>(result + 16), quint32(BI_RGB));
|
||||
QCOMPARE(result[headerSize + 3], quint8(0xff));
|
||||
GlobalUnlock(handle);
|
||||
GlobalFree(handle);
|
||||
}
|
||||
|
||||
void MSWindowsClipboardTests::preservesHealthyMacV5Bitmap()
|
||||
{
|
||||
// A complete 1x1 top-down macOS V5 DIB with BGRA colour masks and one pixel.
|
||||
constexpr qsizetype headerSize = sizeof(BITMAPV5HEADER);
|
||||
std::string dib(headerSize + 4, '\0');
|
||||
auto *raw = reinterpret_cast<quint8 *>(&dib[0]);
|
||||
qToLittleEndian<quint32>(headerSize, raw);
|
||||
qToLittleEndian<quint32>(1, raw + 4);
|
||||
qToLittleEndian<quint32>(-1, raw + 8);
|
||||
qToLittleEndian<quint16>(1, raw + 12);
|
||||
qToLittleEndian<quint16>(32, raw + 14);
|
||||
qToLittleEndian<quint32>(BI_BITFIELDS, raw + 16);
|
||||
qToLittleEndian<quint32>(0x00ff0000, raw + 40);
|
||||
qToLittleEndian<quint32>(0x0000ff00, raw + 44);
|
||||
qToLittleEndian<quint32>(0x000000ff, raw + 48);
|
||||
qToLittleEndian<quint32>(0xff000000, raw + 52);
|
||||
raw[headerSize] = 0x12;
|
||||
raw[headerSize + 1] = 0x34;
|
||||
raw[headerSize + 2] = 0x56;
|
||||
raw[headerSize + 3] = 0x78;
|
||||
|
||||
MSWindowsClipboardBitmapConverter converter;
|
||||
const auto handle = converter.fromIClipboard(dib);
|
||||
QVERIFY(handle != nullptr);
|
||||
QCOMPARE(GlobalSize(handle), SIZE_T(dib.size()));
|
||||
const auto *result = static_cast<const char *>(GlobalLock(handle));
|
||||
QVERIFY(result != nullptr);
|
||||
QCOMPARE(std::string(result, GlobalSize(handle)), dib);
|
||||
GlobalUnlock(handle);
|
||||
GlobalFree(handle);
|
||||
}
|
||||
|
||||
QTEST_MAIN(MSWindowsClipboardTests)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ private Q_SLOTS:
|
|||
void has_withNoFormatAdded();
|
||||
void getNonEmptyText();
|
||||
void isOwnedByDeskflow();
|
||||
void normalisesMalformedMacBitmap();
|
||||
void preservesHealthyMacV5Bitmap();
|
||||
|
||||
private:
|
||||
Log m_log;
|
||||
|
|
|
|||
Loading…
Reference in a new issue