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