1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  */
10 
11 #include <Qt5Transferable.hxx>
12 
13 #include <comphelper/sequence.hxx>
14 #include <sal/log.hxx>
15 
16 #include <QtWidgets/QApplication>
17 
18 #include <Qt5Instance.hxx>
19 #include <Qt5Tools.hxx>
20 
21 #include <cassert>
22 
lcl_textMimeInfo(const OUString & rMimeString,bool & bHaveNoCharset,bool & bHaveUTF16,bool & bHaveUTF8)23 static bool lcl_textMimeInfo(const OUString& rMimeString, bool& bHaveNoCharset, bool& bHaveUTF16,
24                              bool& bHaveUTF8)
25 {
26     sal_Int32 nIndex = 0;
27     if (rMimeString.getToken(0, ';', nIndex) == "text/plain")
28     {
29         OUString aToken(rMimeString.getToken(0, ';', nIndex));
30         if (aToken == "charset=utf-16")
31             bHaveUTF16 = true;
32         else if (aToken == "charset=utf-8")
33             bHaveUTF8 = true;
34         else if (aToken.isEmpty())
35             bHaveNoCharset = true;
36         else // we just handle UTF-16 and UTF-8, everything else is "bytes"
37             return false;
38         return true;
39     }
40     return false;
41 }
42 
Qt5Transferable(const QMimeData * pMimeData)43 Qt5Transferable::Qt5Transferable(const QMimeData* pMimeData)
44     : m_pMimeData(pMimeData)
45     , m_bConvertFromLocale(false)
46 {
47     assert(pMimeData);
48 }
49 
getTransferDataFlavors()50 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::getTransferDataFlavors()
51 {
52     // it's just filled once, ever, so just try to get it without locking first
53     if (m_aMimeTypeSeq.hasElements())
54         return m_aMimeTypeSeq;
55 
56     // better safe then sorry; preventing broken usage
57     // DnD should not be shared and Clipboard access runs in the GUI thread
58     osl::MutexGuard aGuard(m_aMutex);
59     if (m_aMimeTypeSeq.hasElements())
60         return m_aMimeTypeSeq;
61 
62     QStringList aFormatList(m_pMimeData->formats());
63     // we might add the UTF-16 mime text variant later
64     const int nMimeTypeSeqSize = aFormatList.size() + 1;
65     bool bHaveNoCharset = false, bHaveUTF16 = false;
66     css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
67 
68     css::datatransfer::DataFlavor aFlavor;
69     int nMimeTypeCount = 0;
70 
71     for (const QString& rMimeType : aFormatList)
72     {
73         // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
74         if (rMimeType.indexOf('/') == -1)
75             continue;
76 
77         // gtk3 thinks it is not well defined - skip too
78         if (rMimeType == QStringLiteral("text/plain;charset=unicode"))
79             continue;
80 
81         // LO doesn't like 'text/plain', so we have to provide UTF-16
82         bool bIsNoCharset = false, bIsUTF16 = false, bIsUTF8 = false;
83         if (lcl_textMimeInfo(toOUString(rMimeType), bIsNoCharset, bIsUTF16, bIsUTF8))
84         {
85             bHaveNoCharset |= bIsNoCharset;
86             bHaveUTF16 |= bIsUTF16;
87             if (bIsUTF16)
88                 aFlavor.DataType = cppu::UnoType<OUString>::get();
89             else
90                 aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
91         }
92         else
93             aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
94 
95         aFlavor.MimeType = toOUString(rMimeType);
96         assert(nMimeTypeCount < nMimeTypeSeqSize);
97         aMimeTypeSeq[nMimeTypeCount] = aFlavor;
98         nMimeTypeCount++;
99     }
100 
101     m_bConvertFromLocale = bHaveNoCharset && !bHaveUTF16;
102     if (m_bConvertFromLocale)
103     {
104         aFlavor.MimeType = "text/plain;charset=utf-16";
105         aFlavor.DataType = cppu::UnoType<OUString>::get();
106         assert(nMimeTypeCount < nMimeTypeSeqSize);
107         aMimeTypeSeq[nMimeTypeCount] = aFlavor;
108         nMimeTypeCount++;
109     }
110 
111     aMimeTypeSeq.realloc(nMimeTypeCount);
112 
113     m_aMimeTypeSeq = aMimeTypeSeq;
114     return m_aMimeTypeSeq;
115 }
116 
117 sal_Bool SAL_CALL
isDataFlavorSupported(const css::datatransfer::DataFlavor & rFlavor)118 Qt5Transferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
119 {
120     const auto aSeq = getTransferDataFlavors();
121     return std::any_of(aSeq.begin(), aSeq.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
122         return rFlavor.MimeType == aFlavor.MimeType;
123     });
124 }
125 
126 css::uno::Any SAL_CALL
getTransferData(const css::datatransfer::DataFlavor & rFlavor)127 Qt5Transferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
128 {
129     css::uno::Any aAny;
130     if (!isDataFlavorSupported(rFlavor))
131         return aAny;
132 
133     if (rFlavor.MimeType == "text/plain;charset=utf-16")
134     {
135         OUString aString;
136         if (m_bConvertFromLocale)
137         {
138             QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain")));
139             aString = OUString(reinterpret_cast<const sal_Char*>(aByteData.data()),
140                                aByteData.size(), osl_getThreadTextEncoding());
141         }
142         else
143         {
144             QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
145             aString = OUString(reinterpret_cast<const sal_Unicode*>(aByteData.data()),
146                                aByteData.size() / 2);
147         }
148         aAny <<= aString;
149     }
150     else
151     {
152         QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
153         css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aByteData.data()),
154                                           aByteData.size());
155         aAny <<= aSeq;
156     }
157 
158     return aAny;
159 }
160 
Qt5ClipboardTransferable(const QClipboard::Mode aMode,const QMimeData * pMimeData)161 Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
162                                                    const QMimeData* pMimeData)
163     : Qt5Transferable(pMimeData)
164     , m_aMode(aMode)
165 {
166 }
167 
hasInFlightChanged() const168 bool Qt5ClipboardTransferable::hasInFlightChanged() const
169 {
170     const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
171     SAL_WARN_IF(bChanged, "vcl.qt5",
172                 "In flight clipboard change detected - broken clipboard read!");
173     return bChanged;
174 }
175 
176 css::uno::Any SAL_CALL
getTransferData(const css::datatransfer::DataFlavor & rFlavor)177 Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
178 {
179     css::uno::Any aAny;
180     auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
181     SolarMutexGuard g;
182     pSalInst->RunInMainThread([&, this]() {
183         if (!hasInFlightChanged())
184             aAny = Qt5Transferable::getTransferData(rFlavor);
185     });
186     return aAny;
187 }
188 
189 css::uno::Sequence<css::datatransfer::DataFlavor>
getTransferDataFlavors()190     SAL_CALL Qt5ClipboardTransferable::getTransferDataFlavors()
191 {
192     css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
193     auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
194     SolarMutexGuard g;
195     pSalInst->RunInMainThread([&, this]() {
196         if (!hasInFlightChanged())
197             aSeq = Qt5Transferable::getTransferDataFlavors();
198     });
199     return aSeq;
200 }
201 
202 sal_Bool SAL_CALL
isDataFlavorSupported(const css::datatransfer::DataFlavor & rFlavor)203 Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
204 {
205     bool bIsSupported = false;
206     auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
207     SolarMutexGuard g;
208     pSalInst->RunInMainThread([&, this]() {
209         if (!hasInFlightChanged())
210             bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
211     });
212     return bIsSupported;
213 }
214 
Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable> & xTrans)215 Qt5MimeData::Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
216     : m_aContents(xTrans)
217     , m_bHaveNoCharset(false)
218     , m_bHaveUTF8(false)
219 {
220     assert(xTrans.is());
221 }
222 
deepCopy(QMimeData ** const pMimeCopy) const223 bool Qt5MimeData::deepCopy(QMimeData** const pMimeCopy) const
224 {
225     if (!pMimeCopy)
226         return false;
227 
228     QMimeData* pMimeData = new QMimeData();
229     for (QString& format : formats())
230     {
231         QByteArray aData = data(format);
232         // Checking for custom MIME types
233         if (format.startsWith("application/x-qt"))
234         {
235             // Retrieving true format name
236             int indexBegin = format.indexOf('"') + 1;
237             int indexEnd = format.indexOf('"', indexBegin);
238             format = format.mid(indexBegin, indexEnd - indexBegin);
239         }
240         pMimeData->setData(format, aData);
241     }
242 
243     *pMimeCopy = pMimeData;
244     return true;
245 }
246 
formats() const247 QStringList Qt5MimeData::formats() const
248 {
249     if (!m_aMimeTypeList.isEmpty())
250         return m_aMimeTypeList;
251 
252     const css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
253         = m_aContents->getTransferDataFlavors();
254     QStringList aList;
255     bool bHaveUTF16 = false;
256 
257     for (const auto& rFlavor : aFormats)
258     {
259         aList << toQString(rFlavor.MimeType);
260         lcl_textMimeInfo(rFlavor.MimeType, m_bHaveNoCharset, bHaveUTF16, m_bHaveUTF8);
261     }
262 
263     // we provide a locale encoded and a UTF-8 variant, if missing
264     if (m_bHaveNoCharset || bHaveUTF16 || m_bHaveUTF8)
265     {
266         // if there is a text representation from LO point of view, it'll be UTF-16
267         assert(bHaveUTF16);
268         if (!m_bHaveUTF8)
269             aList << QStringLiteral("text/plain;charset=utf-8");
270         if (!m_bHaveNoCharset)
271             aList << QStringLiteral("text/plain");
272     }
273 
274     m_aMimeTypeList = aList;
275     return m_aMimeTypeList;
276 }
277 
retrieveData(const QString & mimeType,QVariant::Type) const278 QVariant Qt5MimeData::retrieveData(const QString& mimeType, QVariant::Type) const
279 {
280     if (!hasFormat(mimeType))
281         return QVariant();
282 
283     css::datatransfer::DataFlavor aFlavor;
284     aFlavor.MimeType = toOUString(mimeType);
285     aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
286 
287     bool bWantNoCharset = false, bWantUTF16 = false, bWantUTF8 = false;
288     if (lcl_textMimeInfo(aFlavor.MimeType, bWantNoCharset, bWantUTF16, bWantUTF8))
289     {
290         if ((bWantNoCharset && !m_bHaveNoCharset) || (bWantUTF8 && !m_bHaveUTF8))
291         {
292             aFlavor.MimeType = "text/plain;charset=utf-16";
293             aFlavor.DataType = cppu::UnoType<OUString>::get();
294         }
295         else if (bWantUTF16)
296             aFlavor.DataType = cppu::UnoType<OUString>::get();
297     }
298 
299     css::uno::Any aValue;
300 
301     try
302     {
303         // tdf#129809 take a reference in case m_aContents is replaced during this call
304         css::uno::Reference<com::sun::star::datatransfer::XTransferable> xCurrentContents(
305             m_aContents);
306         aValue = xCurrentContents->getTransferData(aFlavor);
307     }
308     catch (...)
309     {
310     }
311 
312     QByteArray aByteArray;
313     if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING)
314     {
315         OUString aString;
316         aValue >>= aString;
317 
318         if (bWantUTF8)
319         {
320             OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
321             aByteArray = QByteArray(reinterpret_cast<const char*>(aUTF8String.getStr()),
322                                     aUTF8String.getLength());
323         }
324         else if (bWantNoCharset)
325         {
326             OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
327             aByteArray = QByteArray(reinterpret_cast<const char*>(aLocaleString.getStr()),
328                                     aLocaleString.getLength());
329         }
330         else
331             return QVariant(toQString(aString));
332     }
333     else
334     {
335         css::uno::Sequence<sal_Int8> aData;
336         aValue >>= aData;
337         aByteArray
338             = QByteArray(reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength());
339     }
340     return QVariant::fromValue(aByteArray);
341 }
342 
hasFormat(const QString & mimeType) const343 bool Qt5MimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
344 
345 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
346