From 0ff2655456097926a1720545830b1e34f072371f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 5 Feb 2016 10:26:56 +0100 Subject: [PATCH] Support extended clipboard transfers Implements support in both client and server for the extended clipboard format first seen in UltraVNC. Currently only implements text handling, but that is still an improvement as it extends the clipboard from ISO 8859-1 to full Unicode. --- common/rfb/CConnection.cxx | 91 +++++++++++++++++++++++++++-- common/rfb/CConnection.h | 10 ++++ common/rfb/CMsgHandler.cxx | 25 +++++++- common/rfb/CMsgHandler.h | 16 ++++- common/rfb/CMsgReader.cxx | 112 ++++++++++++++++++++++++++++++++++- common/rfb/CMsgReader.h | 3 +- common/rfb/CMsgWriter.cxx | 105 ++++++++++++++++++++++++++++++++- common/rfb/CMsgWriter.h | 10 +++- common/rfb/ClientParams.cxx | 23 +++++++- common/rfb/ClientParams.h | 7 ++- common/rfb/SConnection.cxx | 83 ++++++++++++++++++++++++-- common/rfb/SConnection.h | 8 +++ common/rfb/SMsgHandler.cxx | 25 +++++++- common/rfb/SMsgHandler.h | 15 ++++- common/rfb/SMsgReader.cxx | 112 +++++++++++++++++++++++++++++++++-- common/rfb/SMsgReader.h | 3 +- common/rfb/SMsgWriter.cxx | 113 +++++++++++++++++++++++++++++++++++- common/rfb/SMsgWriter.h | 12 +++- common/rfb/ServerParams.cxx | 20 ++++++- common/rfb/ServerParams.h | 7 ++- common/rfb/clipboardTypes.h | 41 +++++++++++++ common/rfb/encodings.h | 3 + vncviewer/vncviewer.man | 5 ++ 23 files changed, 812 insertions(+), 37 deletions(-) create mode 100644 common/rfb/clipboardTypes.h diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index 4e8ea4e5..bdde3253 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -53,7 +54,7 @@ CConnection::CConnection() firstUpdate(true), pendingUpdate(false), continuousUpdates(false), forceNonincremental(true), framebuffer(NULL), decoder(this), - serverClipboard(NULL) + serverClipboard(NULL), hasLocalClipboard(false) { } @@ -467,6 +468,8 @@ void CConnection::dataRect(const Rect& r, int encoding) void CConnection::serverCutText(const char* str) { + hasLocalClipboard = false; + strFree(serverClipboard); serverClipboard = NULL; @@ -475,6 +478,67 @@ void CConnection::serverCutText(const char* str) handleClipboardAnnounce(true); } +void CConnection::handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths) +{ + rdr::U32 sizes[] = { 0 }; + + CMsgHandler::handleClipboardCaps(flags, lengths); + + writer()->writeClipboardCaps(rfb::clipboardUTF8 | + rfb::clipboardRequest | + rfb::clipboardPeek | + rfb::clipboardNotify | + rfb::clipboardProvide, + sizes); +} + +void CConnection::handleClipboardRequest(rdr::U32 flags) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + if (!hasLocalClipboard) + return; + handleClipboardRequest(); +} + +void CConnection::handleClipboardPeek(rdr::U32 flags) +{ + if (!hasLocalClipboard) + return; + if (server.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(rfb::clipboardUTF8); +} + +void CConnection::handleClipboardNotify(rdr::U32 flags) +{ + strFree(serverClipboard); + serverClipboard = NULL; + + if (flags & rfb::clipboardUTF8) { + hasLocalClipboard = false; + handleClipboardAnnounce(true); + } else { + handleClipboardAnnounce(false); + } +} + +void CConnection::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + + strFree(serverClipboard); + serverClipboard = NULL; + + serverClipboard = convertLF((const char*)data[0], lengths[0]); + + // FIXME: Should probably verify that this data was actually requested + handleClipboardData(serverClipboard); +} + void CConnection::authSuccess() { } @@ -506,19 +570,35 @@ void CConnection::requestClipboard() handleClipboardData(serverClipboard); return; } + + if (server.clipboardFlags() & rfb::clipboardRequest) + writer()->writeClipboardRequest(rfb::clipboardUTF8); } void CConnection::announceClipboard(bool available) { - if (available) - handleClipboardRequest(); + hasLocalClipboard = available; + + if (server.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } } void CConnection::sendClipboardData(const char* data) { - CharArray latin1(utf8ToLatin1(data)); + if (server.clipboardFlags() & rfb::clipboardProvide) { + CharArray filtered(convertCRLF(data)); + size_t sizes[1] = { strlen(filtered.buf) + 1 }; + const rdr::U8* data[1] = { (const rdr::U8*)filtered.buf }; + writer()->writeClipboardProvide(rfb::clipboardUTF8, sizes, data); + } else { + CharArray latin1(utf8ToLatin1(data)); - writer()->writeClientCutText(latin1.buf); + writer()->writeClientCutText(latin1.buf); + } } void CConnection::refreshFramebuffer() @@ -656,6 +736,7 @@ void CConnection::updateEncodings() encodings.push_back(pseudoEncodingDesktopName); encodings.push_back(pseudoEncodingLastRect); + encodings.push_back(pseudoEncodingExtendedClipboard); encodings.push_back(pseudoEncodingContinuousUpdates); encodings.push_back(pseudoEncodingFence); encodings.push_back(pseudoEncodingQEMUKeyEvent); diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 4106a1e6..f01d5d36 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -111,6 +111,15 @@ namespace rfb { virtual void serverCutText(const char* str); + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + // Methods to be overridden in a derived class @@ -277,6 +286,7 @@ namespace rfb { DecodeManager decoder; char* serverClipboard; + bool hasLocalClipboard; }; } #endif diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index c009067a..9dab5d94 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -98,3 +98,26 @@ void CMsgHandler::setLEDState(unsigned int state) { server.setLEDState(state); } + +void CMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + server.setClipboardCaps(flags, lengths); +} + +void CMsgHandler::handleClipboardRequest(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardPeek(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardNotify(rdr::U32 flags) +{ +} + +void CMsgHandler::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ +} diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index 1581f792..84dd115c 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * Copyright (C) 2011 D. R. Commander. All Rights Reserved. * * This is free software; you can redistribute it and/or modify @@ -42,8 +42,9 @@ namespace rfb { // The following methods are called as corresponding messages are read. A // derived class should override these methods as desired. Note that for // the setDesktopSize(), setExtendedDesktopSize(), setPixelFormat(), - // setName() and serverInit() methods, a derived class should call on to - // CMsgHandler's methods to set the members of "server" appropriately. + // setName(), serverInit() and clipboardCaps methods, a derived class + // should call on to CMsgHandler's methods to set the members of "server" + // appropriately. virtual void setDesktopSize(int w, int h); virtual void setExtendedDesktopSize(unsigned reason, unsigned result, @@ -74,6 +75,15 @@ namespace rfb { virtual void setLEDState(unsigned int state); + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + ServerParams server; }; } diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 86288ad9..a9e12d70 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,8 +20,11 @@ #include #include -#include #include +#include + +#include +#include #include #include #include @@ -30,6 +33,8 @@ static rfb::LogWriter vlog("CMsgReader"); +static rfb::IntParameter maxCutText("MaxCutText", "Maximum permitted length of an incoming clipboard update", 256*1024); + using namespace rfb; CMsgReader::CMsgReader(CMsgHandler* handler_, rdr::InStream* is_) @@ -152,7 +157,15 @@ void CMsgReader::readServerCutText() { is->skip(3); rdr::U32 len = is->readU32(); - if (len > 256*1024) { + + if (len & 0x80000000) { + rdr::S32 slen = len; + slen = -slen; + readExtendedClipboard(slen); + return; + } + + if (len > (size_t)maxCutText) { is->skip(len); vlog.error("cut text too long (%d bytes) - ignoring",len); return; @@ -163,6 +176,99 @@ void CMsgReader::readServerCutText() handler->serverCutText(filtered.buf); } +void CMsgReader::readExtendedClipboard(rdr::S32 len) +{ + rdr::U32 flags; + rdr::U32 action; + + if (len < 4) + throw Exception("Invalid extended clipboard message"); + if (len > maxCutText) { + vlog.error("Extended clipboard message too long (%d bytes) - ignoring", len); + is->skip(len); + return; + } + + flags = is->readU32(); + action = flags & clipboardActionMask; + + if (action & clipboardCaps) { + int i; + size_t num; + rdr::U32 lengths[16]; + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + num++; + } + + if (len < (rdr::S32)(4 + 4*num)) + throw Exception("Invalid extended clipboard message"); + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + lengths[num++] = is->readU32(); + } + + handler->handleClipboardCaps(flags, lengths); + } else if (action == clipboardProvide) { + rdr::ZlibInStream zis; + + int i; + size_t num; + size_t lengths[16]; + rdr::U8* buffers[16]; + + zis.setUnderlying(is, len - 4); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + + lengths[num] = zis.readU32(); + if (lengths[num] > (size_t)maxCutText) { + vlog.error("Extended clipboard data too long (%d bytes) - ignoring", + (unsigned)lengths[num]); + zis.skip(lengths[num]); + flags &= ~(1 << i); + continue; + } + + buffers[num] = new rdr::U8[lengths[num]]; + zis.readBytes(buffers[num], lengths[num]); + num++; + } + + zis.removeUnderlying(); + + handler->handleClipboardProvide(flags, lengths, buffers); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + delete [] buffers[num++]; + } + } else { + switch (action) { + case clipboardRequest: + handler->handleClipboardRequest(flags); + break; + case clipboardPeek: + handler->handleClipboardPeek(flags); + break; + case clipboardNotify: + handler->handleClipboardNotify(flags); + break; + default: + throw Exception("Invalid extended clipboard action"); + } + } +} + void CMsgReader::readFence() { rdr::U32 flags; diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h index 03f3d8d2..050990a9 100644 --- a/common/rfb/CMsgReader.h +++ b/common/rfb/CMsgReader.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,6 +53,7 @@ namespace rfb { void readSetColourMapEntries(); void readBell(); void readServerCutText(); + void readExtendedClipboard(rdr::S32 len); void readFence(); void readEndOfContinuousUpdates(); diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index f1fa58dd..3180391b 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,10 +17,15 @@ * USA. */ #include + #include +#include +#include + #include #include #include +#include #include #include #include @@ -194,6 +199,104 @@ void CMsgWriter::writeClientCutText(const char* str) endMsg(); } +void CMsgWriter::writeClipboardCaps(rdr::U32 caps, + const rdr::U32* lengths) +{ + size_t i, count; + + if (!(server->clipboardFlags() & clipboardCaps)) + throw Exception("Server does not support clipboard \"caps\" action"); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + count++; + } + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-(4 + 4 * count)); + + os->writeU32(caps | clipboardCaps); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + os->writeU32(lengths[count++]); + } + + endMsg(); +} + +void CMsgWriter::writeClipboardRequest(rdr::U32 flags) +{ + if (!(server->clipboardFlags() & clipboardRequest)) + throw Exception("Server does not support clipboard \"request\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardRequest); + endMsg(); +} + +void CMsgWriter::writeClipboardPeek(rdr::U32 flags) +{ + if (!(server->clipboardFlags() & clipboardPeek)) + throw Exception("Server does not support clipboard \"peek\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardPeek); + endMsg(); +} + +void CMsgWriter::writeClipboardNotify(rdr::U32 flags) +{ + if (!(server->clipboardFlags() & clipboardNotify)) + throw Exception("Server does not support clipboard \"notify\" action"); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardNotify); + endMsg(); +} + +void CMsgWriter::writeClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + rdr::MemOutStream mos; + rdr::ZlibOutStream zos; + + int i, count; + + if (!(server->clipboardFlags() & clipboardProvide)) + throw Exception("Server does not support clipboard \"provide\" action"); + + zos.setUnderlying(&mos); + + count = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + zos.writeU32(lengths[count]); + zos.writeBytes(data[count], lengths[count]); + count++; + } + + zos.flush(); + + startMsg(msgTypeClientCutText); + os->pad(3); + os->writeS32(-(4 + mos.length())); + os->writeU32(flags | clipboardProvide); + os->writeBytes(mos.data(), mos.length()); + endMsg(); +} + void CMsgWriter::startMsg(int type) { os->writeU8(type); diff --git a/common/rfb/CMsgWriter.h b/common/rfb/CMsgWriter.h index d3ac19c9..7b839393 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,8 +55,16 @@ namespace rfb { void writeKeyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); void writePointerEvent(const Point& pos, int buttonMask); + void writeClientCutText(const char* str); + void writeClipboardCaps(rdr::U32 caps, const rdr::U32* lengths); + void writeClipboardRequest(rdr::U32 flags); + void writeClipboardPeek(rdr::U32 flags); + void writeClipboardNotify(rdr::U32 flags); + void writeClipboardProvide(rdr::U32 flags, const size_t* lengths, + const rdr::U8* const* data); + protected: void startMsg(int type); void endMsg(); diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx index e42d494b..987abe32 100644 --- a/common/rfb/ClientParams.cxx +++ b/common/rfb/ClientParams.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ #include #include #include +#include #include using namespace rfb; @@ -32,7 +33,13 @@ ClientParams::ClientParams() ledState_(ledUnknown) { setName(""); + cursor_ = new Cursor(0, 0, Point(), NULL); + + clipFlags = clipboardUTF8 | clipboardRTF | clipboardHTML | + clipboardRequest | clipboardNotify | clipboardProvide; + memset(clipSizes, 0, sizeof(clipSizes)); + clipSizes[0] = 20 * 1024 * 1024; } ClientParams::~ClientParams() @@ -136,6 +143,20 @@ void ClientParams::setLEDState(unsigned int state) ledState_ = state; } +void ClientParams::setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + int i, num; + + clipFlags = flags; + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + clipSizes[i] = lengths[num++]; + } +} + bool ClientParams::supportsLocalCursor() const { if (supportsEncoding(pseudoEncodingCursorWithAlpha)) diff --git a/common/rfb/ClientParams.h b/common/rfb/ClientParams.h index f7a7044b..aab3d644 100644 --- a/common/rfb/ClientParams.h +++ b/common/rfb/ClientParams.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -84,6 +84,9 @@ namespace rfb { unsigned int ledState() { return ledState_; } void setLEDState(unsigned int state); + rdr::U32 clipboardFlags() const { return clipFlags; } + void setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths); + // Wrappers to check for functionality rather than specific // encodings bool supportsLocalCursor() const; @@ -108,6 +111,8 @@ namespace rfb { Cursor* cursor_; std::set encodings_; unsigned int ledState_; + rdr::U32 clipFlags; + rdr::U32 clipSizes[16]; }; } #endif diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 46f0a850..4869199a 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,7 @@ SConnection::SConnection() is(0), os(0), reader_(0), writer_(0), ssecurity(0), state_(RFBSTATE_UNINITIALISED), preferredEncoding(encodingRaw), - clientClipboard(NULL) + clientClipboard(NULL), hasLocalClipboard(false) { defaultMajorVersion = 3; defaultMinorVersion = 8; @@ -299,6 +300,16 @@ void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings) } SMsgHandler::setEncodings(nEncodings, encodings); + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard)) { + rdr::U32 sizes[] = { 0 }; + writer()->writeClipboardCaps(rfb::clipboardUTF8 | + rfb::clipboardRequest | + rfb::clipboardPeek | + rfb::clipboardNotify | + rfb::clipboardProvide, + sizes); + } } void SConnection::clientCutText(const char* str) @@ -311,6 +322,49 @@ void SConnection::clientCutText(const char* str) handleClipboardAnnounce(true); } +void SConnection::handleClipboardRequest(rdr::U32 flags) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + if (!hasLocalClipboard) + return; + handleClipboardRequest(); +} + +void SConnection::handleClipboardPeek(rdr::U32 flags) +{ + if (!hasLocalClipboard) + return; + if (client.clipboardFlags() & rfb::clipboardNotify) + writer()->writeClipboardNotify(rfb::clipboardUTF8); +} + +void SConnection::handleClipboardNotify(rdr::U32 flags) +{ + strFree(clientClipboard); + clientClipboard = NULL; + + if (flags & rfb::clipboardUTF8) + handleClipboardAnnounce(true); + else + handleClipboardAnnounce(false); +} + +void SConnection::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + if (!(flags & rfb::clipboardUTF8)) + return; + + strFree(clientClipboard); + clientClipboard = NULL; + + clientClipboard = convertLF((const char*)data[0], lengths[0]); + + handleClipboardData(clientClipboard); +} + void SConnection::supportsQEMUKeyEvent() { writer()->writeQEMUKeyEvent(); @@ -440,19 +494,38 @@ void SConnection::requestClipboard() handleClipboardData(clientClipboard); return; } + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.clipboardFlags() & rfb::clipboardRequest)) + writer()->writeClipboardRequest(rfb::clipboardUTF8); } void SConnection::announceClipboard(bool available) { - if (available) - handleClipboardRequest(); + hasLocalClipboard = available; + + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.clipboardFlags() & rfb::clipboardNotify)) + writer()->writeClipboardNotify(available ? rfb::clipboardUTF8 : 0); + else { + if (available) + handleClipboardRequest(); + } } void SConnection::sendClipboardData(const char* data) { - CharArray latin1(utf8ToLatin1(data)); + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && + (client.clipboardFlags() & rfb::clipboardProvide)) { + CharArray filtered(convertCRLF(data)); + size_t sizes[1] = { strlen(filtered.buf) + 1 }; + const rdr::U8* data[1] = { (const rdr::U8*)filtered.buf }; + writer()->writeClipboardProvide(rfb::clipboardUTF8, sizes, data); + } else { + CharArray latin1(utf8ToLatin1(data)); - writer()->writeServerCutText(latin1.buf); + writer()->writeServerCutText(latin1.buf); + } } void SConnection::writeFakeColourMap(void) diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 6c80569d..db3ab08c 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -82,6 +82,13 @@ namespace rfb { virtual void clientCutText(const char* str); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + virtual void supportsQEMUKeyEvent(); @@ -247,6 +254,7 @@ namespace rfb { AccessRights accessRights; char* clientClipboard; + bool hasLocalClipboard; }; } #endif diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index f952ec2b..32b561e7 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,6 +64,29 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsQEMUKeyEvent(); } +void SMsgHandler::handleClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + client.setClipboardCaps(flags, lengths); +} + +void SMsgHandler::handleClipboardRequest(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardPeek(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardNotify(rdr::U32 flags) +{ +} + +void SMsgHandler::handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ +} + void SMsgHandler::supportsLocalCursor() { } diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 8548d911..b290f194 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,8 +40,8 @@ namespace rfb { // The following methods are called as corresponding messages are read. A // derived class should override these methods as desired. Note that for - // the setPixelFormat(), and setEncodings() methods, a derived class must - // call on to SMsgHandler's methods. + // the setPixelFormat(), setEncodings() and clipboardCaps() methods, a + // derived class must call on to SMsgHandler's methods. virtual void clientInit(bool shared); @@ -54,6 +54,15 @@ namespace rfb { virtual void enableContinuousUpdates(bool enable, int x, int y, int w, int h) = 0; + virtual void handleClipboardCaps(rdr::U32 flags, + const rdr::U32* lengths); + virtual void handleClipboardRequest(rdr::U32 flags); + virtual void handleClipboardPeek(rdr::U32 flags); + virtual void handleClipboardNotify(rdr::U32 flags); + virtual void handleClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data); + // InputHandler interface // The InputHandler methods will be called for the corresponding messages. diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 5efbfe2b..ab42e59a 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,13 @@ * USA. */ #include + #include +#include + #include #include +#include #include #include #include @@ -203,11 +207,16 @@ void SMsgReader::readPointerEvent() void SMsgReader::readClientCutText() { is->skip(3); - int len = is->readU32(); - if (len < 0) { - throw Exception("Cut text too long."); + rdr::U32 len = is->readU32(); + + if (len & 0x80000000) { + rdr::S32 slen = len; + slen = -slen; + readExtendedClipboard(slen); + return; } - if (len > maxCutText) { + + if (len > (size_t)maxCutText) { is->skip(len); vlog.error("Cut text too long (%d bytes) - ignoring", len); return; @@ -218,6 +227,99 @@ void SMsgReader::readClientCutText() handler->clientCutText(filtered.buf); } +void SMsgReader::readExtendedClipboard(rdr::S32 len) +{ + rdr::U32 flags; + rdr::U32 action; + + if (len < 4) + throw Exception("Invalid extended clipboard message"); + if (len > maxCutText) { + vlog.error("Extended clipboard message too long (%d bytes) - ignoring", len); + is->skip(len); + return; + } + + flags = is->readU32(); + action = flags & clipboardActionMask; + + if (action & clipboardCaps) { + int i; + size_t num; + rdr::U32 lengths[16]; + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + num++; + } + + if (len < (rdr::S32)(4 + 4*num)) + throw Exception("Invalid extended clipboard message"); + + num = 0; + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) + lengths[num++] = is->readU32(); + } + + handler->handleClipboardCaps(flags, lengths); + } else if (action == clipboardProvide) { + rdr::ZlibInStream zis; + + int i; + size_t num; + size_t lengths[16]; + rdr::U8* buffers[16]; + + zis.setUnderlying(is, len - 4); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + + lengths[num] = zis.readU32(); + if (lengths[num] > (size_t)maxCutText) { + vlog.error("Extended clipboard data too long (%d bytes) - ignoring", + (unsigned)lengths[num]); + zis.skip(lengths[num]); + flags &= ~(1 << i); + continue; + } + + buffers[num] = new rdr::U8[lengths[num]]; + zis.readBytes(buffers[num], lengths[num]); + num++; + } + + zis.removeUnderlying(); + + handler->handleClipboardProvide(flags, lengths, buffers); + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & 1 << i)) + continue; + delete [] buffers[num++]; + } + } else { + switch (action) { + case clipboardRequest: + handler->handleClipboardRequest(flags); + break; + case clipboardPeek: + handler->handleClipboardPeek(flags); + break; + case clipboardNotify: + handler->handleClipboardNotify(flags); + break; + default: + throw Exception("Invalid extended clipboard action"); + } + } +} + void SMsgReader::readQEMUMessage() { int subType = is->readU8(); diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index 146b29f8..4991fd38 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,6 +54,7 @@ namespace rfb { void readKeyEvent(); void readPointerEvent(); void readClientCutText(); + void readExtendedClipboard(rdr::S32 len); void readQEMUMessage(); void readQEMUKeyEvent(); diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 3d5a64ca..becf6e70 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2009-2017 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,9 +18,14 @@ * USA. */ #include + #include +#include +#include + #include #include +#include #include #include #include @@ -93,6 +98,112 @@ void SMsgWriter::writeServerCutText(const char* str) endMsg(); } +void SMsgWriter::writeClipboardCaps(rdr::U32 caps, + const rdr::U32* lengths) +{ + size_t i, count; + + if (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + count++; + } + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-(4 + 4 * count)); + + os->writeU32(caps | clipboardCaps); + + count = 0; + for (i = 0;i < 16;i++) { + if (caps & (1 << i)) + os->writeU32(lengths[count++]); + } + + endMsg(); +} + +void SMsgWriter::writeClipboardRequest(rdr::U32 flags) +{ + if (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->clipboardFlags() & clipboardRequest)) + throw Exception("Client does not support clipboard \"request\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardRequest); + endMsg(); +} + +void SMsgWriter::writeClipboardPeek(rdr::U32 flags) +{ + if (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->clipboardFlags() & clipboardPeek)) + throw Exception("Client does not support clipboard \"peek\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardPeek); + endMsg(); +} + +void SMsgWriter::writeClipboardNotify(rdr::U32 flags) +{ + if (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->clipboardFlags() & clipboardNotify)) + throw Exception("Client does not support clipboard \"notify\" action"); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-4); + os->writeU32(flags | clipboardNotify); + endMsg(); +} + +void SMsgWriter::writeClipboardProvide(rdr::U32 flags, + const size_t* lengths, + const rdr::U8* const* data) +{ + rdr::MemOutStream mos; + rdr::ZlibOutStream zos; + + int i, count; + + if (!client->supportsEncoding(pseudoEncodingExtendedClipboard)) + throw Exception("Client does not support extended clipboard"); + if (!(client->clipboardFlags() & clipboardProvide)) + throw Exception("Client does not support clipboard \"provide\" action"); + + zos.setUnderlying(&mos); + + count = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + zos.writeU32(lengths[count]); + zos.writeBytes(data[count], lengths[count]); + count++; + } + + zos.flush(); + + startMsg(msgTypeServerCutText); + os->pad(3); + os->writeS32(-(4 + mos.length())); + os->writeU32(flags | clipboardProvide); + os->writeBytes(mos.data(), mos.length()); + endMsg(); +} + void SMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) { if (!client->supportsEncoding(pseudoEncodingFence)) diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 8cf2ae77..2cea44d1 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2014 Pierre Ossman for Cendio AB + * Copyright 2009-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,10 +54,18 @@ namespace rfb { const rdr::U16 green[], const rdr::U16 blue[]); - // writeBell() and writeServerCutText() do the obvious thing. + // writeBell() does the obvious thing. void writeBell(); + void writeServerCutText(const char* str); + void writeClipboardCaps(rdr::U32 caps, const rdr::U32* lengths); + void writeClipboardRequest(rdr::U32 flags); + void writeClipboardPeek(rdr::U32 flags); + void writeClipboardNotify(rdr::U32 flags); + void writeClipboardProvide(rdr::U32 flags, const size_t* lengths, + const rdr::U8* const* data); + // writeFence() sends a new fence request or response to the client. void writeFence(rdr::U32 flags, unsigned len, const char data[]); diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index bfeb80d6..a2e3aa83 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,11 @@ ServerParams::ServerParams() ledState_(ledUnknown) { setName(""); + cursor_ = new Cursor(0, 0, Point(), NULL); + + clipFlags = 0; + memset(clipSizes, 0, sizeof(clipSizes)); } ServerParams::~ServerParams() @@ -82,3 +86,17 @@ void ServerParams::setLEDState(unsigned int state) { ledState_ = state; } + +void ServerParams::setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths) +{ + int i, num; + + clipFlags = flags; + + num = 0; + for (i = 0;i < 16;i++) { + if (!(flags & (1 << i))) + continue; + clipSizes[i] = lengths[num++]; + } +} diff --git a/common/rfb/ServerParams.h b/common/rfb/ServerParams.h index 7a58ea37..c84f6252 100644 --- a/common/rfb/ServerParams.h +++ b/common/rfb/ServerParams.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2014-2018 Pierre Ossman for Cendio AB + * Copyright 2014-2019 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -69,6 +69,9 @@ namespace rfb { unsigned int ledState() { return ledState_; } void setLEDState(unsigned int state); + rdr::U32 clipboardFlags() const { return clipFlags; } + void setClipboardCaps(rdr::U32 flags, const rdr::U32* lengths); + bool supportsQEMUKeyEvent; bool supportsSetDesktopSize; bool supportsFence; @@ -84,6 +87,8 @@ namespace rfb { char* name_; Cursor* cursor_; unsigned int ledState_; + rdr::U32 clipFlags; + rdr::U32 clipSizes[16]; }; } #endif diff --git a/common/rfb/clipboardTypes.h b/common/rfb/clipboardTypes.h new file mode 100644 index 00000000..bd3fa03d --- /dev/null +++ b/common/rfb/clipboardTypes.h @@ -0,0 +1,41 @@ +/* Copyright 2019 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef __RFB_CLIPBOARDTYPES_H__ +#define __RFB_CLIPBOARDTYPES_H__ + +namespace rfb { + + // Formats + const unsigned int clipboardUTF8 = 1 << 0; + const unsigned int clipboardRTF = 1 << 1; + const unsigned int clipboardHTML = 1 << 2; + const unsigned int clipboardDIB = 1 << 3; + const unsigned int clipboardFiles = 1 << 4; + + const unsigned int clipboardFormatMask = 0x0000ffff; + + // Actions + const unsigned int clipboardCaps = 1 << 24; + const unsigned int clipboardRequest = 1 << 25; + const unsigned int clipboardPeek = 1 << 26; + const unsigned int clipboardNotify = 1 << 27; + const unsigned int clipboardProvide = 1 << 28; + + const unsigned int clipboardActionMask = 0xff000000; +} +#endif diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index acb86ecc..cf0c8572 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -63,6 +63,9 @@ namespace rfb { const int pseudoEncodingVMwareCursor = 0x574d5664; const int pseudoEncodingVMwareLEDState = 0x574d5668; + // UltraVNC-specific + const int pseudoEncodingExtendedClipboard = 0xC0A1E5CE; + int encodingNum(const char* name); const char* encodingName(int num); } diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index ebfe7725..f93e096a 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -182,6 +182,11 @@ Set the primary selection as well as the clipboard selection. Default is on. . .TP +.B \-MaxCutText \fIbytes\fP +The maximum size of a clipboard update that will be accepted from a server. +Default is \fB262144\fP. +. +.TP .B \-SendClipboard Send clipboard changes to the server. Default is on. . -- 2.39.5