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 "mozilla/ArrayUtils.h"
7 
8 #include <ole2.h>
9 #include <shlobj.h>
10 
11 #include "nsDataObj.h"
12 #include "nsArrayUtils.h"
13 #include "nsClipboard.h"
14 #include "nsReadableUtils.h"
15 #include "nsITransferable.h"
16 #include "nsISupportsPrimitives.h"
17 #include "IEnumFE.h"
18 #include "nsPrimitiveHelpers.h"
19 #include "nsString.h"
20 #include "nsImageClipboard.h"
21 #include "nsCRT.h"
22 #include "nsPrintfCString.h"
23 #include "nsIStringBundle.h"
24 #include "nsEscape.h"
25 #include "nsIURL.h"
26 #include "nsNetUtil.h"
27 #include "mozilla/Services.h"
28 #include "nsIOutputStream.h"
29 #include "nscore.h"
30 #include "nsDirectoryServiceDefs.h"
31 #include "nsITimer.h"
32 #include "nsThreadUtils.h"
33 #include "mozilla/Preferences.h"
34 #include "nsIContentPolicy.h"
35 #include "nsContentUtils.h"
36 #include "nsIPrincipal.h"
37 #include "nsNativeCharsetUtils.h"
38 
39 #include "WinUtils.h"
40 #include "mozilla/LazyIdleThread.h"
41 #include <algorithm>
42 
43 using namespace mozilla;
44 using namespace mozilla::widget;
45 
46 #define DEFAULT_THREAD_TIMEOUT_MS 30000
47 
NS_IMPL_ISUPPORTS(nsDataObj::CStream,nsIStreamListener)48 NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
49 
50 //-----------------------------------------------------------------------------
51 // CStream implementation
52 nsDataObj::CStream::CStream() : mChannelRead(false), mStreamRead(0) {}
53 
54 //-----------------------------------------------------------------------------
~CStream()55 nsDataObj::CStream::~CStream() {}
56 
57 //-----------------------------------------------------------------------------
58 // helper - initializes the stream
Init(nsIURI * pSourceURI,uint32_t aContentPolicyType,nsIPrincipal * aRequestingPrincipal)59 nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
60                                   uint32_t aContentPolicyType,
61                                   nsIPrincipal* aRequestingPrincipal) {
62   // we can not create a channel without a requestingPrincipal
63   if (!aRequestingPrincipal) {
64     return NS_ERROR_FAILURE;
65   }
66   nsresult rv;
67   rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
68                      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
69                      aContentPolicyType,
70                      nullptr,  // PerformanceStorage
71                      nullptr,  // loadGroup
72                      nullptr,  // aCallbacks
73                      nsIRequest::LOAD_FROM_CACHE);
74 
75   NS_ENSURE_SUCCESS(rv, rv);
76   rv = mChannel->AsyncOpen2(this);
77   NS_ENSURE_SUCCESS(rv, rv);
78   return NS_OK;
79 }
80 
81 //-----------------------------------------------------------------------------
82 // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
83 // IUnknown and nsIStreamListener.
QueryInterface(REFIID refiid,void ** ppvResult)84 STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
85                                                 void** ppvResult) {
86   *ppvResult = nullptr;
87   if (IID_IUnknown == refiid || refiid == IID_IStream)
88 
89   {
90     *ppvResult = this;
91   }
92 
93   if (nullptr != *ppvResult) {
94     ((LPUNKNOWN)*ppvResult)->AddRef();
95     return S_OK;
96   }
97 
98   return E_NOINTERFACE;
99 }
100 
101 // nsIStreamListener implementation
102 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsISupports * aContext,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)103 nsDataObj::CStream::OnDataAvailable(
104     nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aInputStream,
105     uint64_t aOffset,  // offset within the stream
106     uint32_t aCount)   // bytes available on this call
107 {
108   // Extend the write buffer for the incoming data.
109   uint8_t* buffer = mChannelData.AppendElements(aCount, fallible);
110   if (!buffer) {
111     return NS_ERROR_OUT_OF_MEMORY;
112   }
113   NS_ASSERTION((mChannelData.Length() == (aOffset + aCount)),
114                "stream length mismatch w/write buffer");
115 
116   // Read() may not return aCount on a single call, so loop until we've
117   // accumulated all the data OnDataAvailable has promised.
118   nsresult rv;
119   uint32_t odaBytesReadTotal = 0;
120   do {
121     uint32_t bytesReadByCall = 0;
122     rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), aCount,
123                             &bytesReadByCall);
124     odaBytesReadTotal += bytesReadByCall;
125   } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv));
126   return rv;
127 }
128 
OnStartRequest(nsIRequest * aRequest,nsISupports * aContext)129 NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest,
130                                                  nsISupports* aContext) {
131   mChannelResult = NS_OK;
132   return NS_OK;
133 }
134 
OnStopRequest(nsIRequest * aRequest,nsISupports * aContext,nsresult aStatusCode)135 NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
136                                                 nsISupports* aContext,
137                                                 nsresult aStatusCode) {
138   mChannelRead = true;
139   mChannelResult = aStatusCode;
140   return NS_OK;
141 }
142 
143 // Pumps thread messages while waiting for the async listener operation to
144 // complete. Failing this call will fail the stream incall from Windows
145 // and cancel the operation.
WaitForCompletion()146 nsresult nsDataObj::CStream::WaitForCompletion() {
147   // We are guaranteed OnStopRequest will get called, so this should be ok.
148   SpinEventLoopUntil([&]() { return mChannelRead; });
149 
150   if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
151 
152   return mChannelResult;
153 }
154 
155 //-----------------------------------------------------------------------------
156 // IStream
Clone(IStream ** ppStream)157 STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream) { return E_NOTIMPL; }
158 
159 //-----------------------------------------------------------------------------
Commit(DWORD dwFrags)160 STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags) { return E_NOTIMPL; }
161 
162 //-----------------------------------------------------------------------------
CopyTo(IStream * pDestStream,ULARGE_INTEGER nBytesToCopy,ULARGE_INTEGER * nBytesRead,ULARGE_INTEGER * nBytesWritten)163 STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream,
164                                         ULARGE_INTEGER nBytesToCopy,
165                                         ULARGE_INTEGER* nBytesRead,
166                                         ULARGE_INTEGER* nBytesWritten) {
167   return E_NOTIMPL;
168 }
169 
170 //-----------------------------------------------------------------------------
LockRegion(ULARGE_INTEGER nStart,ULARGE_INTEGER nBytes,DWORD dwFlags)171 STDMETHODIMP nsDataObj::CStream::LockRegion(ULARGE_INTEGER nStart,
172                                             ULARGE_INTEGER nBytes,
173                                             DWORD dwFlags) {
174   return E_NOTIMPL;
175 }
176 
177 //-----------------------------------------------------------------------------
Read(void * pvBuffer,ULONG nBytesToRead,ULONG * nBytesRead)178 STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
179                                       ULONG* nBytesRead) {
180   // Wait for the write into our buffer to complete via the stream listener.
181   // We can't respond to this by saying "call us back later".
182   if (NS_FAILED(WaitForCompletion())) return E_FAIL;
183 
184   // Bytes left for Windows to read out of our buffer
185   ULONG bytesLeft = mChannelData.Length() - mStreamRead;
186   // Let Windows know what we will hand back, usually this is the entire buffer
187   *nBytesRead = std::min(bytesLeft, nBytesToRead);
188   // Copy the buffer data over
189   memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
190   // Update our bytes read tracking
191   mStreamRead += *nBytesRead;
192   return S_OK;
193 }
194 
195 //-----------------------------------------------------------------------------
Revert(void)196 STDMETHODIMP nsDataObj::CStream::Revert(void) { return E_NOTIMPL; }
197 
198 //-----------------------------------------------------------------------------
Seek(LARGE_INTEGER nMove,DWORD dwOrigin,ULARGE_INTEGER * nNewPos)199 STDMETHODIMP nsDataObj::CStream::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
200                                       ULARGE_INTEGER* nNewPos) {
201   if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
202 
203   if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
204       (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
205     nNewPos->LowPart = 0;
206     nNewPos->HighPart = 0;
207     return S_OK;
208   }
209 
210   return E_NOTIMPL;
211 }
212 
213 //-----------------------------------------------------------------------------
SetSize(ULARGE_INTEGER nNewSize)214 STDMETHODIMP nsDataObj::CStream::SetSize(ULARGE_INTEGER nNewSize) {
215   return E_NOTIMPL;
216 }
217 
218 //-----------------------------------------------------------------------------
Stat(STATSTG * statstg,DWORD dwFlags)219 STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
220   if (statstg == nullptr) return STG_E_INVALIDPOINTER;
221 
222   if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
223 
224   memset((void*)statstg, 0, sizeof(STATSTG));
225 
226   if (dwFlags != STATFLAG_NONAME) {
227     nsCOMPtr<nsIURI> sourceURI;
228     if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
229       return E_FAIL;
230     }
231 
232     nsAutoCString strFileName;
233     nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
234     sourceURL->GetFileName(strFileName);
235 
236     if (strFileName.IsEmpty()) return E_FAIL;
237 
238     NS_UnescapeURL(strFileName);
239     NS_ConvertUTF8toUTF16 wideFileName(strFileName);
240 
241     uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
242     void* retBuf = CoTaskMemAlloc(nMaxNameLength);  // freed by caller
243     if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
244 
245     ZeroMemory(retBuf, nMaxNameLength);
246     memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
247     statstg->pwcsName = (LPOLESTR)retBuf;
248   }
249 
250   SYSTEMTIME st;
251 
252   statstg->type = STGTY_STREAM;
253 
254   GetSystemTime(&st);
255   SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
256   statstg->ctime = statstg->atime = statstg->mtime;
257 
258   statstg->cbSize.LowPart = (DWORD)mChannelData.Length();
259   statstg->grfMode = STGM_READ;
260   statstg->grfLocksSupported = LOCK_ONLYONCE;
261   statstg->clsid = CLSID_NULL;
262 
263   return S_OK;
264 }
265 
266 //-----------------------------------------------------------------------------
UnlockRegion(ULARGE_INTEGER nStart,ULARGE_INTEGER nBytes,DWORD dwFlags)267 STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart,
268                                               ULARGE_INTEGER nBytes,
269                                               DWORD dwFlags) {
270   return E_NOTIMPL;
271 }
272 
273 //-----------------------------------------------------------------------------
Write(const void * pvBuffer,ULONG nBytesToRead,ULONG * nBytesRead)274 STDMETHODIMP nsDataObj::CStream::Write(const void* pvBuffer, ULONG nBytesToRead,
275                                        ULONG* nBytesRead) {
276   return E_NOTIMPL;
277 }
278 
279 //-----------------------------------------------------------------------------
CreateStream(IStream ** outStream)280 HRESULT nsDataObj::CreateStream(IStream** outStream) {
281   NS_ENSURE_TRUE(outStream, E_INVALIDARG);
282 
283   nsresult rv = NS_ERROR_FAILURE;
284   nsAutoString wideFileName;
285   nsCOMPtr<nsIURI> sourceURI;
286   HRESULT res;
287 
288   res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
289   if (FAILED(res)) return res;
290 
291   nsDataObj::CStream* pStream = new nsDataObj::CStream();
292   NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
293 
294   pStream->AddRef();
295 
296   // query the requestingPrincipal from the transferable and add it to the new
297   // channel
298   nsCOMPtr<nsIPrincipal> requestingPrincipal;
299   mTransferable->GetRequestingPrincipal(getter_AddRefs(requestingPrincipal));
300   MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
301   // default transferable content policy is nsIContentPolicy::TYPE_OTHER
302   uint32_t contentPolicyType = nsIContentPolicy::TYPE_OTHER;
303   mTransferable->GetContentPolicyType(&contentPolicyType);
304   rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal);
305   if (NS_FAILED(rv)) {
306     pStream->Release();
307     return E_FAIL;
308   }
309   *outStream = pStream;
310 
311   return S_OK;
312 }
313 
314 static GUID CLSID_nsDataObj = {
315     0x1bba7640, 0xdf52, 0x11cf, {0x82, 0x7b, 0, 0xa0, 0x24, 0x3a, 0xe5, 0x05}};
316 
317 /*
318  * deliberately not using MAX_PATH. This is because on platforms < XP
319  * a file created with a long filename may be mishandled by the shell
320  * resulting in it not being able to be deleted or moved.
321  * See bug 250392 for more details.
322  */
323 #define NS_MAX_FILEDESCRIPTOR 128 + 1
324 
325 /*
326  * Class nsDataObj
327  */
328 
329 //-----------------------------------------------------
330 // construction
331 //-----------------------------------------------------
nsDataObj(nsIURI * uri)332 nsDataObj::nsDataObj(nsIURI* uri)
333     : m_cRef(0),
334       mTransferable(nullptr),
335       mIsAsyncMode(FALSE),
336       mIsInOperation(FALSE) {
337   mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
338                                  NS_LITERAL_CSTRING("nsDataObj"),
339                                  LazyIdleThread::ManualShutdown);
340   m_enumFE = new CEnumFormatEtc();
341   m_enumFE->AddRef();
342 
343   if (uri) {
344     // A URI was obtained, so pass this through to the DataObject
345     // so it can create a SourceURL for CF_HTML flavour
346     uri->GetSpec(mSourceURL);
347   }
348 }
349 //-----------------------------------------------------
350 // destruction
351 //-----------------------------------------------------
~nsDataObj()352 nsDataObj::~nsDataObj() {
353   NS_IF_RELEASE(mTransferable);
354 
355   mDataFlavors.Clear();
356 
357   m_enumFE->Release();
358 
359   // Free arbitrary system formats
360   for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
361     CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
362     ReleaseStgMedium(&mDataEntryList[idx]->stgm);
363     CoTaskMemFree(mDataEntryList[idx]);
364   }
365 }
366 
367 //-----------------------------------------------------
368 // IUnknown interface methods - see inknown.h for documentation
369 //-----------------------------------------------------
QueryInterface(REFIID riid,void ** ppv)370 STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
371   *ppv = nullptr;
372 
373   if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
374     *ppv = this;
375     AddRef();
376     return S_OK;
377   } else if (IID_IAsyncOperation == riid) {
378     *ppv = static_cast<IAsyncOperation*>(this);
379     AddRef();
380     return S_OK;
381   }
382 
383   return E_NOINTERFACE;
384 }
385 
386 //-----------------------------------------------------
STDMETHODIMP_(ULONG)387 STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
388   ++m_cRef;
389   NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
390   return m_cRef;
391 }
392 
393 namespace {
394 class RemoveTempFileHelper final : public nsIObserver {
395  public:
RemoveTempFileHelper(nsIFile * aTempFile)396   explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
397     MOZ_ASSERT(mTempFile);
398   }
399 
400   // The attach method is seperate from the constructor as we may be addref-ing
401   // ourself, and we want to be sure someone has a strong reference to us.
Attach()402   void Attach() {
403     // We need to listen to both the xpcom shutdown message and our timer, and
404     // fire when the first of either of these two messages is received.
405     nsresult rv;
406     rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
407                                  nsITimer::TYPE_ONE_SHOT);
408     if (NS_WARN_IF(NS_FAILED(rv))) {
409       return;
410     }
411 
412     nsCOMPtr<nsIObserverService> observerService =
413         do_GetService("@mozilla.org/observer-service;1");
414     if (NS_WARN_IF(!observerService)) {
415       mTimer->Cancel();
416       mTimer = nullptr;
417       return;
418     }
419     observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
420   }
421 
422   NS_DECL_ISUPPORTS
423   NS_DECL_NSIOBSERVER
424 
425  private:
~RemoveTempFileHelper()426   ~RemoveTempFileHelper() {
427     if (mTempFile) {
428       mTempFile->Remove(false);
429     }
430   }
431 
432   nsCOMPtr<nsIFile> mTempFile;
433   nsCOMPtr<nsITimer> mTimer;
434 };
435 
436 NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver);
437 
438 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)439 RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
440                               const char16_t* aData) {
441   // Let's be careful and make sure that we don't die immediately
442   RefPtr<RemoveTempFileHelper> grip = this;
443 
444   // Make sure that we aren't called again by destroying references to ourself.
445   nsCOMPtr<nsIObserverService> observerService =
446       do_GetService("@mozilla.org/observer-service;1");
447   if (observerService) {
448     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
449   }
450 
451   if (mTimer) {
452     mTimer->Cancel();
453     mTimer = nullptr;
454   }
455 
456   // Remove the tempfile
457   if (mTempFile) {
458     mTempFile->Remove(false);
459     mTempFile = nullptr;
460   }
461   return NS_OK;
462 }
463 }  // namespace
464 
465 //-----------------------------------------------------
STDMETHODIMP_(ULONG)466 STDMETHODIMP_(ULONG) nsDataObj::Release() {
467   --m_cRef;
468 
469   NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
470   if (0 != m_cRef) return m_cRef;
471 
472   // We have released our last ref on this object and need to delete the
473   // temp file. External app acting as drop target may still need to open the
474   // temp file. Addref a timer so it can delay deleting file and destroying
475   // this object.
476   if (mCachedTempFile) {
477     RefPtr<RemoveTempFileHelper> helper =
478         new RemoveTempFileHelper(mCachedTempFile);
479     mCachedTempFile = nullptr;
480     helper->Attach();
481   }
482 
483   delete this;
484 
485   return 0;
486 }
487 
488 //-----------------------------------------------------
FormatsMatch(const FORMATETC & source,const FORMATETC & target) const489 BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
490                              const FORMATETC& target) const {
491   if ((source.cfFormat == target.cfFormat) &&
492       (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
493     return TRUE;
494   } else {
495     return FALSE;
496   }
497 }
498 
499 //-----------------------------------------------------
500 // IDataObject methods
501 //-----------------------------------------------------
GetData(LPFORMATETC aFormat,LPSTGMEDIUM pSTM)502 STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
503   if (!mTransferable) return DV_E_FORMATETC;
504 
505   uint32_t dfInx = 0;
506 
507   static CLIPFORMAT fileDescriptorFlavorA =
508       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
509   static CLIPFORMAT fileDescriptorFlavorW =
510       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
511   static CLIPFORMAT uniformResourceLocatorA =
512       ::RegisterClipboardFormat(CFSTR_INETURLA);
513   static CLIPFORMAT uniformResourceLocatorW =
514       ::RegisterClipboardFormat(CFSTR_INETURLW);
515   static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
516   static CLIPFORMAT PreferredDropEffect =
517       ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
518 
519   // Arbitrary system formats are used for image feedback during drag
520   // and drop. We are responsible for storing these internally during
521   // drag operations.
522   LPDATAENTRY pde;
523   if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
524     return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
525                                                             : E_UNEXPECTED;
526   }
527 
528   // Firefox internal formats
529   ULONG count;
530   FORMATETC fe;
531   m_enumFE->Reset();
532   while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
533          dfInx < mDataFlavors.Length()) {
534     nsCString& df = mDataFlavors.ElementAt(dfInx);
535     if (FormatsMatch(fe, *aFormat)) {
536       pSTM->pUnkForRelease =
537           nullptr;  // caller is responsible for deleting this data
538       CLIPFORMAT format = aFormat->cfFormat;
539       switch (format) {
540         // Someone is asking for plain or unicode text
541         case CF_TEXT:
542         case CF_UNICODETEXT:
543           return GetText(df, *aFormat, *pSTM);
544 
545         // Some 3rd party apps that receive drag and drop files from the browser
546         // window require support for this.
547         case CF_HDROP:
548           return GetFile(*aFormat, *pSTM);
549 
550         // Someone is asking for an image
551         case CF_DIBV5:
552         case CF_DIB:
553           return GetDib(df, *aFormat, *pSTM);
554 
555         default:
556           if (format == fileDescriptorFlavorA)
557             return GetFileDescriptor(*aFormat, *pSTM, false);
558           if (format == fileDescriptorFlavorW)
559             return GetFileDescriptor(*aFormat, *pSTM, true);
560           if (format == uniformResourceLocatorA)
561             return GetUniformResourceLocator(*aFormat, *pSTM, false);
562           if (format == uniformResourceLocatorW)
563             return GetUniformResourceLocator(*aFormat, *pSTM, true);
564           if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
565           if (format == PreferredDropEffect)
566             return GetPreferredDropEffect(*aFormat, *pSTM);
567           // MOZ_LOG(gWindowsLog, LogLevel::Info,
568           //       ("***** nsDataObj::GetData - Unknown format %u\n", format));
569           return GetText(df, *aFormat, *pSTM);
570       }  // switch
571     }    // if
572     dfInx++;
573   }  // while
574 
575   return DATA_E_FORMATETC;
576 }
577 
578 //-----------------------------------------------------
GetDataHere(LPFORMATETC pFE,LPSTGMEDIUM pSTM)579 STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
580   return E_FAIL;
581 }
582 
583 //-----------------------------------------------------
584 // Other objects querying to see if we support a
585 // particular format
586 //-----------------------------------------------------
QueryGetData(LPFORMATETC pFE)587 STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
588   // Arbitrary system formats are used for image feedback during drag
589   // and drop. We are responsible for storing these internally during
590   // drag operations.
591   LPDATAENTRY pde;
592   if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
593 
594   // Firefox internal formats
595   ULONG count;
596   FORMATETC fe;
597   m_enumFE->Reset();
598   while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
599     if (fe.cfFormat == pFE->cfFormat) {
600       return S_OK;
601     }
602   }
603   return E_FAIL;
604 }
605 
606 //-----------------------------------------------------
GetCanonicalFormatEtc(LPFORMATETC pFEIn,LPFORMATETC pFEOut)607 STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
608                                               LPFORMATETC pFEOut) {
609   return E_NOTIMPL;
610 }
611 
612 //-----------------------------------------------------
SetData(LPFORMATETC aFormat,LPSTGMEDIUM aMedium,BOOL shouldRel)613 STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
614                                 BOOL shouldRel) {
615   // Arbitrary system formats are used for image feedback during drag
616   // and drop. We are responsible for storing these internally during
617   // drag operations.
618   LPDATAENTRY pde;
619   if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
620     // Release the old data the lookup handed us for this format. This
621     // may have been set in CopyMediumData when we originally stored the
622     // data.
623     if (pde->stgm.tymed) {
624       ReleaseStgMedium(&pde->stgm);
625       memset(&pde->stgm, 0, sizeof(STGMEDIUM));
626     }
627 
628     bool result = true;
629     if (shouldRel) {
630       // If shouldRel is TRUE, the data object called owns the storage medium
631       // after the call returns. Store the incoming data in our data array for
632       // release when we are destroyed. This is the common case with arbitrary
633       // data from explorer.
634       pde->stgm = *aMedium;
635     } else {
636       // Copy the incoming data into our data array. (AFAICT, this never gets
637       // called with arbitrary formats for drag images.)
638       result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
639     }
640     pde->fe.tymed = pde->stgm.tymed;
641 
642     return result ? S_OK : DV_E_TYMED;
643   }
644 
645   if (shouldRel) ReleaseStgMedium(aMedium);
646 
647   return S_OK;
648 }
649 
LookupArbitraryFormat(FORMATETC * aFormat,LPDATAENTRY * aDataEntry,BOOL aAddorUpdate)650 bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
651                                       LPDATAENTRY* aDataEntry,
652                                       BOOL aAddorUpdate) {
653   *aDataEntry = nullptr;
654 
655   if (aFormat->ptd != nullptr) return false;
656 
657   // See if it's already in our list. If so return the data entry.
658   for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
659     if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
660         mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
661         mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
662       if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
663         // If the caller requests we update, or if the
664         // medium type matches, return the entry.
665         *aDataEntry = mDataEntryList[idx];
666         return true;
667       } else {
668         // Medium does not match, not found.
669         return false;
670       }
671     }
672   }
673 
674   if (!aAddorUpdate) return false;
675 
676   // Add another entry to mDataEntryList
677   LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
678   if (!dataEntry) return false;
679 
680   dataEntry->fe = *aFormat;
681   *aDataEntry = dataEntry;
682   memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
683 
684   // Add this to our IEnumFORMATETC impl. so we can return it when
685   // it's requested.
686   m_enumFE->AddFormatEtc(aFormat);
687 
688   // Store a copy internally in the arbitrary formats array.
689   mDataEntryList.AppendElement(dataEntry);
690 
691   return true;
692 }
693 
CopyMediumData(STGMEDIUM * aMediumDst,STGMEDIUM * aMediumSrc,LPFORMATETC aFormat,BOOL aSetData)694 bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
695                                LPFORMATETC aFormat, BOOL aSetData) {
696   STGMEDIUM stgmOut = *aMediumSrc;
697 
698   switch (stgmOut.tymed) {
699     case TYMED_ISTREAM:
700       stgmOut.pstm->AddRef();
701       break;
702     case TYMED_ISTORAGE:
703       stgmOut.pstg->AddRef();
704       break;
705     case TYMED_HGLOBAL:
706       if (!aMediumSrc->pUnkForRelease) {
707         if (aSetData) {
708           if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
709           stgmOut.hGlobal =
710               OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
711           if (!stgmOut.hGlobal) return false;
712         } else {
713           // We are returning this data from LookupArbitraryFormat, indicate to
714           // the shell we hold it and will free it.
715           stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
716         }
717       }
718       break;
719     default:
720       return false;
721   }
722 
723   if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
724 
725   *aMediumDst = stgmOut;
726 
727   return true;
728 }
729 
730 //-----------------------------------------------------
EnumFormatEtc(DWORD dwDir,LPENUMFORMATETC * ppEnum)731 STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
732   switch (dwDir) {
733     case DATADIR_GET:
734       m_enumFE->Clone(ppEnum);
735       break;
736     case DATADIR_SET:
737       // fall through
738     default:
739       *ppEnum = nullptr;
740   }  // switch
741 
742   if (nullptr == *ppEnum) return E_FAIL;
743 
744   (*ppEnum)->Reset();
745   // Clone already AddRefed the result so don't addref it again.
746   return NOERROR;
747 }
748 
749 //-----------------------------------------------------
DAdvise(LPFORMATETC pFE,DWORD dwFlags,LPADVISESINK pIAdviseSink,DWORD * pdwConn)750 STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
751                                 LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
752   return OLE_E_ADVISENOTSUPPORTED;
753 }
754 
755 //-----------------------------------------------------
DUnadvise(DWORD dwConn)756 STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
757   return OLE_E_ADVISENOTSUPPORTED;
758 }
759 
760 //-----------------------------------------------------
EnumDAdvise(LPENUMSTATDATA * ppEnum)761 STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
762   return OLE_E_ADVISENOTSUPPORTED;
763 }
764 
765 // IAsyncOperation methods
EndOperation(HRESULT hResult,IBindCtx * pbcReserved,DWORD dwEffects)766 STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
767                                      DWORD dwEffects) {
768   mIsInOperation = FALSE;
769   return S_OK;
770 }
771 
GetAsyncMode(BOOL * pfIsOpAsync)772 STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
773   *pfIsOpAsync = mIsAsyncMode;
774 
775   return S_OK;
776 }
777 
InOperation(BOOL * pfInAsyncOp)778 STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
779   *pfInAsyncOp = mIsInOperation;
780 
781   return S_OK;
782 }
783 
SetAsyncMode(BOOL fDoOpAsync)784 STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
785   mIsAsyncMode = fDoOpAsync;
786   return S_OK;
787 }
788 
StartOperation(IBindCtx * pbcReserved)789 STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
790   mIsInOperation = TRUE;
791   return S_OK;
792 }
793 
794 //-----------------------------------------------------
795 // GetData and SetData helper functions
796 //-----------------------------------------------------
AddSetFormat(FORMATETC & aFE)797 HRESULT nsDataObj::AddSetFormat(FORMATETC& aFE) { return S_OK; }
798 
799 //-----------------------------------------------------
AddGetFormat(FORMATETC & aFE)800 HRESULT nsDataObj::AddGetFormat(FORMATETC& aFE) { return S_OK; }
801 
802 //
803 // GetDIB
804 //
805 // Someone is asking for a bitmap. The data in the transferable will be a
806 // straight imgIContainer, so just QI it.
807 //
808 HRESULT
GetDib(const nsACString & inFlavor,FORMATETC & aFormat,STGMEDIUM & aSTG)809 nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
810                   STGMEDIUM& aSTG) {
811   ULONG result = E_FAIL;
812   uint32_t len = 0;
813   nsCOMPtr<nsISupports> genericDataWrapper;
814   mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
815                                  getter_AddRefs(genericDataWrapper), &len);
816   nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
817   if (!image) {
818     // Check if the image was put in an nsISupportsInterfacePointer wrapper.
819     // This might not be necessary any more, but could be useful for backwards
820     // compatibility.
821     nsCOMPtr<nsISupportsInterfacePointer> ptr(
822         do_QueryInterface(genericDataWrapper));
823     if (ptr) {
824       nsCOMPtr<nsISupports> supports;
825       ptr->GetData(getter_AddRefs(supports));
826       image = do_QueryInterface(supports);
827     }
828   }
829 
830   if (image) {
831     // use the |nsImageToClipboard| helper class to build up a bitmap. We now
832     // own the bits, and pass them back to the OS in |aSTG|.
833     nsImageToClipboard converter(image, aFormat.cfFormat == CF_DIBV5);
834     HANDLE bits = nullptr;
835     nsresult rv = converter.GetPicture(&bits);
836     if (NS_SUCCEEDED(rv) && bits) {
837       aSTG.hGlobal = bits;
838       aSTG.tymed = TYMED_HGLOBAL;
839       result = S_OK;
840     }
841   }  // if we have an image
842   else
843     NS_WARNING("Definitely not an image on clipboard");
844   return result;
845 }
846 
847 //
848 // GetFileDescriptor
849 //
850 
851 HRESULT
GetFileDescriptor(FORMATETC & aFE,STGMEDIUM & aSTG,bool aIsUnicode)852 nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
853                               bool aIsUnicode) {
854   HRESULT res = S_OK;
855 
856   // How we handle this depends on if we're dealing with an internet
857   // shortcut, since those are done under the covers.
858   if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
859     if (aIsUnicode)
860       return GetFileDescriptor_IStreamW(aFE, aSTG);
861     else
862       return GetFileDescriptor_IStreamA(aFE, aSTG);
863   } else if (IsFlavourPresent(kURLMime)) {
864     if (aIsUnicode)
865       res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
866     else
867       res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
868   } else
869     NS_WARNING("Not yet implemented\n");
870 
871   return res;
872 }  // GetFileDescriptor
873 
874 //
875 HRESULT
GetFileContents(FORMATETC & aFE,STGMEDIUM & aSTG)876 nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
877   HRESULT res = S_OK;
878 
879   // How we handle this depends on if we're dealing with an internet
880   // shortcut, since those are done under the covers.
881   if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
882     return GetFileContents_IStream(aFE, aSTG);
883   else if (IsFlavourPresent(kURLMime))
884     return GetFileContentsInternetShortcut(aFE, aSTG);
885   else
886     NS_WARNING("Not yet implemented\n");
887 
888   return res;
889 
890 }  // GetFileContents
891 
892 //
893 // Given a unicode string, we ensure that it contains only characters which are
894 // valid within the file system. Remove all forbidden characters from the name,
895 // and completely disallow any title that starts with a forbidden name and
896 // extension (e.g. "nul" is invalid, but "nul." and "nul.txt" are also invalid
897 // and will cause problems).
898 //
899 // It would seem that this is more functionality suited to being in nsIFile.
900 //
MangleTextToValidFilename(nsString & aText)901 static void MangleTextToValidFilename(nsString& aText) {
902   static const char* forbiddenNames[] = {
903       "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",  "COM8",
904       "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6",  "LPT7",
905       "LPT8", "LPT9", "CON",  "PRN",  "AUX",  "NUL",  "CLOCK$"};
906 
907   aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
908   aText.CompressWhitespace(true, true);
909   uint32_t nameLen;
910   for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
911     nameLen = (uint32_t)strlen(forbiddenNames[n]);
912     if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
913       // invalid name is either the entire string, or a prefix with a period
914       if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
915         aText.Truncate();
916         break;
917       }
918     }
919   }
920 }
921 
922 //
923 // Given a unicode string, convert it down to a valid local charset filename
924 // with the supplied extension. This ensures that we do not cut MBCS characters
925 // in the middle.
926 //
927 // It would seem that this is more functionality suited to being in nsIFile.
928 //
CreateFilenameFromTextA(nsString & aText,const char * aExtension,char * aFilename,uint32_t aFilenameLen)929 static bool CreateFilenameFromTextA(nsString& aText, const char* aExtension,
930                                     char* aFilename, uint32_t aFilenameLen) {
931   // ensure that the supplied name doesn't have invalid characters. If
932   // a valid mangled filename couldn't be created then it will leave the
933   // text empty.
934   MangleTextToValidFilename(aText);
935   if (aText.IsEmpty()) return false;
936 
937   // repeatably call WideCharToMultiByte as long as the title doesn't fit in the
938   // buffer available to us. Continually reduce the length of the source title
939   // until the MBCS version will fit in the buffer with room for the supplied
940   // extension. Doing it this way ensures that even in MBCS environments there
941   // will be a valid MBCS filename of the correct length.
942   int maxUsableFilenameLen =
943       aFilenameLen - strlen(aExtension) - 1;  // space for ext + null byte
944   int currLen, textLen = (int)std::min(aText.Length(), aFilenameLen);
945   char defaultChar = '_';
946   do {
947     currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
948                                   aText.get(), textLen--, aFilename,
949                                   maxUsableFilenameLen, &defaultChar, nullptr);
950   } while (currLen == 0 && textLen > 0 &&
951            GetLastError() == ERROR_INSUFFICIENT_BUFFER);
952   if (currLen > 0 && textLen > 0) {
953     strcpy(&aFilename[currLen], aExtension);
954     return true;
955   } else {
956     // empty names aren't permitted
957     return false;
958   }
959 }
960 
CreateFilenameFromTextW(nsString & aText,const wchar_t * aExtension,wchar_t * aFilename,uint32_t aFilenameLen)961 static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
962                                     wchar_t* aFilename, uint32_t aFilenameLen) {
963   // ensure that the supplied name doesn't have invalid characters. If
964   // a valid mangled filename couldn't be created then it will leave the
965   // text empty.
966   MangleTextToValidFilename(aText);
967   if (aText.IsEmpty()) return false;
968 
969   const int extensionLen = wcslen(aExtension);
970   if (aText.Length() + extensionLen + 1 > aFilenameLen)
971     aText.Truncate(aFilenameLen - extensionLen - 1);
972   wcscpy(&aFilename[0], aText.get());
973   wcscpy(&aFilename[aText.Length()], aExtension);
974   return true;
975 }
976 
977 #define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
978 
GetLocalizedString(const char * aName,nsAString & aString)979 static bool GetLocalizedString(const char* aName, nsAString& aString) {
980   nsCOMPtr<nsIStringBundleService> stringService =
981       mozilla::services::GetStringBundleService();
982   if (!stringService) return false;
983 
984   nsCOMPtr<nsIStringBundle> stringBundle;
985   nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
986                                             getter_AddRefs(stringBundle));
987   if (NS_FAILED(rv)) return false;
988 
989   rv = stringBundle->GetStringFromName(aName, aString);
990   return NS_SUCCEEDED(rv);
991 }
992 
993 //
994 // GetFileDescriptorInternetShortcut
995 //
996 // Create the special format for an internet shortcut and build up the data
997 // structures the shell is expecting.
998 //
999 HRESULT
GetFileDescriptorInternetShortcutA(FORMATETC & aFE,STGMEDIUM & aSTG)1000 nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
1001                                                STGMEDIUM& aSTG) {
1002   // get the title of the shortcut
1003   nsAutoString title;
1004   if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1005 
1006   HGLOBAL fileGroupDescHandle =
1007       ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
1008   if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1009 
1010   LPFILEGROUPDESCRIPTORA fileGroupDescA =
1011       reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
1012           ::GlobalLock(fileGroupDescHandle));
1013   if (!fileGroupDescA) {
1014     ::GlobalFree(fileGroupDescHandle);
1015     return E_OUTOFMEMORY;
1016   }
1017 
1018   // get a valid filename in the following order: 1) from the page title,
1019   // 2) localized string for an untitled page, 3) just use "Untitled.URL"
1020   if (!CreateFilenameFromTextA(title, ".URL", fileGroupDescA->fgd[0].cFileName,
1021                                NS_MAX_FILEDESCRIPTOR)) {
1022     nsAutoString untitled;
1023     if (!GetLocalizedString("noPageTitle", untitled) ||
1024         !CreateFilenameFromTextA(untitled, ".URL",
1025                                  fileGroupDescA->fgd[0].cFileName,
1026                                  NS_MAX_FILEDESCRIPTOR)) {
1027       strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.URL");
1028     }
1029   }
1030 
1031   // one file in the file block
1032   fileGroupDescA->cItems = 1;
1033   fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
1034 
1035   ::GlobalUnlock(fileGroupDescHandle);
1036   aSTG.hGlobal = fileGroupDescHandle;
1037   aSTG.tymed = TYMED_HGLOBAL;
1038 
1039   return S_OK;
1040 }  // GetFileDescriptorInternetShortcutA
1041 
1042 HRESULT
GetFileDescriptorInternetShortcutW(FORMATETC & aFE,STGMEDIUM & aSTG)1043 nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
1044                                                STGMEDIUM& aSTG) {
1045   // get the title of the shortcut
1046   nsAutoString title;
1047   if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1048 
1049   HGLOBAL fileGroupDescHandle =
1050       ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
1051   if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1052 
1053   LPFILEGROUPDESCRIPTORW fileGroupDescW =
1054       reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
1055           ::GlobalLock(fileGroupDescHandle));
1056   if (!fileGroupDescW) {
1057     ::GlobalFree(fileGroupDescHandle);
1058     return E_OUTOFMEMORY;
1059   }
1060 
1061   // get a valid filename in the following order: 1) from the page title,
1062   // 2) localized string for an untitled page, 3) just use "Untitled.URL"
1063   if (!CreateFilenameFromTextW(title, L".URL", fileGroupDescW->fgd[0].cFileName,
1064                                NS_MAX_FILEDESCRIPTOR)) {
1065     nsAutoString untitled;
1066     if (!GetLocalizedString("noPageTitle", untitled) ||
1067         !CreateFilenameFromTextW(untitled, L".URL",
1068                                  fileGroupDescW->fgd[0].cFileName,
1069                                  NS_MAX_FILEDESCRIPTOR)) {
1070       wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.URL");
1071     }
1072   }
1073 
1074   // one file in the file block
1075   fileGroupDescW->cItems = 1;
1076   fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
1077 
1078   ::GlobalUnlock(fileGroupDescHandle);
1079   aSTG.hGlobal = fileGroupDescHandle;
1080   aSTG.tymed = TYMED_HGLOBAL;
1081 
1082   return S_OK;
1083 }  // GetFileDescriptorInternetShortcutW
1084 
1085 //
1086 // GetFileContentsInternetShortcut
1087 //
1088 // Create the special format for an internet shortcut and build up the data
1089 // structures the shell is expecting.
1090 //
1091 HRESULT
GetFileContentsInternetShortcut(FORMATETC & aFE,STGMEDIUM & aSTG)1092 nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
1093   static const char* kShellIconPref = "browser.shell.shortcutFavicons";
1094   nsAutoString url;
1095   if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1096 
1097   nsCOMPtr<nsIURI> aUri;
1098   nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
1099   if (NS_FAILED(rv)) {
1100     return E_FAIL;
1101   }
1102 
1103   nsAutoCString asciiUrl;
1104   rv = aUri->GetAsciiSpec(asciiUrl);
1105   if (NS_FAILED(rv)) {
1106     return E_FAIL;
1107   }
1108 
1109   const char* shortcutFormatStr;
1110   int totalLen;
1111   nsCString asciiPath;
1112   if (!Preferences::GetBool(kShellIconPref, true)) {
1113     shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
1114     const int formatLen = strlen(shortcutFormatStr) - 2;  // don't include %s
1115     totalLen = formatLen + asciiUrl.Length();  // don't include null character
1116   } else {
1117     nsCOMPtr<nsIFile> icoFile;
1118 
1119     nsAutoString aUriHash;
1120 
1121     mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash,
1122                                                          mIOThread, true);
1123 
1124     rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
1125     NS_ENSURE_SUCCESS(rv, E_FAIL);
1126     nsString path;
1127     rv = icoFile->GetPath(path);
1128     NS_ENSURE_SUCCESS(rv, E_FAIL);
1129 
1130     if (NS_IsAscii(path.get())) {
1131       LossyCopyUTF16toASCII(path, asciiPath);
1132       shortcutFormatStr =
1133           "[InternetShortcut]\r\nURL=%s\r\n"
1134           "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
1135           "IconIndex=0\r\n";
1136     } else {
1137       int len =
1138           WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1139                               path.Length(), nullptr, 0, nullptr, nullptr);
1140       NS_ENSURE_TRUE(len > 0, E_FAIL);
1141       asciiPath.SetLength(len);
1142       WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1143                           path.Length(), asciiPath.BeginWriting(), len, nullptr,
1144                           nullptr);
1145       shortcutFormatStr =
1146           "[InternetShortcut]\r\nURL=%s\r\n"
1147           "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
1148           "[InternetShortcut.W]\r\nIconFile=%s\r\n";
1149     }
1150     const int formatLen = strlen(shortcutFormatStr) - 2 * 2;  // no %s twice
1151     totalLen = formatLen + asciiUrl.Length() +
1152                asciiPath.Length();  // we don't want a null character on the end
1153   }
1154 
1155   // create a global memory area and build up the file contents w/in it
1156   HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen);
1157   if (!hGlobalMemory) return E_OUTOFMEMORY;
1158 
1159   char* contents = reinterpret_cast<char*>(::GlobalLock(hGlobalMemory));
1160   if (!contents) {
1161     ::GlobalFree(hGlobalMemory);
1162     return E_OUTOFMEMORY;
1163   }
1164 
1165   // NOTE: we intentionally use the Microsoft version of snprintf here because
1166   // it does NOT null
1167   // terminate strings which reach the maximum size of the buffer. Since we know
1168   // that the formatted length here is totalLen, this call to _snprintf will
1169   // format the string into the buffer without appending the null character.
1170 
1171   if (!Preferences::GetBool(kShellIconPref, true)) {
1172     _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
1173   } else {
1174     _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
1175               asciiPath.get());
1176   }
1177 
1178   ::GlobalUnlock(hGlobalMemory);
1179   aSTG.hGlobal = hGlobalMemory;
1180   aSTG.tymed = TYMED_HGLOBAL;
1181 
1182   return S_OK;
1183 }  // GetFileContentsInternetShortcut
1184 
1185 // check if specified flavour is present in the transferable
IsFlavourPresent(const char * inFlavour)1186 bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
1187   bool retval = false;
1188   NS_ENSURE_TRUE(mTransferable, false);
1189 
1190   // get the list of flavors available in the transferable
1191   nsCOMPtr<nsIArray> flavorList;
1192   mTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
1193   NS_ENSURE_TRUE(flavorList, false);
1194 
1195   // try to find requested flavour
1196   uint32_t cnt;
1197   flavorList->GetLength(&cnt);
1198   for (uint32_t i = 0; i < cnt; ++i) {
1199     nsCOMPtr<nsISupportsCString> currentFlavor =
1200         do_QueryElementAt(flavorList, i);
1201     if (currentFlavor) {
1202       nsAutoCString flavorStr;
1203       currentFlavor->GetData(flavorStr);
1204       if (flavorStr.Equals(inFlavour)) {
1205         retval = true;  // found it!
1206         break;
1207       }
1208     }
1209   }  // for each flavor
1210 
1211   return retval;
1212 }
1213 
GetPreferredDropEffect(FORMATETC & aFE,STGMEDIUM & aSTG)1214 HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
1215   HRESULT res = S_OK;
1216   aSTG.tymed = TYMED_HGLOBAL;
1217   aSTG.pUnkForRelease = nullptr;
1218   HGLOBAL hGlobalMemory = nullptr;
1219   hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
1220   if (hGlobalMemory) {
1221     DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
1222     // The PreferredDropEffect clipboard format is only registered if a
1223     // drag/drop of an image happens from Mozilla to the desktop.  We want its
1224     // value to be DROPEFFECT_MOVE in that case so that the file is moved from
1225     // the temporary location, not copied. This value should, ideally, be set on
1226     // the data object via SetData() but our IDataObject implementation doesn't
1227     // implement SetData.  It adds data to the data object lazily only when the
1228     // drop target asks for it.
1229     *pdw = (DWORD)DROPEFFECT_MOVE;
1230     GlobalUnlock(hGlobalMemory);
1231   } else {
1232     res = E_OUTOFMEMORY;
1233   }
1234   aSTG.hGlobal = hGlobalMemory;
1235   return res;
1236 }
1237 
1238 //-----------------------------------------------------
GetText(const nsACString & aDataFlavor,FORMATETC & aFE,STGMEDIUM & aSTG)1239 HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
1240                            STGMEDIUM& aSTG) {
1241   void* data = nullptr;
1242   uint32_t len;
1243 
1244   // if someone asks for text/plain, look up text/unicode instead in the
1245   // transferable.
1246   const char* flavorStr;
1247   const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor);
1248   if (aDataFlavor.EqualsLiteral("text/plain"))
1249     flavorStr = kUnicodeMime;
1250   else
1251     flavorStr = flat.get();
1252 
1253   // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
1254   nsCOMPtr<nsISupports> genericDataWrapper;
1255   mTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper),
1256                                  &len);
1257   if (!len) return E_FAIL;
1258   nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(flavorStr),
1259                                               genericDataWrapper, &data, len);
1260   if (!data) return E_FAIL;
1261 
1262   HGLOBAL hGlobalMemory = nullptr;
1263 
1264   aSTG.tymed = TYMED_HGLOBAL;
1265   aSTG.pUnkForRelease = nullptr;
1266 
1267   // We play games under the hood and advertise flavors that we know we
1268   // can support, only they require a bit of conversion or munging of the data.
1269   // Do that here.
1270   //
1271   // The transferable gives us data that is null-terminated, but this isn't
1272   // reflected in the |len| parameter. Windoze apps expect this null to be there
1273   // so bump our data buffer by the appropriate size to account for the null
1274   // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
1275   DWORD allocLen = (DWORD)len;
1276   if (aFE.cfFormat == CF_TEXT) {
1277     // Someone is asking for text/plain; convert the unicode (assuming it's
1278     // present) to text with the correct platform encoding.
1279     size_t bufferSize = sizeof(char) * (len + 2);
1280     char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
1281     char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
1282     int32_t plainTextLen =
1283         WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
1284                             plainTextData, bufferSize, NULL, NULL);
1285     // replace the unicode data with our plaintext data. Recall that
1286     // |plainTextLen| doesn't include the null in the length.
1287     free(data);
1288     if (plainTextLen) {
1289       data = plainTextData;
1290       allocLen = plainTextLen;
1291     } else {
1292       free(plainTextData);
1293       NS_WARNING("Oh no, couldn't convert unicode to plain text");
1294       return S_OK;
1295     }
1296   } else if (aFE.cfFormat == nsClipboard::CF_HTML) {
1297     // Someone is asking for win32's HTML flavor. Convert our html fragment
1298     // from unicode to UTF-8 then put it into a format specified by msft.
1299     NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
1300     char* utf8HTML = nullptr;
1301     nsresult rv =
1302         BuildPlatformHTML(converter.get(), &utf8HTML);  // null terminates
1303 
1304     free(data);
1305     if (NS_SUCCEEDED(rv) && utf8HTML) {
1306       // replace the unicode data with our HTML data. Don't forget the null.
1307       data = utf8HTML;
1308       allocLen = strlen(utf8HTML) + sizeof(char);
1309     } else {
1310       NS_WARNING("Oh no, couldn't convert to HTML");
1311       return S_OK;
1312     }
1313   } else if (aFE.cfFormat != nsClipboard::CF_CUSTOMTYPES) {
1314     // we assume that any data that isn't caught above is unicode. This may
1315     // be an erroneous assumption, but is true so far.
1316     allocLen += sizeof(char16_t);
1317   }
1318 
1319   hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
1320 
1321   // Copy text to Global Memory Area
1322   if (hGlobalMemory) {
1323     char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
1324     char* source = reinterpret_cast<char*>(data);
1325     memcpy(dest, source, allocLen);  // copies the null as well
1326     GlobalUnlock(hGlobalMemory);
1327   }
1328   aSTG.hGlobal = hGlobalMemory;
1329 
1330   // Now, delete the memory that was created by CreateDataFromPrimitive (or our
1331   // text/plain data)
1332   free(data);
1333 
1334   return S_OK;
1335 }
1336 
1337 //-----------------------------------------------------
GetFile(FORMATETC & aFE,STGMEDIUM & aSTG)1338 HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1339   uint32_t dfInx = 0;
1340   ULONG count;
1341   FORMATETC fe;
1342   m_enumFE->Reset();
1343   while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
1344          dfInx < mDataFlavors.Length()) {
1345     if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
1346       return DropImage(aFE, aSTG);
1347     if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
1348       return DropFile(aFE, aSTG);
1349     if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
1350       return DropTempFile(aFE, aSTG);
1351     dfInx++;
1352   }
1353   return E_FAIL;
1354 }
1355 
DropFile(FORMATETC & aFE,STGMEDIUM & aSTG)1356 HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1357   nsresult rv;
1358   uint32_t len = 0;
1359   nsCOMPtr<nsISupports> genericDataWrapper;
1360 
1361   mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper),
1362                                  &len);
1363   nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
1364 
1365   if (!file) {
1366     nsCOMPtr<nsISupportsInterfacePointer> ptr(
1367         do_QueryInterface(genericDataWrapper));
1368     if (ptr) {
1369       nsCOMPtr<nsISupports> supports;
1370       ptr->GetData(getter_AddRefs(supports));
1371       file = do_QueryInterface(supports);
1372     }
1373   }
1374 
1375   if (!file) return E_FAIL;
1376 
1377   aSTG.tymed = TYMED_HGLOBAL;
1378   aSTG.pUnkForRelease = nullptr;
1379 
1380   nsAutoString path;
1381   rv = file->GetPath(path);
1382   if (NS_FAILED(rv)) return E_FAIL;
1383 
1384   uint32_t allocLen = path.Length() + 2;
1385   HGLOBAL hGlobalMemory = nullptr;
1386   char16_t* dest;
1387 
1388   hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1389                               sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1390   if (!hGlobalMemory) return E_FAIL;
1391 
1392   DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1393 
1394   // First, populate the drop file structure
1395   pDropFile->pFiles = sizeof(DROPFILES);  // Offset to start of file name string
1396   pDropFile->fNC = 0;
1397   pDropFile->pt.x = 0;
1398   pDropFile->pt.y = 0;
1399   pDropFile->fWide = TRUE;
1400 
1401   // Copy the filename right after the DROPFILES structure
1402   dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1403   memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
1404 
1405   // Two null characters are needed at the end of the file name.
1406   // Lookup the CF_HDROP shell clipboard format for more info.
1407   // Add the second null character right after the first one.
1408   dest[allocLen - 1] = L'\0';
1409 
1410   GlobalUnlock(hGlobalMemory);
1411 
1412   aSTG.hGlobal = hGlobalMemory;
1413 
1414   return S_OK;
1415 }
1416 
DropImage(FORMATETC & aFE,STGMEDIUM & aSTG)1417 HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
1418   nsresult rv;
1419   if (!mCachedTempFile) {
1420     uint32_t len = 0;
1421     nsCOMPtr<nsISupports> genericDataWrapper;
1422 
1423     mTransferable->GetTransferData(kNativeImageMime,
1424                                    getter_AddRefs(genericDataWrapper), &len);
1425     nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
1426 
1427     if (!image) {
1428       // Check if the image was put in an nsISupportsInterfacePointer wrapper.
1429       // This might not be necessary any more, but could be useful for backwards
1430       // compatibility.
1431       nsCOMPtr<nsISupportsInterfacePointer> ptr(
1432           do_QueryInterface(genericDataWrapper));
1433       if (ptr) {
1434         nsCOMPtr<nsISupports> supports;
1435         ptr->GetData(getter_AddRefs(supports));
1436         image = do_QueryInterface(supports);
1437       }
1438     }
1439 
1440     if (!image) return E_FAIL;
1441 
1442     // Use the clipboard helper class to build up a memory bitmap.
1443     nsImageToClipboard converter(image);
1444     HANDLE bits = nullptr;
1445     rv = converter.GetPicture(
1446         &bits);  // Clipboard routines return a global handle we own.
1447 
1448     if (NS_FAILED(rv) || !bits) return E_FAIL;
1449 
1450     // We now own these bits!
1451     uint32_t bitmapSize = GlobalSize(bits);
1452     if (!bitmapSize) {
1453       GlobalFree(bits);
1454       return E_FAIL;
1455     }
1456 
1457     // Save the bitmap to a temporary location.
1458     nsCOMPtr<nsIFile> dropFile;
1459     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1460     if (!dropFile) {
1461       GlobalFree(bits);
1462       return E_FAIL;
1463     }
1464 
1465     // Filename must be random so as not to confuse apps like
1466     // Photoshop which handle multiple drags into a single window.
1467     char buf[13];
1468     nsCString filename;
1469     NS_MakeRandomString(buf, 8);
1470     memcpy(buf + 8, ".bmp", 5);
1471     filename.Append(nsDependentCString(buf, 12));
1472     dropFile->AppendNative(filename);
1473     rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1474     if (NS_FAILED(rv)) {
1475       GlobalFree(bits);
1476       return E_FAIL;
1477     }
1478 
1479     // Cache the temp file so we can delete it later and so
1480     // it doesn't get recreated over and over on multiple calls
1481     // which does occur from windows shell.
1482     dropFile->Clone(getter_AddRefs(mCachedTempFile));
1483 
1484     // Write the data to disk.
1485     nsCOMPtr<nsIOutputStream> outStream;
1486     rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1487     if (NS_FAILED(rv)) {
1488       GlobalFree(bits);
1489       return E_FAIL;
1490     }
1491 
1492     char* bm = (char*)GlobalLock(bits);
1493 
1494     BITMAPFILEHEADER fileHdr;
1495     BITMAPINFOHEADER* bmpHdr = (BITMAPINFOHEADER*)bm;
1496 
1497     fileHdr.bfType = ((WORD)('M' << 8) | 'B');
1498     fileHdr.bfSize = GlobalSize(bits) + sizeof(fileHdr);
1499     fileHdr.bfReserved1 = 0;
1500     fileHdr.bfReserved2 = 0;
1501     fileHdr.bfOffBits = (DWORD)(sizeof(fileHdr) + bmpHdr->biSize);
1502 
1503     uint32_t writeCount = 0;
1504     if (NS_FAILED(outStream->Write((const char*)&fileHdr, sizeof(fileHdr),
1505                                    &writeCount)) ||
1506         NS_FAILED(outStream->Write((const char*)bm, bitmapSize, &writeCount)))
1507       rv = NS_ERROR_FAILURE;
1508 
1509     outStream->Close();
1510 
1511     GlobalUnlock(bits);
1512     GlobalFree(bits);
1513 
1514     if (NS_FAILED(rv)) return E_FAIL;
1515   }
1516 
1517   // Pass the file name back to the drop target so that it can access the file.
1518   nsAutoString path;
1519   rv = mCachedTempFile->GetPath(path);
1520   if (NS_FAILED(rv)) return E_FAIL;
1521 
1522   // Two null characters are needed to terminate the file name list.
1523   HGLOBAL hGlobalMemory = nullptr;
1524 
1525   uint32_t allocLen = path.Length() + 2;
1526 
1527   aSTG.tymed = TYMED_HGLOBAL;
1528   aSTG.pUnkForRelease = nullptr;
1529 
1530   hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1531                               sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1532   if (!hGlobalMemory) return E_FAIL;
1533 
1534   DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1535 
1536   // First, populate the drop file structure.
1537   pDropFile->pFiles =
1538       sizeof(DROPFILES);  // Offset to start of file name char array.
1539   pDropFile->fNC = 0;
1540   pDropFile->pt.x = 0;
1541   pDropFile->pt.y = 0;
1542   pDropFile->fWide = TRUE;
1543 
1544   // Copy the filename right after the DROPFILES structure.
1545   char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1546   memcpy(dest, path.get(),
1547          (allocLen - 1) *
1548              sizeof(char16_t));  // Copies the null character in path as well.
1549 
1550   // Two null characters are needed at the end of the file name.
1551   // Lookup the CF_HDROP shell clipboard format for more info.
1552   // Add the second null character right after the first one.
1553   dest[allocLen - 1] = L'\0';
1554 
1555   GlobalUnlock(hGlobalMemory);
1556 
1557   aSTG.hGlobal = hGlobalMemory;
1558 
1559   return S_OK;
1560 }
1561 
DropTempFile(FORMATETC & aFE,STGMEDIUM & aSTG)1562 HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1563   nsresult rv;
1564   if (!mCachedTempFile) {
1565     // Tempfile will need a temporary location.
1566     nsCOMPtr<nsIFile> dropFile;
1567     rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1568     if (!dropFile) return E_FAIL;
1569 
1570     // Filename must be random
1571     nsCString filename;
1572     nsAutoString wideFileName;
1573     nsCOMPtr<nsIURI> sourceURI;
1574     HRESULT res;
1575     res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
1576     if (FAILED(res)) return res;
1577     NS_CopyUnicodeToNative(wideFileName, filename);
1578 
1579     dropFile->AppendNative(filename);
1580     rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1581     if (NS_FAILED(rv)) return E_FAIL;
1582 
1583     // Cache the temp file so we can delete it later and so
1584     // it doesn't get recreated over and over on multiple calls
1585     // which does occur from windows shell.
1586     dropFile->Clone(getter_AddRefs(mCachedTempFile));
1587 
1588     // Write the data to disk.
1589     nsCOMPtr<nsIOutputStream> outStream;
1590     rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1591     if (NS_FAILED(rv)) return E_FAIL;
1592 
1593     IStream* pStream = nullptr;
1594     nsDataObj::CreateStream(&pStream);
1595     NS_ENSURE_TRUE(pStream, E_FAIL);
1596 
1597     char buffer[512];
1598     ULONG readCount = 0;
1599     uint32_t writeCount = 0;
1600     while (1) {
1601       HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
1602       if (FAILED(hres)) return E_FAIL;
1603       if (readCount == 0) break;
1604       rv = outStream->Write(buffer, readCount, &writeCount);
1605       if (NS_FAILED(rv)) return E_FAIL;
1606     }
1607     outStream->Close();
1608     pStream->Release();
1609   }
1610 
1611   // Pass the file name back to the drop target so that it can access the file.
1612   nsAutoString path;
1613   rv = mCachedTempFile->GetPath(path);
1614   if (NS_FAILED(rv)) return E_FAIL;
1615 
1616   uint32_t allocLen = path.Length() + 2;
1617 
1618   // Two null characters are needed to terminate the file name list.
1619   HGLOBAL hGlobalMemory = nullptr;
1620 
1621   aSTG.tymed = TYMED_HGLOBAL;
1622   aSTG.pUnkForRelease = nullptr;
1623 
1624   hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1625                               sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1626   if (!hGlobalMemory) return E_FAIL;
1627 
1628   DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1629 
1630   // First, populate the drop file structure.
1631   pDropFile->pFiles =
1632       sizeof(DROPFILES);  // Offset to start of file name char array.
1633   pDropFile->fNC = 0;
1634   pDropFile->pt.x = 0;
1635   pDropFile->pt.y = 0;
1636   pDropFile->fWide = TRUE;
1637 
1638   // Copy the filename right after the DROPFILES structure.
1639   char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1640   memcpy(dest, path.get(),
1641          (allocLen - 1) *
1642              sizeof(char16_t));  // Copies the null character in path as well.
1643 
1644   // Two null characters are needed at the end of the file name.
1645   // Lookup the CF_HDROP shell clipboard format for more info.
1646   // Add the second null character right after the first one.
1647   dest[allocLen - 1] = L'\0';
1648 
1649   GlobalUnlock(hGlobalMemory);
1650 
1651   aSTG.hGlobal = hGlobalMemory;
1652 
1653   return S_OK;
1654 }
1655 
1656 //-----------------------------------------------------
GetMetafilePict(FORMATETC &,STGMEDIUM &)1657 HRESULT nsDataObj::GetMetafilePict(FORMATETC&, STGMEDIUM&) { return E_NOTIMPL; }
1658 
1659 //-----------------------------------------------------
SetBitmap(FORMATETC &,STGMEDIUM &)1660 HRESULT nsDataObj::SetBitmap(FORMATETC&, STGMEDIUM&) { return E_NOTIMPL; }
1661 
1662 //-----------------------------------------------------
SetDib(FORMATETC &,STGMEDIUM &)1663 HRESULT nsDataObj::SetDib(FORMATETC&, STGMEDIUM&) { return E_FAIL; }
1664 
1665 //-----------------------------------------------------
SetText(FORMATETC & aFE,STGMEDIUM & aSTG)1666 HRESULT nsDataObj::SetText(FORMATETC& aFE, STGMEDIUM& aSTG) { return E_FAIL; }
1667 
1668 //-----------------------------------------------------
SetMetafilePict(FORMATETC &,STGMEDIUM &)1669 HRESULT nsDataObj::SetMetafilePict(FORMATETC&, STGMEDIUM&) { return E_FAIL; }
1670 
1671 //-----------------------------------------------------
1672 //-----------------------------------------------------
GetClassID() const1673 CLSID nsDataObj::GetClassID() const { return CLSID_nsDataObj; }
1674 
1675 //-----------------------------------------------------
1676 // Registers the DataFlavor/FE pair.
1677 //-----------------------------------------------------
AddDataFlavor(const char * aDataFlavor,LPFORMATETC aFE)1678 void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
1679   // These two lists are the mapping to and from data flavors and FEs.
1680   // Later, OLE will tell us it needs a certain type of FORMATETC (text,
1681   // unicode, etc) unicode, etc), so we will look up the data flavor that
1682   // corresponds to the FE and then ask the transferable for that type of data.
1683   mDataFlavors.AppendElement(aDataFlavor);
1684   m_enumFE->AddFormatEtc(aFE);
1685 }
1686 
1687 //-----------------------------------------------------
1688 // Sets the transferable object
1689 //-----------------------------------------------------
SetTransferable(nsITransferable * aTransferable)1690 void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
1691   NS_IF_RELEASE(mTransferable);
1692 
1693   mTransferable = aTransferable;
1694   if (nullptr == mTransferable) {
1695     return;
1696   }
1697 
1698   NS_ADDREF(mTransferable);
1699 
1700   return;
1701 }
1702 
1703 //
1704 // ExtractURL
1705 //
1706 // Roots around in the transferable for the appropriate flavor that indicates
1707 // a url and pulls out the url portion of the data. Used mostly for creating
1708 // internet shortcuts on the desktop. The url flavor is of the format:
1709 //
1710 //   <url> <linefeed> <page title>
1711 //
ExtractShortcutURL(nsString & outURL)1712 nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
1713   NS_ASSERTION(mTransferable, "We don't have a good transferable");
1714   nsresult rv = NS_ERROR_FAILURE;
1715 
1716   uint32_t len = 0;
1717   nsCOMPtr<nsISupports> genericURL;
1718   if (NS_SUCCEEDED(mTransferable->GetTransferData(
1719           kURLMime, getter_AddRefs(genericURL), &len))) {
1720     nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1721     if (urlObject) {
1722       nsAutoString url;
1723       urlObject->GetData(url);
1724       outURL = url;
1725 
1726       // find the first linefeed in the data, that's where the url ends. trunc
1727       // the result string at that point.
1728       int32_t lineIndex = outURL.FindChar('\n');
1729       NS_ASSERTION(lineIndex > 0,
1730                    "Format for url flavor is <url> <linefeed> <page title>");
1731       if (lineIndex > 0) {
1732         outURL.Truncate(lineIndex);
1733         rv = NS_OK;
1734       }
1735     }
1736   } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
1737                  kURLDataMime, getter_AddRefs(genericURL), &len)) ||
1738              NS_SUCCEEDED(mTransferable->GetTransferData(
1739                  kURLPrivateMime, getter_AddRefs(genericURL), &len))) {
1740     nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1741     if (urlObject) {
1742       nsAutoString url;
1743       urlObject->GetData(url);
1744       outURL = url;
1745 
1746       rv = NS_OK;
1747     }
1748 
1749   }  // if found flavor
1750 
1751   return rv;
1752 
1753 }  // ExtractShortcutURL
1754 
1755 //
1756 // ExtractShortcutTitle
1757 //
1758 // Roots around in the transferable for the appropriate flavor that indicates
1759 // a url and pulls out the title portion of the data. Used mostly for creating
1760 // internet shortcuts on the desktop. The url flavor is of the format:
1761 //
1762 //   <url> <linefeed> <page title>
1763 //
ExtractShortcutTitle(nsString & outTitle)1764 nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
1765   NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
1766   nsresult rv = NS_ERROR_FAILURE;
1767 
1768   uint32_t len = 0;
1769   nsCOMPtr<nsISupports> genericURL;
1770   if (NS_SUCCEEDED(mTransferable->GetTransferData(
1771           kURLMime, getter_AddRefs(genericURL), &len))) {
1772     nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1773     if (urlObject) {
1774       nsAutoString url;
1775       urlObject->GetData(url);
1776 
1777       // find the first linefeed in the data, that's where the url ends. we want
1778       // everything after that linefeed. FindChar() returns -1 if we can't find
1779       int32_t lineIndex = url.FindChar('\n');
1780       NS_ASSERTION(lineIndex != -1,
1781                    "Format for url flavor is <url> <linefeed> <page title>");
1782       if (lineIndex != -1) {
1783         url.Mid(outTitle, lineIndex + 1, (len / 2) - (lineIndex + 1));
1784         rv = NS_OK;
1785       }
1786     }
1787   }  // if found flavor
1788 
1789   return rv;
1790 
1791 }  // ExtractShortcutTitle
1792 
1793 //
1794 // BuildPlatformHTML
1795 //
1796 // Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
1797 // header information on it. This will null terminate |outPlatformHTML|. See
1798 //  http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
1799 // for details.
1800 //
1801 // We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
1802 // or <BODY> tags). We'll wrap the fragment with them to make other apps
1803 // happy.
1804 //
BuildPlatformHTML(const char * inOurHTML,char ** outPlatformHTML)1805 nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
1806                                        char** outPlatformHTML) {
1807   *outPlatformHTML = nullptr;
1808 
1809   nsDependentCString inHTMLString(inOurHTML);
1810   const char* const numPlaceholder = "00000000";
1811   const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:";
1812   const char* const endHTMLPrefix = "\r\nEndHTML:";
1813   const char* const startFragPrefix = "\r\nStartFragment:";
1814   const char* const endFragPrefix = "\r\nEndFragment:";
1815   const char* const startSourceURLPrefix = "\r\nSourceURL:";
1816   const char* const endFragTrailer = "\r\n";
1817 
1818   // Do we already have mSourceURL from a drag?
1819   if (mSourceURL.IsEmpty()) {
1820     nsAutoString url;
1821     ExtractShortcutURL(url);
1822 
1823     AppendUTF16toUTF8(url, mSourceURL);
1824   }
1825 
1826   const int32_t kSourceURLLength = mSourceURL.Length();
1827   const int32_t kNumberLength = strlen(numPlaceholder);
1828 
1829   const int32_t kTotalHeaderLen =
1830       strlen(startHTMLPrefix) + strlen(endHTMLPrefix) +
1831       strlen(startFragPrefix) + strlen(endFragPrefix) + strlen(endFragTrailer) +
1832       (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) +
1833       kSourceURLLength + (4 * kNumberLength);
1834 
1835   NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "<html><body>\r\n");
1836 
1837   NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, "<!--StartFragment-->");
1838 
1839   nsDependentCString trailingString(
1840       "<!--EndFragment-->\r\n"
1841       "</body>\r\n"
1842       "</html>");
1843 
1844   // calculate the offsets
1845   int32_t startHTMLOffset = kTotalHeaderLen;
1846   int32_t startFragOffset = startHTMLOffset + htmlHeaderString.Length() +
1847                             fragmentHeaderString.Length();
1848 
1849   int32_t endFragOffset = startFragOffset + inHTMLString.Length();
1850 
1851   int32_t endHTMLOffset = endFragOffset + trailingString.Length();
1852 
1853   // now build the final version
1854   nsCString clipboardString;
1855   clipboardString.SetCapacity(endHTMLOffset);
1856 
1857   clipboardString.Append(startHTMLPrefix);
1858   clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset));
1859 
1860   clipboardString.Append(endHTMLPrefix);
1861   clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset));
1862 
1863   clipboardString.Append(startFragPrefix);
1864   clipboardString.Append(nsPrintfCString("%08u", startFragOffset));
1865 
1866   clipboardString.Append(endFragPrefix);
1867   clipboardString.Append(nsPrintfCString("%08u", endFragOffset));
1868 
1869   if (kSourceURLLength > 0) {
1870     clipboardString.Append(startSourceURLPrefix);
1871     clipboardString.Append(mSourceURL);
1872   }
1873 
1874   clipboardString.Append(endFragTrailer);
1875 
1876   clipboardString.Append(htmlHeaderString);
1877   clipboardString.Append(fragmentHeaderString);
1878   clipboardString.Append(inHTMLString);
1879   clipboardString.Append(trailingString);
1880 
1881   *outPlatformHTML = ToNewCString(clipboardString);
1882   if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
1883 
1884   return NS_OK;
1885 }
1886 
1887 HRESULT
GetUniformResourceLocator(FORMATETC & aFE,STGMEDIUM & aSTG,bool aIsUnicode)1888 nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
1889                                       bool aIsUnicode) {
1890   HRESULT res = S_OK;
1891   if (IsFlavourPresent(kURLMime)) {
1892     if (aIsUnicode)
1893       res = ExtractUniformResourceLocatorW(aFE, aSTG);
1894     else
1895       res = ExtractUniformResourceLocatorA(aFE, aSTG);
1896   } else
1897     NS_WARNING("Not yet implemented\n");
1898   return res;
1899 }
1900 
1901 HRESULT
ExtractUniformResourceLocatorA(FORMATETC & aFE,STGMEDIUM & aSTG)1902 nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
1903   HRESULT result = S_OK;
1904 
1905   nsAutoString url;
1906   if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1907 
1908   NS_LossyConvertUTF16toASCII asciiUrl(url);
1909   const int totalLen = asciiUrl.Length() + 1;
1910   HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
1911   if (!hGlobalMemory) return E_OUTOFMEMORY;
1912 
1913   char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
1914   if (!contents) {
1915     GlobalFree(hGlobalMemory);
1916     return E_OUTOFMEMORY;
1917   }
1918 
1919   strcpy(contents, asciiUrl.get());
1920   GlobalUnlock(hGlobalMemory);
1921   aSTG.hGlobal = hGlobalMemory;
1922   aSTG.tymed = TYMED_HGLOBAL;
1923 
1924   return result;
1925 }
1926 
1927 HRESULT
ExtractUniformResourceLocatorW(FORMATETC & aFE,STGMEDIUM & aSTG)1928 nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
1929   HRESULT result = S_OK;
1930 
1931   nsAutoString url;
1932   if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1933 
1934   const int totalLen = (url.Length() + 1) * sizeof(char16_t);
1935   HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
1936   if (!hGlobalMemory) return E_OUTOFMEMORY;
1937 
1938   wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
1939   if (!contents) {
1940     GlobalFree(hGlobalMemory);
1941     return E_OUTOFMEMORY;
1942   }
1943 
1944   wcscpy(contents, url.get());
1945   GlobalUnlock(hGlobalMemory);
1946   aSTG.hGlobal = hGlobalMemory;
1947   aSTG.tymed = TYMED_HGLOBAL;
1948 
1949   return result;
1950 }
1951 
1952 // Gets the filename from the kFilePromiseURLMime flavour
GetDownloadDetails(nsIURI ** aSourceURI,nsAString & aFilename)1953 HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
1954                                       nsAString& aFilename) {
1955   *aSourceURI = nullptr;
1956 
1957   NS_ENSURE_TRUE(mTransferable, E_FAIL);
1958 
1959   // get the URI from the kFilePromiseURLMime flavor
1960   nsCOMPtr<nsISupports> urlPrimitive;
1961   uint32_t dataSize = 0;
1962   mTransferable->GetTransferData(kFilePromiseURLMime,
1963                                  getter_AddRefs(urlPrimitive), &dataSize);
1964   nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
1965   NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
1966 
1967   nsAutoString srcUri;
1968   srcUrlPrimitive->GetData(srcUri);
1969   if (srcUri.IsEmpty()) return E_FAIL;
1970   nsCOMPtr<nsIURI> sourceURI;
1971   NS_NewURI(getter_AddRefs(sourceURI), srcUri);
1972 
1973   nsAutoString srcFileName;
1974   nsCOMPtr<nsISupports> fileNamePrimitive;
1975   mTransferable->GetTransferData(kFilePromiseDestFilename,
1976                                  getter_AddRefs(fileNamePrimitive), &dataSize);
1977   nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
1978       do_QueryInterface(fileNamePrimitive);
1979   if (srcFileNamePrimitive) {
1980     srcFileNamePrimitive->GetData(srcFileName);
1981   } else {
1982     nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
1983     if (!sourceURL) return E_FAIL;
1984 
1985     nsAutoCString urlFileName;
1986     sourceURL->GetFileName(urlFileName);
1987     NS_UnescapeURL(urlFileName);
1988     CopyUTF8toUTF16(urlFileName, srcFileName);
1989   }
1990   if (srcFileName.IsEmpty()) return E_FAIL;
1991 
1992   // make the name safe for the filesystem
1993   MangleTextToValidFilename(srcFileName);
1994 
1995   sourceURI.swap(*aSourceURI);
1996   aFilename = srcFileName;
1997   return S_OK;
1998 }
1999 
GetFileDescriptor_IStreamA(FORMATETC & aFE,STGMEDIUM & aSTG)2000 HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2001   HGLOBAL fileGroupDescHandle =
2002       ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2003   NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2004 
2005   LPFILEGROUPDESCRIPTORA fileGroupDescA =
2006       reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
2007   if (!fileGroupDescA) {
2008     ::GlobalFree(fileGroupDescHandle);
2009     return E_OUTOFMEMORY;
2010   }
2011 
2012   nsAutoString wideFileName;
2013   HRESULT res;
2014   nsCOMPtr<nsIURI> sourceURI;
2015   res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2016   if (FAILED(res)) {
2017     ::GlobalFree(fileGroupDescHandle);
2018     return res;
2019   }
2020 
2021   nsAutoCString nativeFileName;
2022   NS_CopyUnicodeToNative(wideFileName, nativeFileName);
2023 
2024   strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(),
2025           NS_MAX_FILEDESCRIPTOR - 1);
2026   fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
2027 
2028   // one file in the file block
2029   fileGroupDescA->cItems = 1;
2030   fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
2031 
2032   GlobalUnlock(fileGroupDescHandle);
2033   aSTG.hGlobal = fileGroupDescHandle;
2034   aSTG.tymed = TYMED_HGLOBAL;
2035 
2036   return S_OK;
2037 }
2038 
GetFileDescriptor_IStreamW(FORMATETC & aFE,STGMEDIUM & aSTG)2039 HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2040   HGLOBAL fileGroupDescHandle =
2041       ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2042   NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2043 
2044   LPFILEGROUPDESCRIPTORW fileGroupDescW =
2045       reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
2046   if (!fileGroupDescW) {
2047     ::GlobalFree(fileGroupDescHandle);
2048     return E_OUTOFMEMORY;
2049   }
2050 
2051   nsAutoString wideFileName;
2052   HRESULT res;
2053   nsCOMPtr<nsIURI> sourceURI;
2054   res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2055   if (FAILED(res)) {
2056     ::GlobalFree(fileGroupDescHandle);
2057     return res;
2058   }
2059 
2060   wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(),
2061           NS_MAX_FILEDESCRIPTOR - 1);
2062   fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
2063   // one file in the file block
2064   fileGroupDescW->cItems = 1;
2065   fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
2066 
2067   GlobalUnlock(fileGroupDescHandle);
2068   aSTG.hGlobal = fileGroupDescHandle;
2069   aSTG.tymed = TYMED_HGLOBAL;
2070 
2071   return S_OK;
2072 }
2073 
GetFileContents_IStream(FORMATETC & aFE,STGMEDIUM & aSTG)2074 HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
2075   IStream* pStream = nullptr;
2076 
2077   nsDataObj::CreateStream(&pStream);
2078   NS_ENSURE_TRUE(pStream, E_FAIL);
2079 
2080   aSTG.tymed = TYMED_ISTREAM;
2081   aSTG.pstm = pStream;
2082   aSTG.pUnkForRelease = nullptr;
2083 
2084   return S_OK;
2085 }
2086