1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qwindowsmime.h"
41 #include "qwindowscontext.h"
42 
43 #include <QtGui/private/qinternalmimedata_p.h>
44 #include <QtCore/qbytearraymatcher.h>
45 #include <QtCore/qtextcodec.h>
46 #include <QtCore/qmap.h>
47 #include <QtCore/qurl.h>
48 #include <QtCore/qdir.h>
49 #include <QtCore/qdebug.h>
50 #include <QtCore/qbuffer.h>
51 #include <QtGui/qimagereader.h>
52 #include <QtGui/qimagewriter.h>
53 
54 #include <shlobj.h>
55 #include <algorithm>
56 
57 QT_BEGIN_NAMESPACE
58 
59 /* The MSVC compilers allows multi-byte characters, that has the behavior of
60  * that each character gets shifted into position. 0x73524742 below is for MSVC
61  * equivalent to doing 'sRGB', but this does of course not work
62  * on conformant C++ compilers. */
63 #define BMP_LCS_sRGB  0x73524742
64 #define BMP_LCS_GM_IMAGES  0x00000004L
65 
66 struct _CIEXYZ {
67     long ciexyzX, ciexyzY, ciexyzZ;
68 };
69 
70 struct _CIEXYZTRIPLE {
71     _CIEXYZ  ciexyzRed, ciexyzGreen, ciexyzBlue;
72 };
73 
74 struct BMP_BITMAPV5HEADER {
75     DWORD  bV5Size;
76     LONG   bV5Width;
77     LONG   bV5Height;
78     WORD   bV5Planes;
79     WORD   bV5BitCount;
80     DWORD  bV5Compression;
81     DWORD  bV5SizeImage;
82     LONG   bV5XPelsPerMeter;
83     LONG   bV5YPelsPerMeter;
84     DWORD  bV5ClrUsed;
85     DWORD  bV5ClrImportant;
86     DWORD  bV5RedMask;
87     DWORD  bV5GreenMask;
88     DWORD  bV5BlueMask;
89     DWORD  bV5AlphaMask;
90     DWORD  bV5CSType;
91     _CIEXYZTRIPLE bV5Endpoints;
92     DWORD  bV5GammaRed;
93     DWORD  bV5GammaGreen;
94     DWORD  bV5GammaBlue;
95     DWORD  bV5Intent;
96     DWORD  bV5ProfileData;
97     DWORD  bV5ProfileSize;
98     DWORD  bV5Reserved;
99 };
100 static const int BMP_BITFIELDS = 3;
101 
102 static const char dibFormatC[] = "dib";
103 
msgConversionError(const char * func,const char * format)104 static inline QByteArray msgConversionError(const char *func, const char *format)
105 {
106     QByteArray msg = func;
107     msg += ": Unable to convert DIB image. The image converter plugin for '";
108     msg += format;
109     msg += "' is not available. Available formats: ";
110     const auto &formats = QImageReader::supportedImageFormats();
111     for (const QByteArray &af : formats) {
112         msg += af;
113         msg += ' ';
114     }
115     return msg;
116 }
117 
readDib(QByteArray data)118 static inline QImage readDib(QByteArray data)
119 {
120     QBuffer buffer(&data);
121     buffer.open(QIODevice::ReadOnly);
122     QImageReader reader(&buffer, dibFormatC);
123     if (!reader.canRead()) {
124          qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
125          return QImage();
126     }
127     return reader.read();
128 }
129 
writeDib(const QImage & img)130 static QByteArray writeDib(const QImage &img)
131 {
132     QByteArray ba;
133     QBuffer buffer(&ba);
134     buffer.open(QIODevice::ReadWrite);
135     QImageWriter writer(&buffer, dibFormatC);
136     if (!writer.canWrite()) {
137         qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
138         return ba;
139     }
140     if (!writer.write(img))
141         ba.clear();
142     return ba;
143 }
144 
qt_write_dibv5(QDataStream & s,QImage image)145 static bool qt_write_dibv5(QDataStream &s, QImage image)
146 {
147     QIODevice* d = s.device();
148     if (!d->isWritable())
149         return false;
150 
151     //depth will be always 32
152     int bpl_bmp = image.width()*4;
153 
154     BMP_BITMAPV5HEADER bi;
155     ZeroMemory(&bi, sizeof(bi));
156     bi.bV5Size          = sizeof(BMP_BITMAPV5HEADER);
157     bi.bV5Width         = image.width();
158     bi.bV5Height        = image.height();
159     bi.bV5Planes        = 1;
160     bi.bV5BitCount      = 32;
161     bi.bV5Compression   = BI_BITFIELDS;
162     bi.bV5SizeImage     = DWORD(bpl_bmp * image.height());
163     bi.bV5XPelsPerMeter = 0;
164     bi.bV5YPelsPerMeter = 0;
165     bi.bV5ClrUsed       = 0;
166     bi.bV5ClrImportant  = 0;
167     bi.bV5BlueMask      = 0x000000ff;
168     bi.bV5GreenMask     = 0x0000ff00;
169     bi.bV5RedMask       = 0x00ff0000;
170     bi.bV5AlphaMask     = 0xff000000;
171     bi.bV5CSType        = BMP_LCS_sRGB;         //LCS_sRGB
172     bi.bV5Intent        = BMP_LCS_GM_IMAGES;    //LCS_GM_IMAGES
173 
174     d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
175     if (s.status() != QDataStream::Ok)
176         return false;
177 
178     if (image.format() != QImage::Format_ARGB32)
179         image = image.convertToFormat(QImage::Format_ARGB32);
180 
181     auto *buf = new uchar[bpl_bmp];
182 
183     memset(buf, 0, size_t(bpl_bmp));
184     for (int y=image.height()-1; y>=0; y--) {
185         // write the image bits
186         const QRgb *p = reinterpret_cast<const QRgb *>(image.constScanLine(y));
187         const QRgb *end = p + image.width();
188         uchar *b = buf;
189         while (p < end) {
190             int alpha = qAlpha(*p);
191             if (alpha) {
192                 *b++ = uchar(qBlue(*p));
193                 *b++ = uchar(qGreen(*p));
194                 *b++ = uchar(qRed(*p));
195             } else {
196                 //white for fully transparent pixels.
197                 *b++ = 0xff;
198                 *b++ = 0xff;
199                 *b++ = 0xff;
200             }
201             *b++ = uchar(alpha);
202             p++;
203         }
204         d->write(reinterpret_cast<const char *>(buf), bpl_bmp);
205         if (s.status() != QDataStream::Ok) {
206             delete[] buf;
207             return false;
208         }
209     }
210     delete[] buf;
211     return true;
212 }
213 
calc_shift(int mask)214 static int calc_shift(int mask)
215 {
216     int result = 0;
217     while (!(mask & 1)) {
218         result++;
219         mask >>= 1;
220     }
221     return result;
222 }
223 
224 //Supports only 32 bit DIBV5
qt_read_dibv5(QDataStream & s,QImage & image)225 static bool qt_read_dibv5(QDataStream &s, QImage &image)
226 {
227     BMP_BITMAPV5HEADER bi;
228     QIODevice* d = s.device();
229     if (d->atEnd())
230         return false;
231 
232     d->read(reinterpret_cast<char *>(&bi), sizeof(bi));   // read BITMAPV5HEADER header
233     if (s.status() != QDataStream::Ok)
234         return false;
235 
236     const int nbits = bi.bV5BitCount;
237     if (nbits != 32 || bi.bV5Planes != 1 || bi.bV5Compression != BMP_BITFIELDS)
238         return false; //Unsupported DIBV5 format
239 
240     const int w = bi.bV5Width;
241     int h = bi.bV5Height;
242     const int red_mask = int(bi.bV5RedMask);
243     const int green_mask = int(bi.bV5GreenMask);
244     const int blue_mask = int(bi.bV5BlueMask);
245     const int alpha_mask = int(bi.bV5AlphaMask);
246 
247     const QImage::Format format = QImage::Format_ARGB32;
248 
249     if (bi.bV5Height < 0)
250         h = -h;     // support images with negative height
251     if (image.size() != QSize(w, h) || image.format() != format) {
252         image = QImage(w, h, format);
253         if (image.isNull())     // could not create image
254             return false;
255     }
256     image.setDotsPerMeterX(bi.bV5XPelsPerMeter);
257     image.setDotsPerMeterY(bi.bV5YPelsPerMeter);
258 
259     const int red_shift = calc_shift(red_mask);
260     const int green_shift = calc_shift(green_mask);
261     const int blue_shift = calc_shift(blue_mask);
262     const int alpha_shift =  alpha_mask ? calc_shift(alpha_mask) : 0u;
263 
264     const int  bpl = image.bytesPerLine();
265     uchar *data = image.bits();
266 
267     auto *buf24 = new uchar[bpl];
268     const int bpl24 = ((w * nbits + 31) / 32) * 4;
269 
270     while (--h >= 0) {
271         QRgb *p = reinterpret_cast<QRgb *>(data + h * bpl);
272         QRgb *end = p + w;
273         if (d->read(reinterpret_cast<char *>(buf24), bpl24) != bpl24)
274             break;
275         const uchar *b = buf24;
276         while (p < end) {
277             const int c = *b | (*(b + 1)) << 8 | (*(b + 2)) << 16 | (*(b + 3)) << 24;
278             *p++ = qRgba(((c & red_mask) >> red_shift) ,
279                                     ((c & green_mask) >> green_shift),
280                                     ((c & blue_mask) >> blue_shift),
281                                     ((c & alpha_mask) >> alpha_shift));
282             b += 4;
283         }
284     }
285     delete[] buf24;
286 
287     if (bi.bV5Height < 0) {
288         // Flip the image
289         auto *buf = new uchar[bpl];
290         h = -bi.bV5Height;
291         for (int y = 0; y < h/2; ++y) {
292             memcpy(buf, data + y * bpl, size_t(bpl));
293             memcpy(data + y*bpl, data + (h - y -1) * bpl, size_t(bpl));
294             memcpy(data + (h - y -1 ) * bpl, buf, size_t(bpl));
295         }
296         delete [] buf;
297     }
298 
299     return true;
300 }
301 
302 // helpers for using global memory
303 
getCf(const FORMATETC & formatetc)304 static int getCf(const FORMATETC &formatetc)
305 {
306     return formatetc.cfFormat;
307 }
308 
setCf(int cf)309 static FORMATETC setCf(int cf)
310 {
311     FORMATETC formatetc;
312     formatetc.cfFormat = CLIPFORMAT(cf);
313     formatetc.dwAspect = DVASPECT_CONTENT;
314     formatetc.lindex = -1;
315     formatetc.ptd = nullptr;
316     formatetc.tymed = TYMED_HGLOBAL;
317     return formatetc;
318 }
319 
setData(const QByteArray & data,STGMEDIUM * pmedium)320 static bool setData(const QByteArray &data, STGMEDIUM *pmedium)
321 {
322     HGLOBAL hData = GlobalAlloc(0, SIZE_T(data.size()));
323     if (!hData)
324         return false;
325 
326     void *out = GlobalLock(hData);
327     memcpy(out, data.data(), size_t(data.size()));
328     GlobalUnlock(hData);
329     pmedium->tymed = TYMED_HGLOBAL;
330     pmedium->hGlobal = hData;
331     pmedium->pUnkForRelease = nullptr;
332     return true;
333 }
334 
getData(int cf,IDataObject * pDataObj,int lindex=-1)335 static QByteArray getData(int cf, IDataObject *pDataObj, int lindex = -1)
336 {
337     QByteArray data;
338     FORMATETC formatetc = setCf(cf);
339     formatetc.lindex = lindex;
340     STGMEDIUM s;
341     if (pDataObj->GetData(&formatetc, &s) == S_OK) {
342         const void *val = GlobalLock(s.hGlobal);
343         data = QByteArray::fromRawData(reinterpret_cast<const char *>(val), int(GlobalSize(s.hGlobal)));
344         data.detach();
345         GlobalUnlock(s.hGlobal);
346         ReleaseStgMedium(&s);
347     } else  {
348         //Try reading IStream data
349         formatetc.tymed = TYMED_ISTREAM;
350         if (pDataObj->GetData(&formatetc, &s) == S_OK) {
351             char szBuffer[4096];
352             ULONG actualRead = 0;
353             LARGE_INTEGER pos = {{0, 0}};
354             //Move to front (can fail depending on the data model implemented)
355             HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
356             while(SUCCEEDED(hr)){
357                 hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
358                 if (SUCCEEDED(hr) && actualRead > 0) {
359                     data += QByteArray::fromRawData(szBuffer, int(actualRead));
360                 }
361                 if (actualRead != sizeof(szBuffer))
362                     break;
363             }
364             data.detach();
365             ReleaseStgMedium(&s);
366         }
367     }
368     return data;
369 }
370 
canGetData(int cf,IDataObject * pDataObj)371 static bool canGetData(int cf, IDataObject * pDataObj)
372 {
373     FORMATETC formatetc = setCf(cf);
374      if (pDataObj->QueryGetData(&formatetc) != S_OK){
375         formatetc.tymed = TYMED_ISTREAM;
376         return pDataObj->QueryGetData(&formatetc) == S_OK;
377     }
378     return true;
379 }
380 
381 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug d,const FORMATETC & tc)382 QDebug operator<<(QDebug d, const FORMATETC &tc)
383 {
384     QDebugStateSaver saver(d);
385     d.nospace();
386     d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
387     switch (tc.cfFormat) {
388     case CF_TEXT:
389         d << "CF_TEXT";
390         break;
391     case CF_BITMAP:
392         d << "CF_BITMAP";
393         break;
394     case CF_TIFF:
395         d << "CF_TIFF";
396         break;
397     case CF_OEMTEXT:
398         d << "CF_OEMTEXT";
399         break;
400     case CF_DIB:
401         d << "CF_DIB";
402         break;
403     case CF_DIBV5:
404         d << "CF_DIBV5";
405         break;
406     case CF_UNICODETEXT:
407         d << "CF_UNICODETEXT";
408         break;
409     case CF_ENHMETAFILE:
410         d << "CF_ENHMETAFILE";
411         break;
412     default:
413         d << QWindowsMimeConverter::clipboardFormatName(tc.cfFormat);
414         break;
415     }
416     d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
417         << ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
418     return d;
419 }
420 
operator <<(QDebug d,IDataObject * dataObj)421 QDebug operator<<(QDebug d, IDataObject *dataObj)
422 {
423     QDebugStateSaver saver(d);
424     d.nospace();
425     d.noquote();
426     d << "IDataObject(";
427     if (dataObj) { // Output formats contained in IDataObject.
428         IEnumFORMATETC *enumFormatEtc;
429         if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
430             FORMATETC formatEtc[1];
431             ULONG fetched;
432             if (SUCCEEDED(enumFormatEtc->Reset())) {
433                 while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
434                     d << formatEtc[0] << ',';
435                 enumFormatEtc->Release();
436             }
437         }
438     } else {
439         d << '0';
440     }
441     d << ')';
442     return d;
443 }
444 #endif // !QT_NO_DEBUG_STREAM
445 
446 /*!
447     \class QWindowsMime
448     \brief The QWindowsMime class maps open-standard MIME to Window Clipboard formats.
449     \internal
450 
451     Qt's drag-and-drop and clipboard facilities use the MIME standard.
452     On X11, this maps trivially to the Xdnd protocol, but on Windows
453     although some applications use MIME types to describe clipboard
454     formats, others use arbitrary non-standardized naming conventions,
455     or unnamed built-in formats of Windows.
456 
457     By instantiating subclasses of QWindowsMime that provide conversions
458     between Windows Clipboard and MIME formats, you can convert
459     proprietary clipboard formats to MIME formats.
460 
461     Qt has predefined support for the following Windows Clipboard formats:
462 
463     \table
464     \header \li Windows Format \li Equivalent MIME type
465     \row \li \c CF_UNICODETEXT \li \c text/plain
466     \row \li \c CF_TEXT        \li \c text/plain
467     \row \li \c CF_DIB         \li \c{image/xyz}, where \c xyz is
468                                  a \l{QImageWriter::supportedImageFormats()}{Qt image format}
469     \row \li \c CF_HDROP       \li \c text/uri-list
470     \row \li \c CF_INETURL     \li \c text/uri-list
471     \row \li \c CF_HTML        \li \c text/html
472     \endtable
473 
474     An example use of this class would be to map the Windows Metafile
475     clipboard format (\c CF_METAFILEPICT) to and from the MIME type
476     \c{image/x-wmf}. This conversion might simply be adding or removing
477     a header, or even just passing on the data. See \l{Drag and Drop}
478     for more information on choosing and definition MIME types.
479 
480     You can check if a MIME type is convertible using canConvertFromMime() and
481     can perform conversions with convertToMime() and convertFromMime().
482 
483     \sa QWindowsMimeConverter
484 */
485 
486 /*!
487 Constructs a new conversion object, adding it to the globally accessed
488 list of available converters.
489 */
490 QWindowsMime::QWindowsMime() = default;
491 
492 /*!
493 Destroys a conversion object, removing it from the global
494 list of available converters.
495 */
496 QWindowsMime::~QWindowsMime() = default;
497 
498 /*!
499     Registers the MIME type \a mime, and returns an ID number
500     identifying the format on Windows.
501 */
registerMimeType(const QString & mime)502 int QWindowsMime::registerMimeType(const QString &mime)
503 {
504     const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mime.utf16()));
505     if (!f)
506         qErrnoWarning("QWindowsMime::registerMimeType: Failed to register clipboard format");
507 
508     return int(f);
509 }
510 
511 /*!
512 \fn bool QWindowsMime::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
513 
514   Returns \c true if the converter can convert from the \a mimeData to
515   the format specified in \a formatetc.
516 
517   All subclasses must reimplement this pure virtual function.
518 */
519 
520 /*!
521   \fn bool QWindowsMime::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
522 
523   Returns \c true if the converter can convert to the \a mimeType from
524   the available formats in \a pDataObj.
525 
526   All subclasses must reimplement this pure virtual function.
527 */
528 
529 /*!
530 \fn QString QWindowsMime::mimeForFormat(const FORMATETC &formatetc) const
531 
532   Returns the mime type that will be created form the format specified
533   in \a formatetc, or an empty string if this converter does not support
534   \a formatetc.
535 
536   All subclasses must reimplement this pure virtual function.
537 */
538 
539 /*!
540 \fn QVector<FORMATETC> QWindowsMime::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
541 
542   Returns a QVector of FORMATETC structures representing the different windows clipboard
543   formats that can be provided for the \a mimeType from the \a mimeData.
544 
545   All subclasses must reimplement this pure virtual function.
546 */
547 
548 /*!
549     \fn QVariant QWindowsMime::convertToMime(const QString &mimeType, IDataObject *pDataObj,
550                                              QVariant::Type preferredType) const
551 
552     Returns a QVariant containing the converted data for \a mimeType from \a pDataObj.
553     If possible the QVariant should be of the \a preferredType to avoid needless conversions.
554 
555     All subclasses must reimplement this pure virtual function.
556 */
557 
558 /*!
559 \fn bool QWindowsMime::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
560 
561   Convert the \a mimeData to the format specified in \a formatetc.
562   The converted data should then be placed in \a pmedium structure.
563 
564   Return true if the conversion was successful.
565 
566   All subclasses must reimplement this pure virtual function.
567 */
568 
569 class QWindowsMimeText : public QWindowsMime
570 {
571 public:
572     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
573     QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const override;
574     QString mimeForFormat(const FORMATETC &formatetc) const override;
575     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
576     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
577     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
578 };
579 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const580 bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
581 {
582     int cf = getCf(formatetc);
583     return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText();
584 }
585 
586 /*
587 text/plain is defined as using CRLF, but so many programs don't,
588 and programmers just look for '\n' in strings.
589 Windows really needs CRLF, so we ensure it here.
590 */
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const591 bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
592 {
593     if (canConvertFromMime(formatetc, mimeData)) {
594         QByteArray data;
595         int cf = getCf(formatetc);
596         if (cf == CF_TEXT) {
597             data = mimeData->text().toLocal8Bit();
598             // Anticipate required space for CRLFs at 1/40
599             int maxsize=data.size()+data.size()/40+3;
600             QByteArray r(maxsize, '\0');
601             char* o = r.data();
602             const char* d = data.data();
603             const int s = data.size();
604             bool cr=false;
605             int j=0;
606             for (int i=0; i<s; i++) {
607                 char c = d[i];
608                 if (c=='\r')
609                     cr=true;
610                 else {
611                     if (c=='\n') {
612                         if (!cr)
613                             o[j++]='\r';
614                     }
615                     cr=false;
616                 }
617                 o[j++]=c;
618                 if (j+3 >= maxsize) {
619                     maxsize += maxsize/4;
620                     r.resize(maxsize);
621                     o = r.data();
622                 }
623             }
624             o[j]=0;
625             return setData(r, pmedium);
626         }
627         if (cf == CF_UNICODETEXT) {
628             QString str = mimeData->text();
629             const QChar *u = str.unicode();
630             QString res;
631             const int s = str.length();
632             int maxsize = s + s/40 + 3;
633             res.resize(maxsize);
634             int ri = 0;
635             bool cr = false;
636             for (int i=0; i < s; ++i) {
637                 if (*u == u'\r')
638                     cr = true;
639                 else {
640                     if (*u == u'\n' && !cr)
641                         res[ri++] = u'\r';
642                     cr = false;
643                 }
644                 res[ri++] = *u;
645                 if (ri+3 >= maxsize) {
646                     maxsize += maxsize/4;
647                     res.resize(maxsize);
648                 }
649                 ++u;
650             }
651             res.truncate(ri);
652             const int byteLength = res.length() * int(sizeof(ushort));
653             QByteArray r(byteLength + 2, '\0');
654             memcpy(r.data(), res.unicode(), size_t(byteLength));
655             r[byteLength] = 0;
656             r[byteLength+1] = 0;
657             return setData(r, pmedium);
658         }
659     }
660     return false;
661 }
662 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const663 bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
664 {
665     return mimeType.startsWith(u"text/plain")
666            && (canGetData(CF_UNICODETEXT, pDataObj)
667            || canGetData(CF_TEXT, pDataObj));
668 }
669 
mimeForFormat(const FORMATETC & formatetc) const670 QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
671 {
672     int cf = getCf(formatetc);
673     if (cf == CF_UNICODETEXT || cf == CF_TEXT)
674         return QStringLiteral("text/plain");
675     return QString();
676 }
677 
678 
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const679 QVector<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
680 {
681     QVector<FORMATETC> formatics;
682     if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
683         formatics += setCf(CF_UNICODETEXT);
684         formatics += setCf(CF_TEXT);
685     }
686     return formatics;
687 }
688 
convertToMime(const QString & mime,LPDATAOBJECT pDataObj,QVariant::Type preferredType) const689 QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
690 {
691     QVariant ret;
692 
693     if (canConvertToMime(mime, pDataObj)) {
694         QString str;
695         QByteArray data = getData(CF_UNICODETEXT, pDataObj);
696         if (!data.isEmpty()) {
697             str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
698             str.replace(QLatin1String("\r\n"), QLatin1String("\n"));
699         } else {
700             data = getData(CF_TEXT, pDataObj);
701             if (!data.isEmpty()) {
702                 const char* d = data.data();
703                 const unsigned s = qstrlen(d);
704                 QByteArray r(data.size()+1, '\0');
705                 char* o = r.data();
706                 int j=0;
707                 for (unsigned i = 0; i < s; ++i) {
708                     char c = d[i];
709                     if (c!='\r')
710                         o[j++]=c;
711                 }
712                 o[j]=0;
713                 str = QString::fromLocal8Bit(r);
714             }
715         }
716         if (preferredType == QVariant::String)
717             ret = str;
718         else
719             ret = std::move(str).toUtf8();
720     }
721     qCDebug(lcQpaMime) << __FUNCTION__ << ret;
722     return ret;
723 }
724 
725 class QWindowsMimeURI : public QWindowsMime
726 {
727 public:
728     QWindowsMimeURI();
729     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
730     QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const override;
731     QString mimeForFormat(const FORMATETC &formatetc) const override;
732     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
733     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
734     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
735 private:
736     int CF_INETURL_W; // wide char version
737     int CF_INETURL;
738 };
739 
QWindowsMimeURI()740 QWindowsMimeURI::QWindowsMimeURI()
741 {
742     CF_INETURL_W = QWindowsMime::registerMimeType(QStringLiteral("UniformResourceLocatorW"));
743     CF_INETURL = QWindowsMime::registerMimeType(QStringLiteral("UniformResourceLocator"));
744 }
745 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const746 bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
747 {
748     if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
749         const auto urls = mimeData->urls();
750         for (const QUrl &url : urls) {
751             if (url.isLocalFile())
752                 return true;
753         }
754     }
755     return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
756 }
757 
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const758 bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
759 {
760     if (canConvertFromMime(formatetc, mimeData)) {
761         if (getCf(formatetc) == CF_HDROP) {
762             const auto &urls = mimeData->urls();
763             QStringList fileNames;
764             int size = sizeof(DROPFILES)+2;
765             for (const QUrl &url : urls) {
766                 const QString fn = QDir::toNativeSeparators(url.toLocalFile());
767                 if (!fn.isEmpty()) {
768                     size += sizeof(ushort) * size_t(fn.length() + 1);
769                     fileNames.append(fn);
770                 }
771             }
772 
773             QByteArray result(size, '\0');
774             auto* d = reinterpret_cast<DROPFILES *>(result.data());
775             d->pFiles = sizeof(DROPFILES);
776             GetCursorPos(&d->pt); // try
777             d->fNC = true;
778             char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
779 
780             d->fWide = true;
781             auto *f = reinterpret_cast<wchar_t *>(files);
782             for (int i=0; i<fileNames.size(); i++) {
783                 const auto l = size_t(fileNames.at(i).length());
784                 memcpy(f, fileNames.at(i).utf16(), l * sizeof(ushort));
785                 f += l;
786                 *f++ = 0;
787             }
788             *f = 0;
789 
790             return setData(result, pmedium);
791         }
792         if (getCf(formatetc) == CF_INETURL_W) {
793             const auto urls = mimeData->urls();
794             QByteArray result;
795             if (!urls.isEmpty()) {
796                 QString url = urls.at(0).toString();
797                 result = QByteArray(reinterpret_cast<const char *>(url.utf16()),
798                                     url.length() * int(sizeof(ushort)));
799             }
800             result.append('\0');
801             result.append('\0');
802             return setData(result, pmedium);
803         }
804         if (getCf(formatetc) == CF_INETURL) {
805             const auto urls = mimeData->urls();
806             QByteArray result;
807             if (!urls.isEmpty())
808                 result = urls.at(0).toString().toLocal8Bit();
809             return setData(result, pmedium);
810         }
811     }
812 
813     return false;
814 }
815 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const816 bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
817 {
818     return mimeType == u"text/uri-list"
819            && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
820 }
821 
mimeForFormat(const FORMATETC & formatetc) const822 QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
823 {
824     QString format;
825     if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
826         format = QStringLiteral("text/uri-list");
827     return format;
828 }
829 
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const830 QVector<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
831 {
832     QVector<FORMATETC> formatics;
833     if (mimeType == u"text/uri-list") {
834         if (canConvertFromMime(setCf(CF_HDROP), mimeData))
835             formatics += setCf(CF_HDROP);
836         if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
837             formatics += setCf(CF_INETURL_W);
838         if (canConvertFromMime(setCf(CF_INETURL), mimeData))
839             formatics += setCf(CF_INETURL);
840     }
841     return formatics;
842 }
843 
convertToMime(const QString & mimeType,LPDATAOBJECT pDataObj,QVariant::Type preferredType) const844 QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const
845 {
846     if (mimeType == u"text/uri-list") {
847         if (canGetData(CF_HDROP, pDataObj)) {
848             QList<QVariant> urls;
849 
850             QByteArray data = getData(CF_HDROP, pDataObj);
851             if (data.isEmpty())
852                 return QVariant();
853 
854             const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.constData());
855             if (hdrop->fWide) {
856                 const auto *filesw = reinterpret_cast<const wchar_t *>(data.constData() + hdrop->pFiles);
857                 int i = 0;
858                 while (filesw[i]) {
859                     QString fileurl = QString::fromWCharArray(filesw + i);
860                     urls += QUrl::fromLocalFile(fileurl);
861                     i += fileurl.length()+1;
862                 }
863             } else {
864                 const char* files = reinterpret_cast<const char *>(data.constData() + hdrop->pFiles);
865                 int i=0;
866                 while (files[i]) {
867                     urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
868                     i += int(strlen(files+i))+1;
869                 }
870             }
871 
872             if (preferredType == QVariant::Url && urls.size() == 1)
873                 return urls.at(0);
874             if (!urls.isEmpty())
875                 return urls;
876         } else if (canGetData(CF_INETURL_W, pDataObj)) {
877             QByteArray data = getData(CF_INETURL_W, pDataObj);
878             if (data.isEmpty())
879                 return QVariant();
880             return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
881          } else if (canGetData(CF_INETURL, pDataObj)) {
882             QByteArray data = getData(CF_INETURL, pDataObj);
883             if (data.isEmpty())
884                 return QVariant();
885             return QUrl(QString::fromLocal8Bit(data.constData()));
886         }
887     }
888     return QVariant();
889 }
890 
891 class QWindowsMimeHtml : public QWindowsMime
892 {
893 public:
894     QWindowsMimeHtml();
895 
896     // for converting from Qt
897     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
898     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
899     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
900 
901     // for converting to Qt
902     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
903     QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
904     QString mimeForFormat(const FORMATETC &formatetc) const override;
905 
906 private:
907     int CF_HTML;
908 };
909 
QWindowsMimeHtml()910 QWindowsMimeHtml::QWindowsMimeHtml()
911 {
912     CF_HTML = QWindowsMime::registerMimeType(QStringLiteral("HTML Format"));
913 }
914 
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const915 QVector<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
916 {
917     QVector<FORMATETC> formatetcs;
918     if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
919         formatetcs += setCf(CF_HTML);
920     return formatetcs;
921 }
922 
mimeForFormat(const FORMATETC & formatetc) const923 QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
924 {
925     if (getCf(formatetc) == CF_HTML)
926         return QStringLiteral("text/html");
927     return QString();
928 }
929 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const930 bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
931 {
932     return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
933 }
934 
935 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const936 bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
937 {
938     return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
939 }
940 
941 /*
942 The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
943 in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
944 
945   Version: 1.0
946   StartHTML:xxxxxxxxxx
947   EndHTML:xxxxxxxxxx
948   StartFragment:xxxxxxxxxx
949   EndFragment:xxxxxxxxxx
950   ...html...
951 
952 */
convertToMime(const QString & mime,IDataObject * pDataObj,QVariant::Type preferredType) const953 QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const
954 {
955     Q_UNUSED(preferredType);
956     QVariant result;
957     if (canConvertToMime(mime, pDataObj)) {
958         QByteArray html = getData(CF_HTML, pDataObj);
959         static Q_RELAXED_CONSTEXPR auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
960         static Q_RELAXED_CONSTEXPR auto endMatcher   = qMakeStaticByteArrayMatcher("EndHTML:");
961         qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
962         int start = startMatcher.indexIn(html);
963         int end = endMatcher.indexIn(html);
964 
965         if (start != -1) {
966             int startOffset = start + 10;
967             int i = startOffset;
968             while (html.at(i) != '\r' && html.at(i) != '\n')
969                 ++i;
970             QByteArray bytecount = html.mid(startOffset, i - startOffset);
971             start = bytecount.toInt();
972         }
973 
974         if (end != -1) {
975             int endOffset = end + 8;
976             int i = endOffset ;
977             while (html.at(i) != '\r' && html.at(i) != '\n')
978                 ++i;
979             QByteArray bytecount = html.mid(endOffset , i - endOffset);
980             end = bytecount.toInt();
981         }
982 
983         if (end > start && start > 0) {
984             html = html.mid(start, end - start);
985             html.replace('\r', "");
986             result = QString::fromUtf8(html);
987         }
988     }
989     return result;
990 }
991 
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const992 bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
993 {
994     if (canConvertFromMime(formatetc, mimeData)) {
995         QByteArray data = mimeData->html().toUtf8();
996         QByteArray result =
997             "Version:1.0\r\n"                    // 0-12
998             "StartHTML:0000000107\r\n"           // 13-35
999             "EndHTML:0000000000\r\n"             // 36-55
1000             "StartFragment:0000000000\r\n"       // 56-81
1001             "EndFragment:0000000000\r\n\r\n";    // 82-107
1002 
1003         static Q_RELAXED_CONSTEXPR auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
1004         static Q_RELAXED_CONSTEXPR auto endFragmentMatcher   = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
1005 
1006         if (startFragmentMatcher.indexIn(data) == -1)
1007             result += "<!--StartFragment-->";
1008         result += data;
1009         if (endFragmentMatcher.indexIn(data) == -1)
1010             result += "<!--EndFragment-->";
1011 
1012         // set the correct number for EndHTML
1013         QByteArray pos = QByteArray::number(result.size());
1014         memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
1015 
1016         // set correct numbers for StartFragment and EndFragment
1017         pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
1018         memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
1019         pos = QByteArray::number(endFragmentMatcher.indexIn(result));
1020         memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
1021 
1022         return setData(result, pmedium);
1023     }
1024     return false;
1025 }
1026 
1027 
1028 #ifndef QT_NO_IMAGEFORMAT_BMP
1029 class QWindowsMimeImage : public QWindowsMime
1030 {
1031 public:
1032     QWindowsMimeImage();
1033     // for converting from Qt
1034     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1035     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1036     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1037 
1038     // for converting to Qt
1039     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1040     QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1041     QString mimeForFormat(const FORMATETC &formatetc) const override;
1042 private:
1043     bool hasOriginalDIBV5(IDataObject *pDataObj) const;
1044     UINT CF_PNG;
1045 };
1046 
QWindowsMimeImage()1047 QWindowsMimeImage::QWindowsMimeImage()
1048 {
1049     CF_PNG = RegisterClipboardFormat(L"PNG");
1050 }
1051 
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const1052 QVector<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1053 {
1054     QVector<FORMATETC> formatetcs;
1055     if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
1056         //add DIBV5 if image has alpha channel. Do not add CF_PNG here as it will confuse MS Office (QTBUG47656).
1057         auto image = qvariant_cast<QImage>(mimeData->imageData());
1058         if (!image.isNull() && image.hasAlphaChannel())
1059             formatetcs += setCf(CF_DIBV5);
1060         formatetcs += setCf(CF_DIB);
1061     }
1062     if (!formatetcs.isEmpty())
1063         qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1064     return formatetcs;
1065 }
1066 
mimeForFormat(const FORMATETC & formatetc) const1067 QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
1068 {
1069     int cf = getCf(formatetc);
1070     if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
1071        return QStringLiteral("application/x-qt-image");
1072     return QString();
1073 }
1074 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1075 bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1076 {
1077     return mimeType == u"application/x-qt-image"
1078         && (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
1079 }
1080 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1081 bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1082 {
1083     int cf = getCf(formatetc);
1084     if (!mimeData->hasImage())
1085         return false;
1086     const QImage image = qvariant_cast<QImage>(mimeData->imageData());
1087     if (image.isNull())
1088         return false;
1089     // QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
1090     // cannot handle it.
1091     return cf == CF_DIBV5 || cf == CF_DIB
1092         || (cf == int(CF_PNG) && image.hasAlphaChannel());
1093 }
1094 
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1095 bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1096 {
1097     int cf = getCf(formatetc);
1098     if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
1099         auto img = qvariant_cast<QImage>(mimeData->imageData());
1100         if (img.isNull())
1101             return false;
1102         QByteArray ba;
1103         if (cf == CF_DIB) {
1104             if (img.format() > QImage::Format_ARGB32)
1105                 img = img.convertToFormat(QImage::Format_RGB32);
1106             const QByteArray ba = writeDib(img);
1107             if (!ba.isEmpty())
1108                 return setData(ba, pmedium);
1109         } else if (cf == int(CF_PNG)) {
1110             QBuffer buffer(&ba);
1111             const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
1112             buffer.close();
1113             if (written)
1114                 return setData(ba, pmedium);
1115         } else {
1116             QDataStream s(&ba, QIODevice::WriteOnly);
1117             s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
1118             if (qt_write_dibv5(s, img))
1119                 return setData(ba, pmedium);
1120         }
1121     }
1122     return false;
1123 }
1124 
hasOriginalDIBV5(IDataObject * pDataObj) const1125 bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
1126 {
1127     bool isSynthesized = true;
1128     IEnumFORMATETC *pEnum = nullptr;
1129     HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
1130     if (res == S_OK && pEnum) {
1131         FORMATETC fc;
1132         while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
1133             if (fc.ptd)
1134                 CoTaskMemFree(fc.ptd);
1135             if (fc.cfFormat == CF_DIB)
1136                 break;
1137             if (fc.cfFormat == CF_DIBV5) {
1138                 isSynthesized  = false;
1139                 break;
1140             }
1141         }
1142         pEnum->Release();
1143     }
1144     return !isSynthesized;
1145 }
1146 
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1147 QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1148 {
1149     Q_UNUSED(preferredType);
1150     QVariant result;
1151     if (mimeType != u"application/x-qt-image")
1152         return result;
1153     //Try to convert from a format which has more data
1154     //DIBV5, use only if its is not synthesized
1155     if (canGetData(CF_DIBV5, pDataObj) && hasOriginalDIBV5(pDataObj)) {
1156         QImage img;
1157         QByteArray data = getData(CF_DIBV5, pDataObj);
1158         QDataStream s(&data, QIODevice::ReadOnly);
1159         s.setByteOrder(QDataStream::LittleEndian);
1160         if (qt_read_dibv5(s, img)) { // #### supports only 32bit DIBV5
1161             return img;
1162         }
1163     }
1164     //PNG, MS Office place this (undocumented)
1165     if (canGetData(CF_PNG, pDataObj)) {
1166         QImage img;
1167         QByteArray data = getData(CF_PNG, pDataObj);
1168         if (img.loadFromData(data, "PNG")) {
1169             return img;
1170         }
1171     }
1172     //Fallback to DIB
1173     if (canGetData(CF_DIB, pDataObj)) {
1174         const QImage img = readDib(getData(CF_DIB, pDataObj));
1175         if (!img.isNull())
1176             return img;
1177     }
1178     // Failed
1179     return result;
1180 }
1181 #endif
1182 
1183 class QBuiltInMimes : public QWindowsMime
1184 {
1185 public:
1186     QBuiltInMimes();
1187 
1188     // for converting from Qt
1189     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1190     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1191     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1192 
1193     // for converting to Qt
1194     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1195     QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1196     QString mimeForFormat(const FORMATETC &formatetc) const override;
1197 
1198 private:
1199     QMap<int, QString> outFormats;
1200     QMap<int, QString> inFormats;
1201 };
1202 
QBuiltInMimes()1203 QBuiltInMimes::QBuiltInMimes()
1204 : QWindowsMime()
1205 {
1206     outFormats.insert(QWindowsMime::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
1207     inFormats.insert(QWindowsMime::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
1208 }
1209 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1210 bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1211 {
1212     // really check
1213     return formatetc.tymed & TYMED_HGLOBAL
1214         && outFormats.contains(formatetc.cfFormat)
1215         && mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
1216 }
1217 
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1218 bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1219 {
1220     if (canConvertFromMime(formatetc, mimeData)) {
1221         QByteArray data;
1222         if (outFormats.value(getCf(formatetc)) == u"text/html") {
1223             // text/html is in wide chars on windows (compatible with mozillia)
1224             QString html = mimeData->html();
1225             // same code as in the text converter up above
1226             const QChar *u = html.unicode();
1227             QString res;
1228             const int s = html.length();
1229             int maxsize = s + s/40 + 3;
1230             res.resize(maxsize);
1231             int ri = 0;
1232             bool cr = false;
1233             for (int i=0; i < s; ++i) {
1234                 if (*u == u'\r')
1235                     cr = true;
1236                 else {
1237                     if (*u == u'\n' && !cr)
1238                         res[ri++] = u'\r';
1239                     cr = false;
1240                 }
1241                 res[ri++] = *u;
1242                 if (ri+3 >= maxsize) {
1243                     maxsize += maxsize/4;
1244                     res.resize(maxsize);
1245                 }
1246                 ++u;
1247             }
1248             res.truncate(ri);
1249             const int byteLength = res.length() * int(sizeof(ushort));
1250             QByteArray r(byteLength + 2, '\0');
1251             memcpy(r.data(), res.unicode(), size_t(byteLength));
1252             r[byteLength] = 0;
1253             r[byteLength+1] = 0;
1254             data = r;
1255         } else {
1256 #if QT_CONFIG(draganddrop)
1257             data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
1258 #endif // QT_CONFIG(draganddrop)
1259         }
1260         return setData(data, pmedium);
1261     }
1262     return false;
1263 }
1264 
formatsForMime(const QString & mimeType,const QMimeData * mimeData) const1265 QVector<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1266 {
1267     QVector<FORMATETC> formatetcs;
1268     const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
1269     if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
1270         formatetcs += setCf(mit.key());
1271     return formatetcs;
1272 }
1273 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1274 bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1275 {
1276     const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
1277     return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
1278 }
1279 
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1280 QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1281 {
1282     QVariant val;
1283     if (canConvertToMime(mimeType, pDataObj)) {
1284         QByteArray data = getData(inFormats.key(mimeType), pDataObj);
1285         if (!data.isEmpty()) {
1286             qCDebug(lcQpaMime) << __FUNCTION__;
1287             if (mimeType == u"text/html" && preferredType == QVariant::String) {
1288                 // text/html is in wide chars on windows (compatible with Mozilla)
1289                 val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
1290             } else {
1291                 val = data; // it should be enough to return the data and let QMimeData do the rest.
1292             }
1293         }
1294     }
1295     return val;
1296 }
1297 
mimeForFormat(const FORMATETC & formatetc) const1298 QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
1299 {
1300     return inFormats.value(getCf(formatetc));
1301 }
1302 
1303 
1304 class QLastResortMimes : public QWindowsMime
1305 {
1306 public:
1307 
1308     QLastResortMimes();
1309     // for converting from Qt
1310     bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1311     bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1312     QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1313 
1314     // for converting to Qt
1315     bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1316     QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const override;
1317     QString mimeForFormat(const FORMATETC &formatetc) const override;
1318 
1319 private:
1320     mutable QMap<int, QString> formats;
1321     static QStringList ianaTypes;
1322     static QStringList excludeList;
1323 };
1324 
1325 QStringList QLastResortMimes::ianaTypes;
1326 QStringList QLastResortMimes::excludeList;
1327 
QLastResortMimes()1328 QLastResortMimes::QLastResortMimes()
1329 {
1330     //MIME Media-Types
1331     if (ianaTypes.isEmpty()) {
1332         ianaTypes.append(QStringLiteral("application/"));
1333         ianaTypes.append(QStringLiteral("audio/"));
1334         ianaTypes.append(QStringLiteral("example/"));
1335         ianaTypes.append(QStringLiteral("image/"));
1336         ianaTypes.append(QStringLiteral("message/"));
1337         ianaTypes.append(QStringLiteral("model/"));
1338         ianaTypes.append(QStringLiteral("multipart/"));
1339         ianaTypes.append(QStringLiteral("text/"));
1340         ianaTypes.append(QStringLiteral("video/"));
1341     }
1342     //Types handled by other classes
1343     if (excludeList.isEmpty()) {
1344         excludeList.append(QStringLiteral("HTML Format"));
1345         excludeList.append(QStringLiteral("UniformResourceLocator"));
1346         excludeList.append(QStringLiteral("text/html"));
1347         excludeList.append(QStringLiteral("text/plain"));
1348         excludeList.append(QStringLiteral("text/uri-list"));
1349         excludeList.append(QStringLiteral("application/x-qt-image"));
1350         excludeList.append(QStringLiteral("application/x-color"));
1351     }
1352 }
1353 
canConvertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1354 bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1355 {
1356     // really check
1357 #if QT_CONFIG(draganddrop)
1358     return formatetc.tymed & TYMED_HGLOBAL
1359         && (formats.contains(formatetc.cfFormat)
1360         && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
1361 #else
1362     Q_UNUSED(mimeData);
1363     Q_UNUSED(formatetc);
1364     return formatetc.tymed & TYMED_HGLOBAL
1365         && formats.contains(formatetc.cfFormat);
1366 #endif // QT_CONFIG(draganddrop)
1367 }
1368 
convertFromMime(const FORMATETC & formatetc,const QMimeData * mimeData,STGMEDIUM * pmedium) const1369 bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1370 {
1371 #if QT_CONFIG(draganddrop)
1372     return canConvertFromMime(formatetc, mimeData)
1373         && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
1374 #else
1375     Q_UNUSED(mimeData);
1376     Q_UNUSED(formatetc);
1377     Q_UNUSED(pmedium);
1378     return false;
1379 #endif // QT_CONFIG(draganddrop)
1380 }
1381 
formatsForMime(const QString & mimeType,const QMimeData *) const1382 QVector<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
1383 {
1384     QVector<FORMATETC> formatetcs;
1385     auto mit = std::find(formats.begin(), formats.end(), mimeType);
1386     // register any other available formats
1387     if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
1388         mit = formats.insert(QWindowsMime::registerMimeType(mimeType), mimeType);
1389     if (mit != formats.end())
1390         formatetcs += setCf(mit.key());
1391 
1392     if (!formatetcs.isEmpty())
1393         qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1394     return formatetcs;
1395 }
1396 static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
1397 
isCustomMimeType(const QString & mimeType)1398 static bool isCustomMimeType(const QString &mimeType)
1399 {
1400     return mimeType.startsWith(QLatin1String(x_qt_windows_mime), Qt::CaseInsensitive);
1401 }
1402 
customMimeType(const QString & mimeType,int * lindex=nullptr)1403 static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
1404 {
1405     int len = sizeof(x_qt_windows_mime) - 1;
1406     int n = mimeType.lastIndexOf(u'\"') - len;
1407     QString ret = mimeType.mid(len, n);
1408 
1409     const int beginPos = mimeType.indexOf(u";index=");
1410     if (beginPos > -1) {
1411         const int endPos = mimeType.indexOf(u';', beginPos + 1);
1412         const int indexStartPos = beginPos + 7;
1413         if (lindex)
1414             *lindex = mimeType.midRef(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
1415     } else {
1416         if (lindex)
1417             *lindex = -1;
1418     }
1419     return ret;
1420 }
1421 
canConvertToMime(const QString & mimeType,IDataObject * pDataObj) const1422 bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1423 {
1424     if (isCustomMimeType(mimeType)) {
1425         // MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
1426         QString clipFormat = customMimeType(mimeType);
1427         const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1428         return canGetData(int(cf), pDataObj);
1429     }
1430     // if it is not in there then register it and see if we can get it
1431     const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1432     const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType);
1433     return canGetData(cf, pDataObj);
1434 }
1435 
convertToMime(const QString & mimeType,IDataObject * pDataObj,QVariant::Type preferredType) const1436 QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const
1437 {
1438     Q_UNUSED(preferredType);
1439     QVariant val;
1440     if (canConvertToMime(mimeType, pDataObj)) {
1441         QByteArray data;
1442         if (isCustomMimeType(mimeType)) {
1443             int lindex;
1444             QString clipFormat = customMimeType(mimeType, &lindex);
1445             const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1446             data = getData(int(cf), pDataObj, lindex);
1447         } else {
1448             const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1449             const int cf = mit != formats.cend() ? mit.key() : QWindowsMime::registerMimeType(mimeType);
1450             data = getData(cf, pDataObj);
1451         }
1452         if (!data.isEmpty())
1453             val = data; // it should be enough to return the data and let QMimeData do the rest.
1454     }
1455     return val;
1456 }
1457 
mimeForFormat(const FORMATETC & formatetc) const1458 QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
1459 {
1460     QString format = formats.value(getCf(formatetc));
1461     if (!format.isEmpty())
1462         return format;
1463 
1464     const QString clipFormat = QWindowsMimeConverter::clipboardFormatName(getCf(formatetc));
1465     if (!clipFormat.isEmpty()) {
1466 #if QT_CONFIG(draganddrop)
1467         if (QInternalMimeData::canReadData(clipFormat))
1468             format = clipFormat;
1469         else if((formatetc.cfFormat >= 0xC000)){
1470             //create the mime as custom. not registered.
1471             if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
1472                 //check if this is a mime type
1473                 bool ianaType = false;
1474                 int sz = ianaTypes.size();
1475                 for (int i = 0; i < sz; i++) {
1476                     if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
1477                         ianaType =  true;
1478                         break;
1479                     }
1480                 }
1481                 if (!ianaType)
1482                     format = QLatin1String(x_qt_windows_mime) + clipFormat + u'"';
1483                 else
1484                     format = clipFormat;
1485             }
1486         }
1487 #endif // QT_CONFIG(draganddrop)
1488     }
1489 
1490     return format;
1491 }
1492 
1493 /*!
1494     \class QWindowsMimeConverter
1495     \brief Manages the list of QWindowsMime instances.
1496     \internal
1497     \sa QWindowsMime
1498 */
1499 
1500 QWindowsMimeConverter::QWindowsMimeConverter() = default;
1501 
~QWindowsMimeConverter()1502 QWindowsMimeConverter::~QWindowsMimeConverter()
1503 {
1504     qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
1505 }
1506 
converterToMime(const QString & mimeType,IDataObject * pDataObj) const1507 QWindowsMime * QWindowsMimeConverter::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
1508 {
1509     ensureInitialized();
1510     for (int i = m_mimes.size()-1; i >= 0; --i) {
1511         if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
1512             return m_mimes.at(i);
1513     }
1514     return nullptr;
1515 }
1516 
allMimesForFormats(IDataObject * pDataObj) const1517 QStringList QWindowsMimeConverter::allMimesForFormats(IDataObject *pDataObj) const
1518 {
1519     qCDebug(lcQpaMime) << "QWindowsMime::allMimesForFormats()";
1520     ensureInitialized();
1521     QStringList formats;
1522     LPENUMFORMATETC FAR fmtenum;
1523     HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
1524 
1525     if (hr == NOERROR) {
1526         FORMATETC fmtetc;
1527         while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
1528             for (int i= m_mimes.size() - 1; i >= 0; --i) {
1529                 QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
1530                 if (!format.isEmpty() && !formats.contains(format)) {
1531                     formats += format;
1532                     if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
1533                         qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
1534                 }
1535             }
1536             // as documented in MSDN to avoid possible memleak
1537             if (fmtetc.ptd)
1538                 CoTaskMemFree(fmtetc.ptd);
1539         }
1540         fmtenum->Release();
1541     }
1542     qCDebug(lcQpaMime) << pDataObj << formats;
1543     return formats;
1544 }
1545 
converterFromMime(const FORMATETC & formatetc,const QMimeData * mimeData) const1546 QWindowsMime * QWindowsMimeConverter::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1547 {
1548     ensureInitialized();
1549     qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
1550     for (int i = m_mimes.size()-1; i >= 0; --i) {
1551         if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
1552             return m_mimes.at(i);
1553     }
1554     return nullptr;
1555 }
1556 
allFormatsForMime(const QMimeData * mimeData) const1557 QVector<FORMATETC> QWindowsMimeConverter::allFormatsForMime(const QMimeData *mimeData) const
1558 {
1559     ensureInitialized();
1560     QVector<FORMATETC> formatics;
1561 #if !QT_CONFIG(draganddrop)
1562     Q_UNUSED(mimeData);
1563 #else
1564     formatics.reserve(20);
1565     const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
1566     for (int f = 0; f < formats.size(); ++f) {
1567         for (int i = m_mimes.size() - 1; i >= 0; --i)
1568             formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
1569     }
1570 #endif // QT_CONFIG(draganddrop)
1571     return formatics;
1572 }
1573 
ensureInitialized() const1574 void QWindowsMimeConverter::ensureInitialized() const
1575 {
1576     if (m_mimes.isEmpty()) {
1577         m_mimes
1578 #ifndef QT_NO_IMAGEFORMAT_BMP
1579                 << new QWindowsMimeImage
1580 #endif //QT_NO_IMAGEFORMAT_BMP
1581                 << new QLastResortMimes
1582                 << new QWindowsMimeText << new QWindowsMimeURI
1583                 << new QWindowsMimeHtml << new QBuiltInMimes;
1584         m_internalMimeCount = m_mimes.size();
1585     }
1586 }
1587 
clipboardFormatName(int cf)1588 QString QWindowsMimeConverter::clipboardFormatName(int cf)
1589 {
1590     wchar_t buf[256] = {0};
1591     return GetClipboardFormatName(UINT(cf), buf, 255)
1592         ? QString::fromWCharArray(buf) : QString();
1593 }
1594 
convertToMime(const QStringList & mimeTypes,IDataObject * pDataObj,QVariant::Type preferredType,QString * formatIn) const1595 QVariant QWindowsMimeConverter::convertToMime(const QStringList &mimeTypes,
1596                                               IDataObject *pDataObj,
1597                                               QVariant::Type preferredType,
1598                                               QString *formatIn /* = 0 */) const
1599 {
1600     for (const QString &format : mimeTypes) {
1601         if (const QWindowsMime *converter = converterToMime(format, pDataObj)) {
1602             if (converter->canConvertToMime(format, pDataObj)) {
1603                 const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
1604                 if (dataV.isValid()) {
1605                     qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
1606                         << format << pDataObj << " returns " << dataV;
1607                     if (formatIn)
1608                         *formatIn = format;
1609                     return dataV;
1610                 }
1611             }
1612         }
1613     }
1614     qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType;
1615     return QVariant();
1616 }
1617 
registerMime(QWindowsMime * mime)1618 void QWindowsMimeConverter::registerMime(QWindowsMime *mime)
1619 {
1620     ensureInitialized();
1621     m_mimes.append(mime);
1622 }
1623 
1624 QT_END_NAMESPACE
1625