Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/lib/deskflow/ClipboardChunk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli
}

if (mark == ChunkType::DataStart) {
s_expectedSize = QString::fromStdString(data).toULong();
bool ok = false;
s_expectedSize = QString::fromStdString(data).toULong(&ok);
if (!ok) {
LOG_ERR("invalid clipboard size string");
return Error;
}
LOG_DEBUG("start receiving clipboard data");
dataCached.clear();
return Started;
Expand All @@ -86,7 +91,10 @@ ClipboardChunk::assemble(deskflow::IStream *stream, std::string &dataCached, Cli
if (id >= kClipboardEnd) {
return Error;
} else if (s_expectedSize != dataCached.size()) {
LOG_ERR("corrupted clipboard data, expected size=%d actual size=%d", s_expectedSize, dataCached.size());
LOG_ERR(
"corrupted clipboard data, expected size=%zu actual size=%zu", static_cast<size_t>(s_expectedSize),
dataCached.size()
);
return Error;
}
return Finished;
Expand Down
15 changes: 15 additions & 0 deletions src/lib/deskflow/IClipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,27 @@ void IClipboard::unmarshall(IClipboard *clipboard, const std::string_view &data,
assert(clipboard != nullptr);

const char *index = data.data();
const char *dataEnd = data.data() + data.size();

if (clipboard->open(time)) {
// clear existing data
clipboard->empty();

// read the number of formats
if (index + 4 > dataEnd) {
clipboard->close();
return;
}
const uint32_t numFormats = readUInt32(index);
index += 4;

// read each format
for (uint32_t i = 0; i < numFormats; ++i) {
// need at least 8 bytes for format id + size
if (index + 8 > dataEnd) {
break;
}

// get the format id
auto format = static_cast<IClipboard::Format>(readUInt32(index));
index += 4;
Expand All @@ -38,6 +48,11 @@ void IClipboard::unmarshall(IClipboard *clipboard, const std::string_view &data,
uint32_t size = readUInt32(index);
index += 4;

// validate size doesn't exceed remaining data
if (size > static_cast<size_t>(dataEnd - index)) {
break;
}

// save the data if it's a known format. if either the client
// or server supports more clipboard formats than the other
// then one of them will get a format >= TotalFormats here.
Expand Down
5 changes: 1 addition & 4 deletions src/lib/deskflow/StreamChunker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void StreamChunker::sendClipboard(
size_t sentLength = 0;
size_t chunkSize = g_chunkSize;

while (true) {
while (sentLength < size) {
// make sure we don't read too much from the mock data.
if (sentLength + chunkSize > size) {
chunkSize = size - sentLength;
Expand All @@ -39,9 +39,6 @@ void StreamChunker::sendClipboard(
events->addEvent(Event(EventTypes::ClipboardSending, eventTarget, dataChunk));

sentLength += chunkSize;
if (sentLength == size) {
break;
}
}

// send last message
Expand Down
9 changes: 6 additions & 3 deletions src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ MSWindowsClipboardAnyTextConverter::fromIClipboard(const std::string &data) cons

std::string MSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const
{
// get datator
// get data pointer
const char *src = (const char *)GlobalLock(data);
uint32_t srcSize = (uint32_t)GlobalSize(data);
if (src == nullptr || srcSize <= 1) {
if (src != nullptr) {
GlobalUnlock(data);
}
return std::string();
}

Expand Down Expand Up @@ -97,7 +100,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std:
uint32_t numNewlines = 0;
uint32_t n = (uint32_t)src.size();
for (const char *scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] == '\r' && scan[1] == '\n') {
if (scan[0] == '\r' && n > 1 && scan[1] == '\n') {
++numNewlines;
}
}
Expand All @@ -112,7 +115,7 @@ std::string MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(const std:
// copy string, converting newlines
n = (uint32_t)src.size();
for (const char *scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] != '\r' || scan[1] != '\n') {
if (scan[0] != '\r' || n <= 1 || scan[1] != '\n') {
dst += scan[0];
}
}
Expand Down
66 changes: 52 additions & 14 deletions src/lib/platform/MSWindowsClipboardBitmapConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,18 @@ MSWindowsClipboardBitmapConverter::fromIClipboard(const std::string &data) const

std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
{
// get datator
// get data pointer
LPVOID src = GlobalLock(data);
if (src == nullptr) {
return std::string();
}
uint32_t srcSize = (uint32_t)GlobalSize(data);

if (srcSize < sizeof(BITMAPINFOHEADER)) {
GlobalUnlock(data);
return std::string();
}

// check image type
const BITMAPINFO *bitmap = static_cast<const BITMAPINFO *>(src);
LOG(
Expand All @@ -66,12 +71,26 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
return image;
}

// validate dimensions
LONG w = bitmap->bmiHeader.biWidth;
LONG h = bitmap->bmiHeader.biHeight;
if (w <= 0 || h == 0) {
GlobalUnlock(data);
return std::string();
}
LONG absH = (h > 0) ? h : -h;

// check for integer overflow in pixel data size (4 bytes per pixel)
if (w > 32767 || absH > 32767 || static_cast<uint64_t>(w) * absH > 0x40000000ULL) {
LOG_WARN("bitmap too large: %dx%d", w, absH);
GlobalUnlock(data);
return std::string();
}

// create a destination DIB section
LOG_INFO("convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression);
void *raw;
BITMAPINFOHEADER info;
LONG w = bitmap->bmiHeader.biWidth;
LONG h = bitmap->bmiHeader.biHeight;
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = w;
info.biHeight = h;
Expand All @@ -84,33 +103,52 @@ std::string MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const
info.biClrUsed = 0;
info.biClrImportant = 0;
HDC dc = GetDC(nullptr);
if (dc == nullptr) {
GlobalUnlock(data);
return std::string();
}
HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO *)&info, DIB_RGB_COLORS, &raw, nullptr, 0);
if (dst == nullptr) {
ReleaseDC(nullptr, dc);
GlobalUnlock(data);
return std::string();
}

// find the start of the pixel data
const char *srcBits = (const char *)bitmap + bitmap->bmiHeader.biSize;
if (bitmap->bmiHeader.biBitCount >= 16) {
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS &&
(bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) {
srcBits += 3 * sizeof(DWORD);
// Only BITMAPINFOHEADER (40 bytes) has color masks/table after the header.
// BITMAPV4HEADER (108 bytes) and BITMAPV5HEADER (124 bytes) include
// masks and color space info within the header itself.
if (bitmap->bmiHeader.biSize == sizeof(BITMAPINFOHEADER)) {
if (bitmap->bmiHeader.biBitCount >= 16) {
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS &&
(bitmap->bmiHeader.biBitCount == 16 || bitmap->bmiHeader.biBitCount == 32)) {
srcBits += 3 * sizeof(DWORD);
}
} else if (bitmap->bmiHeader.biClrUsed != 0) {
srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD);
} else if (bitmap->bmiHeader.biBitCount > 0 && bitmap->bmiHeader.biBitCount <= 8) {
srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD);
}
} else if (bitmap->bmiHeader.biClrUsed != 0) {
srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD);
} else {
// http://msdn.microsoft.com/en-us/library/ke55d167(VS.80).aspx
srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD);
}

// copy source image to destination image
HDC dstDC = CreateCompatibleDC(dc);
if (dstDC == nullptr) {
DeleteObject(dst);
ReleaseDC(nullptr, dc);
GlobalUnlock(data);
return std::string();
}
HGDIOBJ oldBitmap = SelectObject(dstDC, dst);
SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, srcBits, bitmap, DIB_RGB_COLORS);
SetDIBitsToDevice(dstDC, 0, 0, w, absH, 0, 0, 0, absH, srcBits, bitmap, DIB_RGB_COLORS);
SelectObject(dstDC, oldBitmap);
DeleteDC(dstDC);
GdiFlush();

// extract data
std::string image((const char *)&info, info.biSize);
image.append((const char *)raw, 4 * w * h);
image.append((const char *)raw, 4LL * w * absH);

// clean up GDI
DeleteObject(dst);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/platform/MSWindowsClipboardHTMLConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ std::string MSWindowsClipboardHTMLConverter::doToIClipboard(const std::string &d
// convert args to integers
int32_t start = (int32_t)atoi(startArg.c_str());
int32_t end = (int32_t)atoi(endArg.c_str());
if (start <= 0 || end <= 0 || start >= end) {
if (start <= 0 || end <= 0 || start >= end || static_cast<size_t>(end) > data.size()) {
return std::string();
}

Expand Down
14 changes: 14 additions & 0 deletions src/lib/platform/OSXClipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ bool OSXClipboard::has(Format format) const
PasteboardItemID item;
PasteboardGetItemIdentifier(m_pboard, (CFIndex)1, &item);

// For bitmap format, only report available if the pasteboard actually
// contains native image data (TIFF or PNG). Without this check, macOS
// may claim com.microsoft.bmp is available via UTI auto-conversion
// for non-image pasteboard content (e.g. file references), but the
// actual conversion can hang and block the event loop, causing the
// client to disconnect due to keep-alive timeout.
if (format == IClipboard::Format::Bitmap) {
PasteboardFlavorFlags flags;
if (PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.tiff"), &flags) != noErr &&
PasteboardGetItemFlavorFlags(m_pboard, item, CFSTR("public.png"), &flags) != noErr) {
return false;
}
}

for (ConverterList::const_iterator index = m_converters.begin(); index != m_converters.end(); ++index) {
IOSXClipboardConverter *converter = *index;
if (converter->getFormat() == format) {
Expand Down
59 changes: 47 additions & 12 deletions src/lib/platform/OSXClipboardBMPConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ struct CBMPHeader
};

// BMP is little-endian
static inline uint16_t fromLEU16(const uint8_t *data)
{
return static_cast<uint16_t>(data[0]) | (static_cast<uint16_t>(data[1]) << 8);
}

static inline uint32_t fromLEU32(const uint8_t *data)
{
return static_cast<uint32_t>(data[0]) | (static_cast<uint32_t>(data[1]) << 8) |
Expand Down Expand Up @@ -62,21 +67,57 @@ CFStringRef OSXClipboardBMPConverter::getOSXFormat() const
std::string OSXClipboardBMPConverter::fromIClipboard(const std::string &bmp) const
{
LOG_DEBUG1("getting data from clipboard");
// create BMP image

if (bmp.size() < 4) {
return std::string();
}

// read the actual DIB header size from biSize
const uint8_t *rawDIB = reinterpret_cast<const uint8_t *>(bmp.data());
uint32_t biSize = fromLEU32(rawDIB);

// validate biSize is reasonable
if (biSize < 40 || biSize > 1024) {
return std::string();
}

// compute pixel data offset: file header + DIB header + color table
uint32_t pixelOffset = 14 + biSize;

if (bmp.size() >= biSize) {
uint16_t biBitCount = fromLEU16(rawDIB + 14);
uint32_t biCompression = fromLEU32(rawDIB + 16);
uint32_t biClrUsed = fromLEU32(rawDIB + 32);

// BITMAPINFOHEADER with BI_BITFIELDS has 3 DWORD color masks after header
if (biSize == 40 && biCompression == 3 /* BI_BITFIELDS */) {
pixelOffset += 3 * 4;
}

// add color table size
if (biBitCount <= 8) {
uint32_t colors = biClrUsed ? biClrUsed : (1u << biBitCount);
pixelOffset += colors * 4;
} else if (biClrUsed > 0) {
pixelOffset += biClrUsed * 4;
}
}

// create BMP file header
uint8_t header[14];
uint8_t *dst = header;
toLE(dst, 'B');
toLE(dst, 'M');
toLE(dst, static_cast<uint32_t>(14 + bmp.size()));
toLE(dst, static_cast<uint16_t>(0));
toLE(dst, static_cast<uint16_t>(0));
toLE(dst, static_cast<uint32_t>(14 + 40));
toLE(dst, pixelOffset);
return std::string(reinterpret_cast<const char *>(header), 14) + bmp;
}

std::string OSXClipboardBMPConverter::toIClipboard(const std::string &bmp) const
{
// make sure data is big enough for a BMP file
// make sure data is big enough for a BMP file header + minimal DIB header
if (bmp.size() <= 14 + 40) {
return std::string();
}
Expand All @@ -87,13 +128,7 @@ std::string OSXClipboardBMPConverter::toIClipboard(const std::string &bmp) const
return std::string();
}

// get offset to image data
uint32_t offset = fromLEU32(rawBMPHeader + 10);

// construct BMP
if (offset == 14 + 40) {
return bmp.substr(14);
} else {
return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset);
}
// strip the 14-byte BMP file header, keep the entire DIB
// (info header + optional color table + pixel data)
return bmp.substr(14);
}
6 changes: 3 additions & 3 deletions src/lib/platform/OSXClipboardHTMLConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ std::string OSXClipboardHTMLConverter::convertString(

CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, nullptr, 0, &buffSize);

char *buffer = new char[buffSize];

if (buffer == nullptr) {
if (buffSize <= 0) {
CFRelease(stringRef);
return std::string();
}

char *buffer = new char[buffSize];

CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, (uint8_t *)buffer, buffSize, nullptr);

std::string result(buffer, buffSize);
Expand Down
6 changes: 3 additions & 3 deletions src/lib/platform/OSXClipboardTextConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ std::string OSXClipboardTextConverter::convertString(

CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, nullptr, 0, &buffSize);

char *buffer = new char[buffSize];

if (buffer == nullptr) {
if (buffSize <= 0) {
CFRelease(stringRef);
return std::string();
}

char *buffer = new char[buffSize];

CFStringGetBytes(stringRef, entireString, toEncoding, 0, false, (uint8_t *)buffer, buffSize, nullptr);

std::string result(buffer, buffSize);
Expand Down
Loading
Loading