1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsClipboard.h"
7 #include <ole2.h>
8 #include <shlobj.h>
9 #include <intshcut.h>
10 
11 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
12 #include <shellapi.h>
13 
14 #include "nsArrayUtils.h"
15 #include "nsCOMPtr.h"
16 #include "nsDataObj.h"
17 #include "nsIClipboardOwner.h"
18 #include "nsString.h"
19 #include "nsNativeCharsetUtils.h"
20 #include "nsIFormatConverter.h"
21 #include "nsITransferable.h"
22 #include "nsCOMPtr.h"
23 #include "nsXPCOM.h"
24 #include "nsISupportsPrimitives.h"
25 #include "nsReadableUtils.h"
26 #include "nsUnicharUtils.h"
27 #include "nsPrimitiveHelpers.h"
28 #include "nsImageClipboard.h"
29 #include "nsIWidget.h"
30 #include "nsIComponentManager.h"
31 #include "nsWidgetsCID.h"
32 #include "nsCRT.h"
33 #include "nsNetUtil.h"
34 #include "nsIFileProtocolHandler.h"
35 #include "nsIOutputStream.h"
36 #include "nsEscape.h"
37 #include "nsIObserverService.h"
38 
39 using mozilla::LogLevel;
40 
41 static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard");
42 
43 // oddly, this isn't in the MSVC headers anywhere.
44 UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format");
45 UINT nsClipboard::CF_CUSTOMTYPES =
46     ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
47 
48 //-------------------------------------------------------------------------
49 //
50 // nsClipboard constructor
51 //
52 //-------------------------------------------------------------------------
nsClipboard()53 nsClipboard::nsClipboard() : nsBaseClipboard() {
54   mIgnoreEmptyNotification = false;
55   mWindow = nullptr;
56 
57   // Register for a shutdown notification so that we can flush data
58   // to the OS clipboard.
59   nsCOMPtr<nsIObserverService> observerService =
60       do_GetService("@mozilla.org/observer-service;1");
61   if (observerService) {
62     observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
63                                  PR_FALSE);
64   }
65 }
66 
67 //-------------------------------------------------------------------------
68 // nsClipboard destructor
69 //-------------------------------------------------------------------------
~nsClipboard()70 nsClipboard::~nsClipboard() {}
71 
NS_IMPL_ISUPPORTS_INHERITED(nsClipboard,nsBaseClipboard,nsIObserver)72 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
73 
74 NS_IMETHODIMP
75 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
76                      const char16_t* aData) {
77   // This will be called on shutdown.
78   ::OleFlushClipboard();
79   ::CloseClipboard();
80 
81   return NS_OK;
82 }
83 
84 //-------------------------------------------------------------------------
GetFormat(const char * aMimeStr,bool aMapHTMLMime)85 UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
86   UINT format;
87 
88   if (strcmp(aMimeStr, kTextMime) == 0) {
89     format = CF_TEXT;
90   } else if (strcmp(aMimeStr, kUnicodeMime) == 0) {
91     format = CF_UNICODETEXT;
92   } else if (strcmp(aMimeStr, kRTFMime) == 0) {
93     format = ::RegisterClipboardFormat(L"Rich Text Format");
94   } else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
95              strcmp(aMimeStr, kJPGImageMime) == 0 ||
96              strcmp(aMimeStr, kPNGImageMime) == 0) {
97     format = CF_DIBV5;
98   } else if (strcmp(aMimeStr, kFileMime) == 0 ||
99              strcmp(aMimeStr, kFilePromiseMime) == 0) {
100     format = CF_HDROP;
101   } else if ((strcmp(aMimeStr, kNativeHTMLMime) == 0) ||
102              (aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)) {
103     format = CF_HTML;
104   } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
105     format = CF_CUSTOMTYPES;
106   } else {
107     format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
108   }
109 
110   return format;
111 }
112 
113 //-------------------------------------------------------------------------
CreateNativeDataObject(nsITransferable * aTransferable,IDataObject ** aDataObj,nsIURI * uri)114 nsresult nsClipboard::CreateNativeDataObject(nsITransferable* aTransferable,
115                                              IDataObject** aDataObj,
116                                              nsIURI* uri) {
117   if (nullptr == aTransferable) {
118     return NS_ERROR_FAILURE;
119   }
120 
121   // Create our native DataObject that implements
122   // the OLE IDataObject interface
123   nsDataObj* dataObj = new nsDataObj(uri);
124 
125   if (!dataObj) {
126     return NS_ERROR_OUT_OF_MEMORY;
127   }
128 
129   dataObj->AddRef();
130 
131   // Now set it up with all the right data flavors & enums
132   nsresult res = SetupNativeDataObject(aTransferable, dataObj);
133   if (NS_OK == res) {
134     *aDataObj = dataObj;
135   } else {
136     delete dataObj;
137   }
138   return res;
139 }
140 
141 //-------------------------------------------------------------------------
SetupNativeDataObject(nsITransferable * aTransferable,IDataObject * aDataObj)142 nsresult nsClipboard::SetupNativeDataObject(nsITransferable* aTransferable,
143                                             IDataObject* aDataObj) {
144   if (nullptr == aTransferable || nullptr == aDataObj) {
145     return NS_ERROR_FAILURE;
146   }
147 
148   nsDataObj* dObj = static_cast<nsDataObj*>(aDataObj);
149 
150   // Now give the Transferable to the DataObject
151   // for getting the data out of it
152   dObj->SetTransferable(aTransferable);
153 
154   // Get the transferable list of data flavors
155   nsCOMPtr<nsIArray> dfList;
156   aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList));
157 
158   // Walk through flavors that contain data and register them
159   // into the DataObj as supported flavors
160   uint32_t i;
161   uint32_t cnt;
162   dfList->GetLength(&cnt);
163   for (i = 0; i < cnt; i++) {
164     nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(dfList, i);
165     if (currentFlavor) {
166       nsCString flavorStr;
167       currentFlavor->ToString(getter_Copies(flavorStr));
168       // When putting data onto the clipboard, we want to maintain kHTMLMime
169       // ("text/html") and not map it to CF_HTML here since this will be done
170       // below.
171       UINT format = GetFormat(flavorStr.get(), false);
172 
173       // Now tell the native IDataObject about both our mime type and
174       // the native data format
175       FORMATETC fe;
176       SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
177       dObj->AddDataFlavor(flavorStr.get(), &fe);
178 
179       // Do various things internal to the implementation, like map one
180       // flavor to another or add additional flavors based on what's required
181       // for the win32 impl.
182       if (flavorStr.EqualsLiteral(kUnicodeMime)) {
183         // if we find text/unicode, also advertise text/plain (which we will
184         // convert on our own in nsDataObj::GetText().
185         FORMATETC textFE;
186         SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
187         dObj->AddDataFlavor(kTextMime, &textFE);
188       } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
189         // if we find text/html, also advertise win32's html flavor (which we
190         // will convert on our own in nsDataObj::GetText().
191         FORMATETC htmlFE;
192         SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
193         dObj->AddDataFlavor(kHTMLMime, &htmlFE);
194       } else if (flavorStr.EqualsLiteral(kURLMime)) {
195         // if we're a url, in addition to also being text, we need to register
196         // the "file" flavors so that the win32 shell knows to create an
197         // internet shortcut when it sees one of these beasts.
198         FORMATETC shortcutFE;
199         SET_FORMATETC(shortcutFE,
200                       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
201                       DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
202         dObj->AddDataFlavor(kURLMime, &shortcutFE);
203         SET_FORMATETC(shortcutFE,
204                       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
205                       DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
206         dObj->AddDataFlavor(kURLMime, &shortcutFE);
207         SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
208                       0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
209         dObj->AddDataFlavor(kURLMime, &shortcutFE);
210         SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
211                       DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
212         dObj->AddDataFlavor(kURLMime, &shortcutFE);
213         SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
214                       DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
215         dObj->AddDataFlavor(kURLMime, &shortcutFE);
216       } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
217                  flavorStr.EqualsLiteral(kJPEGImageMime) ||
218                  flavorStr.EqualsLiteral(kJPGImageMime) ||
219                  flavorStr.EqualsLiteral(kGIFImageMime) ||
220                  flavorStr.EqualsLiteral(kNativeImageMime)) {
221         // if we're an image, register the native bitmap flavor
222         FORMATETC imageFE;
223         // Add DIBv5
224         SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
225         dObj->AddDataFlavor(flavorStr.get(), &imageFE);
226         // Add DIBv3
227         SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
228         dObj->AddDataFlavor(flavorStr.get(), &imageFE);
229       } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
230         // if we're a file promise flavor, also register the
231         // CFSTR_PREFERREDDROPEFFECT format.  The data object
232         // returns a value of DROPEFFECTS_MOVE to the drop target
233         // when it asks for the value of this format.  This causes
234         // the file to be moved from the temporary location instead
235         // of being copied.  The right thing to do here is to call
236         // SetData() on the data object and set the value of this format
237         // to DROPEFFECTS_MOVE on this particular data object.  But,
238         // since all the other clipboard formats follow the model of setting
239         // data on the data object only when the drop object calls GetData(),
240         // I am leaving this format's value hard coded in the data object.
241         // We can change this if other consumers of this format get added to
242         // this codebase and they need different values.
243         FORMATETC shortcutFE;
244         SET_FORMATETC(shortcutFE,
245                       ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
246                       DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
247         dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
248       }
249     }
250   }
251 
252   return NS_OK;
253 }
254 
255 //-------------------------------------------------------------------------
SetNativeClipboardData(int32_t aWhichClipboard)256 NS_IMETHODIMP nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
257   if (aWhichClipboard != kGlobalClipboard) {
258     return NS_ERROR_FAILURE;
259   }
260 
261   mIgnoreEmptyNotification = true;
262 
263   // make sure we have a good transferable
264   if (nullptr == mTransferable) {
265     return NS_ERROR_FAILURE;
266   }
267 
268   IDataObject* dataObj;
269   if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj,
270                                           nullptr))) {  // this add refs dataObj
271     ::OleSetClipboard(dataObj);
272     dataObj->Release();
273   } else {
274     // Clear the native clipboard
275     ::OleSetClipboard(nullptr);
276   }
277 
278   mIgnoreEmptyNotification = false;
279 
280   return NS_OK;
281 }
282 
283 //-------------------------------------------------------------------------
GetGlobalData(HGLOBAL aHGBL,void ** aData,uint32_t * aLen)284 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
285                                     uint32_t* aLen) {
286   // Allocate a new memory buffer and copy the data from global memory.
287   // Recall that win98 allocates to nearest DWORD boundary. As a safety
288   // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
289   // and null them out to ensure that all of our NS_strlen calls will succeed.
290   // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
291   // a full NUL char16_t when |*aLen| is odd.
292   nsresult result = NS_ERROR_FAILURE;
293   if (aHGBL != nullptr) {
294     LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
295     CheckedInt<uint32_t> allocSize = CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
296     if (!allocSize.isValid()) {
297       return NS_ERROR_INVALID_ARG;
298     }
299     char* data = static_cast<char*>(malloc(allocSize.value()));
300     if (data) {
301       uint32_t size = allocSize.value() - 3;
302       memcpy(data, lpStr, size);
303       // null terminate for safety
304       data[size] = data[size + 1] = data[size + 2] = '\0';
305 
306       GlobalUnlock(aHGBL);
307       *aData = data;
308       *aLen = size;
309 
310       result = NS_OK;
311     }
312   } else {
313     // We really shouldn't ever get here
314     // but just in case
315     *aData = nullptr;
316     *aLen = 0;
317     LPVOID lpMsgBuf;
318 
319     FormatMessageW(
320         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
321         GetLastError(),
322         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // Default language
323         (LPWSTR)&lpMsgBuf, 0, nullptr);
324 
325     // Display the string.
326     MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
327                 MB_OK | MB_ICONINFORMATION);
328 
329     // Free the buffer.
330     LocalFree(lpMsgBuf);
331   }
332 
333   return result;
334 }
335 
336 //-------------------------------------------------------------------------
GetNativeDataOffClipboard(nsIWidget * aWidget,UINT,UINT aFormat,void ** aData,uint32_t * aLen)337 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
338                                                 UINT /*aIndex*/, UINT aFormat,
339                                                 void** aData, uint32_t* aLen) {
340   HGLOBAL hglb;
341   nsresult result = NS_ERROR_FAILURE;
342 
343   HWND nativeWin = nullptr;
344   if (::OpenClipboard(nativeWin)) {
345     hglb = ::GetClipboardData(aFormat);
346     result = GetGlobalData(hglb, aData, aLen);
347     ::CloseClipboard();
348   }
349   return result;
350 }
351 
DisplayErrCode(HRESULT hres)352 static void DisplayErrCode(HRESULT hres) {
353 #if defined(DEBUG_rods) || defined(DEBUG_pinkerton)
354   if (hres == E_INVALIDARG) {
355     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
356   } else if (hres == E_UNEXPECTED) {
357     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
358   } else if (hres == E_OUTOFMEMORY) {
359     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
360   } else if (hres == DV_E_LINDEX) {
361     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
362   } else if (hres == DV_E_FORMATETC) {
363     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
364   } else if (hres == DV_E_TYMED) {
365     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
366   } else if (hres == DV_E_DVASPECT) {
367     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
368   } else if (hres == OLE_E_NOTRUNNING) {
369     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
370   } else if (hres == STG_E_MEDIUMFULL) {
371     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
372   } else if (hres == DV_E_CLIPFORMAT) {
373     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
374   } else if (hres == S_OK) {
375     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
376   } else {
377     MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
378             ("****** DisplayErrCode 0x%X\n", hres));
379   }
380 #endif
381 }
382 
383 //-------------------------------------------------------------------------
FillSTGMedium(IDataObject * aDataObject,UINT aFormat,LPFORMATETC pFE,LPSTGMEDIUM pSTM,DWORD aTymed)384 static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
385                              LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) {
386   SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
387 
388   // Starting by querying for the data to see if we can get it as from global
389   // memory
390   HRESULT hres = S_FALSE;
391   hres = aDataObject->QueryGetData(pFE);
392   DisplayErrCode(hres);
393   if (S_OK == hres) {
394     hres = aDataObject->GetData(pFE, pSTM);
395     DisplayErrCode(hres);
396   }
397   return hres;
398 }
399 
400 //-------------------------------------------------------------------------
401 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
402 // an image encoder (e.g. image/png).
403 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
GetNativeDataOffClipboard(IDataObject * aDataObject,UINT aIndex,UINT aFormat,const char * aMIMEImageFormat,void ** aData,uint32_t * aLen)404 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
405                                                 UINT aIndex, UINT aFormat,
406                                                 const char* aMIMEImageFormat,
407                                                 void** aData, uint32_t* aLen) {
408   nsresult result = NS_ERROR_FAILURE;
409   *aData = nullptr;
410   *aLen = 0;
411 
412   if (!aDataObject) {
413     return result;
414   }
415 
416   UINT format = aFormat;
417   HRESULT hres = S_FALSE;
418 
419   // XXX at the moment we only support global memory transfers
420   // It is here where we will add support for native images
421   // and IStream
422   FORMATETC fe;
423   STGMEDIUM stm;
424   hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
425 
426   // Currently this is only handling TYMED_HGLOBAL data
427   // For Text, Dibs, Files, and generic data (like HTML)
428   if (S_OK == hres) {
429     static CLIPFORMAT fileDescriptorFlavorA =
430         ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
431     static CLIPFORMAT fileDescriptorFlavorW =
432         ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
433     static CLIPFORMAT fileFlavor =
434         ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
435     static CLIPFORMAT preferredDropEffect =
436         ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
437 
438     switch (stm.tymed) {
439       case TYMED_HGLOBAL: {
440         switch (fe.cfFormat) {
441           case CF_TEXT: {
442             // Get the data out of the global data handle. The size we
443             // return should not include the null because the other
444             // platforms don't use nulls, so just return the length we get
445             // back from strlen(), since we know CF_TEXT is null
446             // terminated. Recall that GetGlobalData() returns the size of
447             // the allocated buffer, not the size of the data (on 98, these
448             // are not the same) so we can't use that.
449             uint32_t allocLen = 0;
450             if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
451               *aLen = strlen(reinterpret_cast<char*>(*aData));
452               result = NS_OK;
453             }
454           } break;
455 
456           case CF_UNICODETEXT: {
457             // Get the data out of the global data handle. The size we
458             // return should not include the null because the other
459             // platforms don't use nulls, so just return the length we get
460             // back from strlen(), since we know CF_UNICODETEXT is null
461             // terminated. Recall that GetGlobalData() returns the size of
462             // the allocated buffer, not the size of the data (on 98, these
463             // are not the same) so we can't use that.
464             uint32_t allocLen = 0;
465             if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
466               *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
467               result = NS_OK;
468             }
469           } break;
470 
471           case CF_DIBV5:
472             if (aMIMEImageFormat) {
473               uint32_t allocLen = 0;
474               unsigned char* clipboardData;
475               if (NS_SUCCEEDED(GetGlobalData(
476                       stm.hGlobal, (void**)&clipboardData, &allocLen))) {
477                 nsImageFromClipboard converter;
478                 nsIInputStream* inputStream;
479                 converter.GetEncodedImageStream(
480                     clipboardData, aMIMEImageFormat,
481                     &inputStream);  // addrefs for us, don't release
482                 if (inputStream) {
483                   *aData = inputStream;
484                   *aLen = sizeof(nsIInputStream*);
485                   result = NS_OK;
486                 }
487               }
488             }
489             break;
490 
491           case CF_HDROP: {
492             // in the case of a file drop, multiple files are stashed within a
493             // single data object. In order to match mozilla's D&D apis, we
494             // just pull out the file at the requested index, pretending as
495             // if there really are multiple drag items.
496             HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
497 
498             UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
499             NS_ASSERTION(numFiles > 0,
500                          "File drop flavor, but no files...hmmmm");
501             NS_ASSERTION(aIndex < numFiles,
502                          "Asked for a file index out of range of list");
503             if (numFiles > 0) {
504               UINT fileNameLen =
505                   ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
506               wchar_t* buffer = reinterpret_cast<wchar_t*>(
507                   moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
508               if (buffer) {
509                 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
510                 *aData = buffer;
511                 *aLen = fileNameLen * sizeof(char16_t);
512                 result = NS_OK;
513               } else {
514                 result = NS_ERROR_OUT_OF_MEMORY;
515               }
516             }
517             GlobalUnlock(stm.hGlobal);
518 
519           } break;
520 
521           default: {
522             if (fe.cfFormat == fileDescriptorFlavorA ||
523                 fe.cfFormat == fileDescriptorFlavorW ||
524                 fe.cfFormat == fileFlavor) {
525               NS_WARNING(
526                   "Mozilla doesn't yet understand how to read this type of "
527                   "file flavor");
528             } else {
529               // Get the data out of the global data handle. The size we
530               // return should not include the null because the other
531               // platforms don't use nulls, so just return the length we get
532               // back from strlen(), since we know CF_UNICODETEXT is null
533               // terminated. Recall that GetGlobalData() returns the size of
534               // the allocated buffer, not the size of the data (on 98, these
535               // are not the same) so we can't use that.
536               //
537               // NOTE: we are assuming that anything that falls into this
538               //        default case is unicode. As we start to get more
539               //        kinds of binary data, this may become an incorrect
540               //        assumption. Stay tuned.
541               uint32_t allocLen = 0;
542               if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
543                 if (fe.cfFormat == CF_HTML) {
544                   // CF_HTML is actually UTF8, not unicode, so disregard the
545                   // assumption above. We have to check the header for the
546                   // actual length, and we'll do that in FindPlatformHTML().
547                   // For now, return the allocLen. This case is mostly to
548                   // ensure we don't try to call strlen on the buffer.
549                   *aLen = allocLen;
550                 } else if (fe.cfFormat == CF_CUSTOMTYPES) {
551                   // Binary data
552                   *aLen = allocLen;
553                 } else if (fe.cfFormat == preferredDropEffect) {
554                   // As per the MSDN doc entitled: "Shell Clipboard Formats"
555                   // CFSTR_PREFERREDDROPEFFECT should return a DWORD
556                   // Reference:
557                   // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
558                   NS_ASSERTION(
559                       allocLen == sizeof(DWORD),
560                       "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
561                   *aLen = allocLen;
562                 } else {
563                   *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
564                           sizeof(char16_t);
565                 }
566                 result = NS_OK;
567               }
568             }
569           } break;
570         }  // switch
571       } break;
572 
573       case TYMED_GDI: {
574 #ifdef DEBUG
575         MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
576                 ("*********************** TYMED_GDI\n"));
577 #endif
578       } break;
579 
580       default:
581         break;
582     }  // switch
583 
584     ReleaseStgMedium(&stm);
585   }
586 
587   return result;
588 }
589 
590 //-------------------------------------------------------------------------
GetDataFromDataObject(IDataObject * aDataObject,UINT anIndex,nsIWidget * aWindow,nsITransferable * aTransferable)591 nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
592                                             UINT anIndex, nsIWidget* aWindow,
593                                             nsITransferable* aTransferable) {
594   // make sure we have a good transferable
595   if (!aTransferable) {
596     return NS_ERROR_INVALID_ARG;
597   }
598 
599   nsresult res = NS_ERROR_FAILURE;
600 
601   // get flavor list that includes all flavors that can be written (including
602   // ones obtained through conversion)
603   nsCOMPtr<nsIArray> flavorList;
604   res = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
605   if (NS_FAILED(res)) {
606     return NS_ERROR_FAILURE;
607   }
608 
609   // Walk through flavors and see which flavor is on the clipboard them on the
610   // native clipboard,
611   uint32_t i;
612   uint32_t cnt;
613   flavorList->GetLength(&cnt);
614   for (i = 0; i < cnt; i++) {
615     nsCOMPtr<nsISupportsCString> currentFlavor =
616         do_QueryElementAt(flavorList, i);
617     if (currentFlavor) {
618       nsCString flavorStr;
619       currentFlavor->ToString(getter_Copies(flavorStr));
620       UINT format = GetFormat(flavorStr.get());
621 
622       // Try to get the data using the desired flavor. This might fail, but all
623       // is not lost.
624       void* data = nullptr;
625       uint32_t dataLen = 0;
626       bool dataFound = false;
627       if (nullptr != aDataObject) {
628         if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
629                                                    flavorStr.get(), &data,
630                                                    &dataLen))) {
631           dataFound = true;
632         }
633       } else if (nullptr != aWindow) {
634         if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
635                                                    &data, &dataLen))) {
636           dataFound = true;
637         }
638       }
639 
640       // This is our second chance to try to find some data, having not found it
641       // when directly asking for the flavor. Let's try digging around in other
642       // flavors to help satisfy our craving for data.
643       if (!dataFound) {
644         if (flavorStr.EqualsLiteral(kUnicodeMime)) {
645           dataFound =
646               FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
647         } else if (flavorStr.EqualsLiteral(kURLMime)) {
648           // drags from other windows apps expose the native
649           // CFSTR_INETURL{A,W} flavor
650           dataFound =
651               FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
652           if (!dataFound) {
653             dataFound =
654                 FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
655           }
656         }
657       }  // if we try one last ditch effort to find our data
658 
659       // Hopefully by this point we've found it and can go about our business
660       if (dataFound) {
661         nsCOMPtr<nsISupports> genericDataWrapper;
662         if (flavorStr.EqualsLiteral(kFileMime)) {
663           // we have a file path in |data|. Create an nsLocalFile object.
664           nsDependentString filepath(reinterpret_cast<char16_t*>(data));
665           nsCOMPtr<nsIFile> file;
666           if (NS_SUCCEEDED(
667                   NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
668             genericDataWrapper = do_QueryInterface(file);
669           }
670           free(data);
671         } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
672           uint32_t dummy;
673           // the editor folks want CF_HTML exactly as it's on the clipboard, no
674           // conversions, no fancy stuff. Pull it off the clipboard, stuff it
675           // into a wrapper and hand it back to them.
676           if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
677             nsPrimitiveHelpers::CreatePrimitiveForData(
678                 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
679           } else {
680             free(data);
681             continue;  // something wrong with this flavor, keep looking for
682                        // other data
683           }
684           free(data);
685         } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
686           uint32_t startOfData = 0;
687           // The JS folks want CF_HTML exactly as it is on the clipboard, but
688           // minus the CF_HTML header index information.
689           // It also needs to be converted to UTF16 and have linebreaks changed.
690           if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
691                                &dataLen)) {
692             dataLen -= startOfData;
693             nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
694                 static_cast<char*>(data) + startOfData, &dataLen,
695                 getter_AddRefs(genericDataWrapper));
696           } else {
697             free(data);
698             continue;  // something wrong with this flavor, keep looking for
699                        // other data
700           }
701           free(data);
702         } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
703                    flavorStr.EqualsLiteral(kJPGImageMime) ||
704                    flavorStr.EqualsLiteral(kPNGImageMime)) {
705           nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
706           genericDataWrapper = do_QueryInterface(imageStream);
707           NS_IF_RELEASE(imageStream);
708         } else {
709           // Treat custom types as a string of bytes.
710           if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
711             // we probably have some form of text. The DOM only wants LF, so
712             // convert from Win32 line endings to DOM line endings.
713             int32_t signedLen = static_cast<int32_t>(dataLen);
714             nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &data,
715                                                                &signedLen);
716             dataLen = signedLen;
717 
718             if (flavorStr.EqualsLiteral(kRTFMime)) {
719               // RTF on Windows is known to sometimes deliver an extra null
720               // byte.
721               if (dataLen > 0 &&
722                   static_cast<char*>(data)[dataLen - 1] == '\0') {
723                 dataLen--;
724               }
725             }
726           }
727 
728           nsPrimitiveHelpers::CreatePrimitiveForData(
729               flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
730           free(data);
731         }
732 
733         NS_ASSERTION(genericDataWrapper,
734                      "About to put null data into the transferable");
735         aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper,
736                                        dataLen);
737         res = NS_OK;
738 
739         // we found one, get out of the loop
740         break;
741       }
742     }
743   }  // foreach flavor
744 
745   return res;
746 }
747 
748 //
749 // FindPlatformHTML
750 //
751 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
752 // as-is.
753 //
FindPlatformHTML(IDataObject * inDataObject,UINT inIndex,void ** outData,uint32_t * outStartOfData,uint32_t * outDataLen)754 bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
755                                     void** outData, uint32_t* outStartOfData,
756                                     uint32_t* outDataLen) {
757   // Reference: MSDN doc entitled "HTML Clipboard Format"
758   // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
759   // CF_HTML is UTF8, not unicode. We also can't rely on it being
760   // null-terminated so we have to check the CF_HTML header for the correct
761   // length. The length we return is the bytecount from the beginning of the
762   // selected data to the end of the selected data, without the null
763   // termination. Because it's UTF8, we're guaranteed the header is ASCII.
764 
765   if (!outData || !*outData) {
766     return false;
767   }
768 
769   char version[8] = {0};
770   int32_t startOfData = 0;
771   int32_t endOfData = 0;
772   int numFound =
773       sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
774              &startOfData, &endOfData);
775 
776   if (numFound != 3 || startOfData < -1 || endOfData < -1) {
777     return false;
778   }
779 
780   // Fixup the start and end markers if they have no context (set to -1)
781   if (startOfData == -1) {
782     startOfData = 0;
783   }
784   if (endOfData == -1) {
785     endOfData = *outDataLen;
786   }
787 
788   // Make sure we were passed sane values within our buffer size.
789   // (Note that we've handled all cases of negative endOfData above, so we can
790   // safely cast it to be unsigned here.)
791   if (!endOfData || startOfData >= endOfData ||
792       static_cast<uint32_t>(endOfData) > *outDataLen) {
793     return false;
794   }
795 
796   // We want to return the buffer not offset by startOfData because it will be
797   // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
798   // in CF_HTML format.
799 
800   // We return the byte offset from the start of the data buffer to where the
801   // HTML data starts. The caller might want to extract the HTML only.
802   *outStartOfData = startOfData;
803   *outDataLen = endOfData;
804   return true;
805 }
806 
807 //
808 // FindUnicodeFromPlainText
809 //
810 // we are looking for text/unicode and we failed to find it on the clipboard
811 // first, try again with text/plain. If that is present, convert it to unicode.
812 //
FindUnicodeFromPlainText(IDataObject * inDataObject,UINT inIndex,void ** outData,uint32_t * outDataLen)813 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
814                                             UINT inIndex, void** outData,
815                                             uint32_t* outDataLen) {
816   // we are looking for text/unicode and we failed to find it on the clipboard
817   // first, try again with text/plain. If that is present, convert it to
818   // unicode.
819   nsresult rv =
820       GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime),
821                                 nullptr, outData, outDataLen);
822   if (NS_FAILED(rv) || !*outData) {
823     return false;
824   }
825 
826   const char* castedText = static_cast<char*>(*outData);
827   nsAutoString tmp;
828   rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
829                               tmp);
830   if (NS_FAILED(rv)) {
831     return false;
832   }
833 
834   // out with the old, in with the new
835   free(*outData);
836   *outData = ToNewUnicode(tmp);
837   *outDataLen = tmp.Length() * sizeof(char16_t);
838 
839   return true;
840 
841 }  // FindUnicodeFromPlainText
842 
843 //
844 // FindURLFromLocalFile
845 //
846 // we are looking for a URL and couldn't find it, try again with looking for
847 // a local file. If we have one, it may either be a normal file or an internet
848 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
849 // in the local file case).
850 //
FindURLFromLocalFile(IDataObject * inDataObject,UINT inIndex,void ** outData,uint32_t * outDataLen)851 bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
852                                         void** outData, uint32_t* outDataLen) {
853   bool dataFound = false;
854 
855   nsresult loadResult =
856       GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
857                                 nullptr, outData, outDataLen);
858   if (NS_SUCCEEDED(loadResult) && *outData) {
859     // we have a file path in |data|. Is it an internet shortcut or a normal
860     // file?
861     const nsDependentString filepath(static_cast<char16_t*>(*outData));
862     nsCOMPtr<nsIFile> file;
863     nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
864     if (NS_FAILED(rv)) {
865       free(*outData);
866       return dataFound;
867     }
868 
869     if (IsInternetShortcut(filepath)) {
870       free(*outData);
871       nsAutoCString url;
872       ResolveShortcut(file, url);
873       if (!url.IsEmpty()) {
874         // convert it to unicode and pass it out
875         NS_ConvertUTF8toUTF16 urlString(url);
876         // the internal mozilla URL format, text/x-moz-url, contains
877         // URL\ntitle.  We can guess the title from the file's name.
878         nsAutoString title;
879         file->GetLeafName(title);
880         // We rely on IsInternetShortcut check that file has a .url extension.
881         title.SetLength(title.Length() - 4);
882         if (title.IsEmpty()) {
883           title = urlString;
884         }
885         *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + title);
886         *outDataLen =
887             NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
888 
889         dataFound = true;
890       }
891     } else {
892       // we have a normal file, use some Necko objects to get our file path
893       nsAutoCString urlSpec;
894       NS_GetURLSpecFromFile(file, urlSpec);
895 
896       // convert it to unicode and pass it out
897       free(*outData);
898       *outData = UTF8ToNewUnicode(urlSpec);
899       *outDataLen =
900           NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
901       dataFound = true;
902     }  // else regular file
903   }
904 
905   return dataFound;
906 }  // FindURLFromLocalFile
907 
908 //
909 // FindURLFromNativeURL
910 //
911 // we are looking for a URL and couldn't find it using our internal
912 // URL flavor, so look for it using the native URL flavor,
913 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
914 //
FindURLFromNativeURL(IDataObject * inDataObject,UINT inIndex,void ** outData,uint32_t * outDataLen)915 bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
916                                         void** outData, uint32_t* outDataLen) {
917   bool dataFound = false;
918 
919   void* tempOutData = nullptr;
920   uint32_t tempDataLen = 0;
921 
922   nsresult loadResult = GetNativeDataOffClipboard(
923       inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
924       &tempOutData, &tempDataLen);
925   if (NS_SUCCEEDED(loadResult) && tempOutData) {
926     nsDependentString urlString(static_cast<char16_t*>(tempOutData));
927     // the internal mozilla URL format, text/x-moz-url, contains
928     // URL\ntitle.  Since we don't actually have a title here,
929     // just repeat the URL to fake it.
930     *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString);
931     *outDataLen =
932         NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
933     free(tempOutData);
934     dataFound = true;
935   } else {
936     loadResult = GetNativeDataOffClipboard(
937         inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
938         nullptr, &tempOutData, &tempDataLen);
939     if (NS_SUCCEEDED(loadResult) && tempOutData) {
940       // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
941       // CF_TEXT which is by definition ANSI encoded.
942       nsCString urlUnescapedA;
943       bool unescaped =
944           NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
945                          esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
946 
947       nsString urlString;
948       if (unescaped) {
949         NS_CopyNativeToUnicode(urlUnescapedA, urlString);
950       } else {
951         NS_CopyNativeToUnicode(
952             nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
953             urlString);
954       }
955 
956       // the internal mozilla URL format, text/x-moz-url, contains
957       // URL\ntitle.  Since we don't actually have a title here,
958       // just repeat the URL to fake it.
959       *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString);
960       *outDataLen =
961           NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
962       free(tempOutData);
963       dataFound = true;
964     }
965   }
966 
967   return dataFound;
968 }  // FindURLFromNativeURL
969 
970 //
971 // ResolveShortcut
972 //
ResolveShortcut(nsIFile * aFile,nsACString & outURL)973 void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
974   nsCOMPtr<nsIFileProtocolHandler> fph;
975   nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
976   if (NS_FAILED(rv)) {
977     return;
978   }
979 
980   nsCOMPtr<nsIURI> uri;
981   rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
982   if (NS_FAILED(rv)) {
983     return;
984   }
985 
986   uri->GetSpec(outURL);
987 }  // ResolveShortcut
988 
989 //
990 // IsInternetShortcut
991 //
992 // A file is an Internet Shortcut if it ends with .URL
993 //
IsInternetShortcut(const nsAString & inFileName)994 bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
995   return StringEndsWith(inFileName, NS_LITERAL_STRING(".url"),
996                         nsCaseInsensitiveStringComparator());
997 }  // IsInternetShortcut
998 
999 //-------------------------------------------------------------------------
1000 NS_IMETHODIMP
GetNativeClipboardData(nsITransferable * aTransferable,int32_t aWhichClipboard)1001 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
1002                                     int32_t aWhichClipboard) {
1003   // make sure we have a good transferable
1004   if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
1005     return NS_ERROR_FAILURE;
1006   }
1007 
1008   nsresult res;
1009 
1010   // This makes sure we can use the OLE functionality for the clipboard
1011   IDataObject* dataObj;
1012   if (S_OK == ::OleGetClipboard(&dataObj)) {
1013     // Use OLE IDataObject for clipboard operations
1014     res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1015     dataObj->Release();
1016   } else {
1017     // do it the old manual way
1018     res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1019   }
1020   return res;
1021 }
1022 
1023 NS_IMETHODIMP
EmptyClipboard(int32_t aWhichClipboard)1024 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
1025   // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1026   // clipboard to scan it.  If we i) empty and then ii) set data, then the
1027   // 'set data' can sometimes fail with access denied becacuse another program
1028   // has the clipboard open.  So to avoid this race condition for OpenClipboard
1029   // we do not empty the clipboard when we're setting it.
1030   if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
1031     OleSetClipboard(nullptr);
1032   }
1033   return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
1034 }
1035 
1036 //-------------------------------------------------------------------------
HasDataMatchingFlavors(const char ** aFlavorList,uint32_t aLength,int32_t aWhichClipboard,bool * _retval)1037 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList,
1038                                                   uint32_t aLength,
1039                                                   int32_t aWhichClipboard,
1040                                                   bool* _retval) {
1041   *_retval = false;
1042   if (aWhichClipboard != kGlobalClipboard || !aFlavorList) {
1043     return NS_OK;
1044   }
1045 
1046   for (uint32_t i = 0; i < aLength; ++i) {
1047 #ifdef DEBUG
1048     if (strcmp(aFlavorList[i], kTextMime) == 0) {
1049       NS_WARNING(
1050           "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
1051           "INSTEAD");
1052     }
1053 #endif
1054 
1055     UINT format = GetFormat(aFlavorList[i]);
1056     if (IsClipboardFormatAvailable(format)) {
1057       *_retval = true;
1058       break;
1059     } else {
1060       // We haven't found the exact flavor the client asked for, but maybe we
1061       // can still find it from something else that's on the clipboard...
1062       if (strcmp(aFlavorList[i], kUnicodeMime) == 0) {
1063         // client asked for unicode and it wasn't present, check if we have
1064         // CF_TEXT. We'll handle the actual data substitution in the data
1065         // object.
1066         if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
1067           *_retval = true;
1068         }
1069       }
1070     }
1071   }
1072 
1073   return NS_OK;
1074 }
1075