Rabbit Remote Control 0.0.30
Loading...
Searching...
No Matches
ClipboardMimeData.cpp
1// Author: Kang Lin <kl222@126.com>
2
3// see: https://learn.microsoft.com/en-us/windows/win32/shell/clipboard
4
5#include "ClipboardMimeData.h"
6#include "ClipboardFreeRDP.h"
7#include <QLoggingCategory>
8#include <QEventLoop>
9#include <QRegularExpression>
10#include <QImage>
11#include <QUrl>
12#include <QDir>
13#include <QFileInfo>
14#include <QDateTime>
15
16#if !(defined (Q_OS_WINDOWS) || defined(Q_OS_WIN) || defined(Q_OS_WIN32) || defined(Q_OS_WINRT))
17 //
18 // format of CF_FILEGROUPDESCRIPTOR
19 //
20 typedef struct _FILEGROUPDESCRIPTORW { // fgd
21 UINT cItems;
22 FILEDESCRIPTORW fgd[1];
24
25#endif
26
27QAtomicInteger<qint32> CClipboardMimeData::m_nId(1);
28static int g_UINT32 = qRegisterMetaType<UINT32>("UINT32");
29
30static Q_LOGGING_CATEGORY(log, "FreeRDP.Clipboard.MimeData")
31
32CClipboardMimeData::CClipboardMimeData(CliprdrClientContext *pContext)
33 : QMimeData(),
34 m_pContext(pContext),
35 m_pClipboard(nullptr),
36 m_bExit(false)
37{
38 m_Id = m_nId++;
39 while(0 == m_Id)
40 m_Id = m_nId++;
41 qDebug(log) << "CClipboardMimeData::CClipboardMimeData:" << GetId();
42
43 CClipboardFreeRDP* pThis = (CClipboardFreeRDP*)pContext->custom;
44 m_pClipboard = pThis->m_pClipboard;
45 bool check = false;
46 check = connect(this, SIGNAL(sigRequestFileFromServer(const QString&,
47 const QString&,
48 const void*,
49 const UINT32)),
50 this, SLOT(slotRequestFileFromServer(const QString&,
51 const QString&,
52 const void*,
53 const UINT32)),
54 Qt::DirectConnection);
55 Q_ASSERT(check);
56}
57
58CClipboardMimeData::~CClipboardMimeData()
59{
60 qDebug(log) << "CClipboardMimeData::~CClipboardMimeData():" << GetId();
61 m_bExit = true;
62 emit sigContinue();
63 qDebug(log) << "CClipboardMimeData::~CClipboardMimeData() end:" << GetId();
64}
65
66const qint32 CClipboardMimeData::GetId() const
67{
68 return m_Id;
69}
70
71int CClipboardMimeData::SetFormat(const CLIPRDR_FORMAT_LIST *pList)
72{
73 if(!m_pClipboard)
74 {
75 Q_ASSERT(FALSE);
76 return -1;
77 }
78 m_Formats.clear();
79 for (UINT32 i = 0; i < pList->numFormats; i++)
80 {
81 CLIPRDR_FORMAT* pFormat = &pList->formats[i];
82 /*
83 qDebug(log) << "Format Id:" << pFormat->formatId
84 << "name:" << pFormat->formatName;//*/
85 AddFormat(pFormat->formatId, pFormat->formatName);
86 }
87
88 if(m_Formats.isEmpty())
89 return 0;
90
91 QString szFormats;
92 for(auto it = m_Formats.begin(); it != m_Formats.end(); it++)
93 {
94 //*
95 szFormats += QString::number(it->id) + "[";
96 szFormats += it->name;
97 szFormats += "]; "; //*/
98
99 m_indexId.insert(it->id, *it);
100 if(it->name.isEmpty())
101 {
102 switch (it->id) {
103 case CF_TEXT:
104 case CF_OEMTEXT:
105 case CF_UNICODETEXT:
106 case CF_LOCALE:
107 {
108 m_indexString.insert("text/plain", *it);
109 break;
110 }
111 case CF_DIB:
112 //case CF_BITMAP:
113 case CF_DIBV5:
114 {
115 m_indexString.insert("image/bmp", *it);
116 break;
117 }
118 case CF_HDROP:
119 {
120 m_indexString.insert("text/uri-list", *it);
121 break;
122 }
123 default:
124 {
125 const char* name = ClipboardGetFormatName(m_pClipboard, it->id);
126 if(name)
127 m_indexString.insert(name, *it);
128 }
129 }
130 } else {
131 m_indexString.insert(it->name, *it);
132 if("FileGroupDescriptorW" == it->name) {
133#ifdef Q_OS_WINDOWS
134 m_indexString.insert("text/uri-list", *it);
135#else
136 m_indexString.insert("x-special/gnome-copied-files", *it);
137#endif
138 } else if("FileGroupDescriptor" == it->name) {
139#ifdef Q_OS_WINDOWS
140 m_indexString.insert("text/uri-list", *it);
141#else
142 m_indexString.insert("x-special/gnome-copied-files", *it);
143#endif
144 } else if("UniformResourceLocatorW" == it->name) {
145 m_indexString.insert("text/uri-list", *it);
146 } else if("UniformResourceLocator" == it->name) {
147 m_indexString.insert("text/uri-list", *it);
148 } else if("x-special/gnome-copied-files" == it->name) {
149 m_indexString.insert("text/uri-list", *it);
150 } else if("text/html" != it->name && isHtml(it->name, false)) {
151 m_indexString.insert("text/html", *it);
152 } else if("text/plain" != it->name && isText(it->name, false)) {
153 m_indexString.insert("text/plain", *it);
154 } else if("image/bmp" != it->name && isImage(it->name)) {
155 m_indexString.insert("image/bmp", *it);
156 }
157 }
158 }
159
160 m_lstFormats.clear();
161 for(auto it = m_indexString.begin(); m_indexString.end() != it; it++)
162 {
163 if(!m_lstFormats.contains(it.key()))
164 m_lstFormats << (it.key());
165 }
166 if(m_lstFormats.contains("image/bmp")
167 && !m_lstFormats.contains("application/x-qt-image"))
168 {
169 m_lstFormats << ("application/x-qt-image");
170 }
171
172 // Only used by linux or unix
173 if(m_lstFormats.contains("text/uri-list")
174 && !m_lstFormats.contains("x-special/gnome-copied-files"))
175 {
176 m_lstFormats.push_front("x-special/gnome-copied-files");
177 m_lstFormats.removeOne("text/uri-list");
178 m_lstFormats.push_front("text/uri-list");
179 }
180
181 // Only used by windows
182 if(m_lstFormats.contains("text/uri-list")
183 && !m_lstFormats.contains("FileGroupDescriptorW"))
184 {
185 m_lstFormats.push_front("FileGroupDescriptorW");
186 m_lstFormats.removeOne("text/uri-list");
187 m_lstFormats.push_front("text/uri-list");
188 }
189
190 // Used to identify oneself
191 m_lstFormats << MIME_TYPE_RABBITREMOTECONTROL_PLUGINS_FREERDP;
192
193 qDebug(log) << "CClipboardMimeData::SetFormat: input formats:" << szFormats
194 << "Formats:" << m_lstFormats;
195
196 return 0;
197}
198
199int CClipboardMimeData::AddFormat(UINT32 id, const char *name)
200{
201 int nRet = 0;
202
203 foreach(auto it, m_Formats)
204 {
205 if(it.id == id)
206 {
207 qWarning(log) << "Repeat format id:" << id;
208 return -1;
209 }
210
211 if(name)
212 {
213 if(name == it.name)
214 {
215 qWarning(log) << "Repeat format name:" << name;
216 return -2;
217 }
218 }
219 }
220
221 _FORMAT f = {id, name, id};
222 if(name)
223 {
224 f.localId = ClipboardRegisterFormat(m_pClipboard, name);
225 }
226
227 m_Formats.push_back(f);
228
229 return nRet;
230}
231
232bool CClipboardMimeData::hasFormat(const QString &mimetype) const
233{
234 //*
235 qDebug(log) << "CClipboardMimeData::hasFormat:"
236 << mimetype.toStdString().c_str();//*/
237
238 if(isImage(mimetype) && m_lstFormats.contains("image/bmp"))
239 return true;
240 if(isUrls(mimetype) && m_lstFormats.contains("text/uri-list"))
241 return true;
242 return m_lstFormats.contains(mimetype);
243}
244
245QStringList CClipboardMimeData::formats() const
246{
247 /*
248 qDebug(log) << "CClipboardMimeData::formats:" << m_lstFormats; //*/
249 return m_lstFormats;
250}
251
252#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
253QVariant CClipboardMimeData::retrieveData(const QString &mimeType,
254 QMetaType preferredType) const
255
256#else
257QVariant CClipboardMimeData::retrieveData(const QString &mimeType,
258 QVariant::Type preferredType) const
259#endif
260{
261 //*
262 qDebug(log) << "CClipboardMimeData::retrieveData:" << GetId() << mimeType
263 << "Variant:" << m_Variant; //*/
264 if(MIME_TYPE_RABBITREMOTECONTROL_PLUGINS_FREERDP == mimeType)
265 return GetId();
266
267 QString mt = mimeType;
268 if(isImage(mt)) mt = "image/bmp";
269 if(m_indexString.find(mt) == m_indexString.end())
270 return QVariant();
271
272 _FORMAT value;
273 auto lstValue = m_indexString.values(mt);
274 if(lstValue.isEmpty())
275 return QVariant();
276 value = *lstValue.crbegin();
277 //*
278 qDebug(log) << "CClipboardMimeData::retrieveData: format id:" << value.id
279 << "name:" << value.name << "mimeData:" << mimeType; //*/
280
281 if(!m_pContext) return QVariant();
282
283 emit sigSendDataRequest(m_pContext, value.id);
284
285 // add wait response event
286 QEventLoop loop;
287 connect(this, SIGNAL(sigContinue()), &loop, SLOT(quit()), Qt::DirectConnection);
288 loop.exec();
289 qDebug(log) << "CClipboardMimeData::retrieveData end";
290 // Objecte destruct
291 if(m_bExit)
292 return QVariant();
293
294 if(isUrls(mimeType) && !m_Variant.isNull())
295 {
296 QByteArray data = m_Variant.toByteArray();
297 emit sigRequestFileFromServer(mimeType, value.name, data.data(), data.size());
298 }
299 return m_Variant;
300}
301
303void CClipboardMimeData::slotServerFormatData(const BYTE* pData, UINT32 nLen,
304 UINT32 id)
305{
306 //*
307 qDebug(log) << "CClipboardMimeData::slotServerFormatData: id:" << id;//*/
308
309 UINT32 srcId = 0;
310 UINT32 dstId = 0;
311 do{
312 if(!pData && 0 == nLen)
313 {
314 m_pContext = nullptr;
315 break;
316 }
317
318 if(m_indexId.find(id) == m_indexId.end())
319 break;
320
321 auto it = m_indexId[id];
322 switch (id) {
323 case CF_DIB:
324 case CF_DIBV5:
325 {
326 srcId = it.localId;
327 dstId = ClipboardGetFormatId(m_pClipboard, "image/bmp");
328 break;
329 }
330 default:
331 {
332 srcId = it.localId;
333 if(it.name.isEmpty())
334 dstId = it.localId;
335 else {
336 if(isHtml(it.name, false))
337 dstId = ClipboardGetFormatId(m_pClipboard, "text/html");
338 else
339 dstId = it.localId;
340 }
341 }
342 }
343 bool bSuccess = ClipboardSetData(m_pClipboard, srcId, pData, nLen);
344 if(!bSuccess) break;
345
346 UINT32 size = 0;
347 void* data = ClipboardGetData(m_pClipboard, dstId, &size);
348 if(!data)
349 {
350 qDebug(log) << "ClipboardGetData fail: dstId:" << dstId
351 << "srcId:" << srcId;
352 break;
353 }
354
355 QByteArray d((char*)data, size);
356 if(d.isEmpty()) break;
357
358 switch (id) {
359 case CF_TEXT:
360 {
361 m_Variant = QString::fromLatin1(d);
362 break;
363 }
364 case CF_OEMTEXT:
365 {
366#ifdef Q_OS_WINDOWS
367 m_Variant = QString::fromLocal8Bit(d);
368#else
369 m_Variant = d;
370#endif
371 break;
372 }
373 case CF_UNICODETEXT:
374 {
375 m_Variant = QString::fromUtf16((const char16_t*)data, size / 2);
376 break;
377 }
378 default:
379 if("UTF8_STRING" == it.name) {
380 m_Variant = QString::fromUtf8(d);
381 } else if(isHtml(it.name)) {
382 m_Variant = QString(d);
383 } else if(ClipboardGetFormatId(m_pClipboard, "image/bmp") == dstId) {
384 QImage img;
385 if(img.loadFromData(d, "BMP"))
386 m_Variant = img;
387 } else
388 m_Variant = QVariant(d);
389 }
390 }while(0);
391 emit sigContinue();
392 return;
393}
394
395bool CClipboardMimeData::isText(QString mimeType, bool bRegular) const
396{
397 //qDebug(log) << "CClipboardMimeData::isText:" << mimeType;
398 if("UTF8_STRING" == mimeType) return true;
399 if("TEXT" == mimeType) return true;
400 if("STRING" == mimeType) return true;
401 if("text/plain" == mimeType) return true;
402 if(bRegular)
403 {
404 QRegularExpression re("text/plain[;]*.*",
405 QRegularExpression::CaseInsensitiveOption);
406 QRegularExpressionMatch match = re.match(mimeType);
407 if(match.hasMatch())
408 return true;
409 }
410 return false;
411}
412
413bool CClipboardMimeData::isHtml(QString mimeType, bool bRegular) const
414{
415 //qDebug(log) << "CClipboardMimeData::isHtml:" << mimeType;
416
417 if("text/html" == mimeType || "HTML Format" == mimeType)
418 return true;
419
420 return false;
421}
422
423bool CClipboardMimeData::isUrls(QString mimeType, bool bRegular) const
424{
425 //qDebug(log) << "CClipboardMimeData::isUrls:" << mimeType;
426
427 if("FileGroupDescriptorW" == mimeType
428 || "FileGroupDescriptor" == mimeType
429 || "UniformResourceLocatorW" == mimeType
430 || "UniformResourceLocator" == mimeType
431 || "text/uri-list" == mimeType
432 || "x-special/gnome-copied-files" == mimeType)
433 return true;
434
435 return false;
436}
437
438bool CClipboardMimeData::isImage(QString mimeType, bool bRegular) const
439{
440 //qDebug(log) << "CClipboardMimeData::isImage:" << mimeType;
441
442 if("image/bmp" == mimeType) return true;
443 // QClipboard return QImage mimeType is "application/x-qt-image"
444 if("application/x-qt-image" == mimeType) return true;
445 if(bRegular)
446 {
447 QRegularExpression re("image/.*",
448 QRegularExpression::CaseInsensitiveOption);
449 QRegularExpressionMatch match = re.match(mimeType);
450 if(match.hasMatch())
451 return true;
452 }
453 return false;
454}
455
456void CClipboardMimeData::slotRequestFileFromServer(const QString &mimeType,
457 const QString &valueName,
458 const void *pData,
459 const UINT32 nLen)
460{
461 return; //TODO: delete it!!!
462 //*
463 qDebug(log) << "CClipboardMimeData::slotRequestFileFromServer:"
464 << valueName << mimeType << pData;//*/
465 if(!("FileGroupDescriptorW" == valueName
466 || "FileGroupDescriptor" == valueName))
467 return;
468
469 int srcId = ClipboardGetFormatId(m_pClipboard, valueName.toStdString().c_str());
470 int dstId = ClipboardGetFormatId(m_pClipboard, mimeType.toStdString().c_str());
471 bool bSuccess = ClipboardSetData(m_pClipboard, srcId, pData, nLen);
472 if(!bSuccess) {
473 qCritical(log) << "ClipboardSetData fail: dstId:" << dstId
474 << "srcId:" << srcId;
475 return;
476 }
477
478 UINT32 size = 0;
479 void* data = ClipboardGetData(m_pClipboard, dstId, &size);
480 if(!data) {
481 qCritical(log) << "ClipboardGetData fail: dstId:" << dstId
482 << "srcId:" << srcId;
483 return;
484 }
485 QString szFiles = QString::fromLatin1((char*)data, size);
486 QStringList lstFile = szFiles.split("\n");
487 free(data);
488
490 for(int i = 0; i < pDes->cItems; i++)
491 {
492 QString szFile = lstFile[i].trimmed();
493 szFile = QUrl(szFile).toLocalFile();
494 QFileInfo fileInfo(szFile);
495 QDir d(fileInfo.absolutePath());
496 if(!d.exists())
497 d.mkpath(fileInfo.absolutePath());
498
499 QSharedPointer<_CliprdrFileStream> stream
500 = QSharedPointer<_CliprdrFileStream>(new _CliprdrFileStream());
501 stream->m_Success = false;
502 stream->m_File.setFileName(szFile) ;
503 m_Stream.insert(i, stream);
504
505 if(pDes->fgd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
506 continue;
507 // Get file size
508 UINT rc = sendRequestFilecontents(i, FILECONTENTS_SIZE, 0, 0, 8);
509 if(CHANNEL_RC_OK != rc)
510 {
511 continue;
512 }
513 if(stream->m_Data.isNull() || stream->m_Data.size() == 0)
514 continue;
515 ULARGE_INTEGER size, offset;
516 offset.QuadPart = 0;
517 size.QuadPart = *((LONGLONG*)(stream->m_Data.data()));
518 if(size.QuadPart <= 0)
519 continue;
520
521 qDebug(log) << "File" << szFile
522 << ";Length:" << size.u.HighPart << size.u.LowPart;
523 // Open local file
524 if(!stream->m_File.open(QFile::WriteOnly))
525 {
526 qCritical(log) << "Open file fail:" << szFile
527 << stream->m_File.errorString();
528 return;
529 }
530 bool bSuccess = true;
531 do {
532 UINT32 nBlock = 2 << 15;
533 UINT32 nLen = size.QuadPart - offset.QuadPart;
534 if(nLen > nBlock)
535 nLen = nBlock;
536 // Request file from server
537 rc = sendRequestFilecontents(i, FILECONTENTS_RANGE,
538 offset.u.HighPart,
539 offset.u.LowPart,
540 nLen);
541 if(CHANNEL_RC_OK != rc)
542 {
543 bSuccess = false;
544 break;
545 }
546 if(stream->m_Data.isNull() || stream->m_Data.size() == 0)
547 {
548 bSuccess = false;
549 break;
550 }
551 // Save to local file
552 stream->m_File.write(stream->m_Data);
553 offset.QuadPart += stream->m_Data.size();
554 } while(offset.QuadPart < size.QuadPart);
555 stream->m_File.close();
556 if(!bSuccess)
557 stream->m_File.remove();
558 stream->m_Success = bSuccess;
559 }
560
561 // Convert file list
562 // "x-special/gnome-copied-files" format is copy\nLocalFile1\nLocalFile2\n...
563 if("x-special/gnome-copied-files" == mimeType)
564 {
565 QByteArray gnomeFormat;
566 gnomeFormat.append("copy\n");
567 int b = 0;
568 foreach(auto s, m_Stream)
569 {
570 if(!s->m_Success)
571 continue;
572 QString fileName;
573 if(b)
574 fileName += "\n";
575 else
576 b=1;
577 fileName += QUrl::fromLocalFile(s->m_File.fileName()).toEncoded();
578 gnomeFormat.append(fileName.toStdString().c_str());
579 }
580 m_gnomeFiles = gnomeFormat;
581 m_Variant = gnomeFormat;
582 }
583 //*
584 // "text/uri-list" format is LocalFile1\r\nLocalFile2\r\n...
585 // See:
586 // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
587 // uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
588 // uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
589 if("text/uri-list" == mimeType || "FileGroupDescriptorW" == mimeType)
590 {
591 QByteArray uriFormat;
592 foreach(auto s, m_Stream)
593 {
594 if(!s->m_Success)
595 continue;
596 QString fileName;
597 fileName += QUrl::fromLocalFile(s->m_File.fileName()).toEncoded();
598 fileName += "\r\n";
599 uriFormat.append(fileName.toStdString().c_str());
600 }
601 m_uriFiles = uriFormat;
602 m_Variant = uriFormat;
603 } //*/
604
605 qDebug(log) << "CClipboardMimeData::slotRequestFileFromServer::QVariant:" << m_Variant;
606
607 return;
608}
609
610UINT CClipboardMimeData::sendRequestFilecontents(UINT32 listIndex,
611 UINT32 dwFlags,
612 DWORD nPositionHigh,
613 DWORD nPositionLow,
614 UINT32 cbRequested)
615{
616 //*
617 qDebug(log) << "CClipboardMimeData::sendRequestFilecontents";//*/
618 UINT rc = ERROR_INTERNAL_ERROR;
619 if(!m_pContext) return rc;
620
621 CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest = {0};
622 fileContentsRequest.streamId = listIndex;
623 fileContentsRequest.listIndex = listIndex;
624 fileContentsRequest.dwFlags = dwFlags;
625 switch (dwFlags)
626 {
627 /*
628 * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST).
629 *
630 * A request for the size of the file identified by the lindex field. The size MUST be
631 * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to
632 * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be
633 * set to 0x00000000.
634 */
635 case FILECONTENTS_SIZE:
636 fileContentsRequest.cbRequested = sizeof(UINT64);
637 fileContentsRequest.nPositionHigh = 0;
638 fileContentsRequest.nPositionLow = 0;
639 break;
640 case FILECONTENTS_RANGE:
641 fileContentsRequest.cbRequested = cbRequested;
642 fileContentsRequest.nPositionHigh = nPositionHigh;
643 fileContentsRequest.nPositionLow = nPositionLow;
644 break;
645 }
646 rc = m_pContext->ClientFileContentsRequest(m_pContext, &fileContentsRequest);
647
648 // add wait response event
649 QEventLoop loop;
650 connect(this, SIGNAL(sigContinue()), &loop, SLOT(quit()), Qt::DirectConnection);
651 loop.exec();
652
653 // Objecte destruct
654 if(m_bExit)
655 return CHANNEL_RC_NULL_DATA;
656
657 return rc;
658}
659
660void CClipboardMimeData::slotServerFileContentsRespose(UINT32 streamId,
661 QByteArray &data)
662{
663 //*
664 qDebug(log) << "CClipboardMimeData::slotServerFileContentsRespose: index:"
665 << streamId << ";Data length:" << data.size();//*/
666 auto stream = m_Stream.find(streamId);
667 do{
668 if(m_Stream.end() == stream || data.isNull())
669 break;
670 QSharedPointer<_CliprdrFileStream> s = *stream;
671 s->m_Data = data;
672 }while(0);
673 emit sigContinue();
674}
void slotServerFormatData(const BYTE *pData, UINT32 nLen, UINT32 id)
if(pData == nullptr && nLen == 0) is Notify clipboard program has exited