Bug 1799258 - Fix constexpr issue on base toolchain builds. r=gfx-reviewers,lsalzman
[gecko.git] / widget / windows / nsDataObj.cpp
blob6112eab4a97798d127ae900592f025cee232465a
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/. */
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/TextUtils.h"
9 #include <ole2.h>
10 #include <shlobj.h>
12 #include "nsComponentManagerUtils.h"
13 #include "nsDataObj.h"
14 #include "nsArrayUtils.h"
15 #include "nsClipboard.h"
16 #include "nsReadableUtils.h"
17 #include "nsICookieJarSettings.h"
18 #include "nsITransferable.h"
19 #include "nsISupportsPrimitives.h"
20 #include "IEnumFE.h"
21 #include "nsPrimitiveHelpers.h"
22 #include "nsString.h"
23 #include "nsCRT.h"
24 #include "nsPrintfCString.h"
25 #include "nsIStringBundle.h"
26 #include "nsEscape.h"
27 #include "nsIURL.h"
28 #include "nsNetUtil.h"
29 #include "mozilla/Components.h"
30 #include "mozilla/SpinEventLoopUntil.h"
31 #include "mozilla/Unused.h"
32 #include "nsProxyRelease.h"
33 #include "nsIObserverService.h"
34 #include "nsIOutputStream.h"
35 #include "nscore.h"
36 #include "nsDirectoryServiceDefs.h"
37 #include "nsITimer.h"
38 #include "nsThreadUtils.h"
39 #include "mozilla/Preferences.h"
40 #include "nsContentUtils.h"
41 #include "nsIPrincipal.h"
42 #include "nsNativeCharsetUtils.h"
43 #include "nsMimeTypes.h"
44 #include "nsIMIMEService.h"
45 #include "imgIEncoder.h"
46 #include "imgITools.h"
47 #include "WinUtils.h"
48 #include "nsLocalFile.h"
50 #include "mozilla/LazyIdleThread.h"
51 #include <algorithm>
53 using namespace mozilla;
54 using namespace mozilla::glue;
55 using namespace mozilla::widget;
57 #define BFH_LENGTH 14
58 #define DEFAULT_THREAD_TIMEOUT_MS 30000
60 //-----------------------------------------------------------------------------
61 // CStreamBase implementation
62 nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {}
64 //-----------------------------------------------------------------------------
65 nsDataObj::CStreamBase::~CStreamBase() {}
67 NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
69 //-----------------------------------------------------------------------------
70 // CStream implementation
71 nsDataObj::CStream::CStream() : mChannelRead(false) {}
73 //-----------------------------------------------------------------------------
74 nsDataObj::CStream::~CStream() {}
76 //-----------------------------------------------------------------------------
77 // helper - initializes the stream
78 nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
79 nsContentPolicyType aContentPolicyType,
80 nsIPrincipal* aRequestingPrincipal,
81 nsICookieJarSettings* aCookieJarSettings) {
82 // we can not create a channel without a requestingPrincipal
83 if (!aRequestingPrincipal) {
84 return NS_ERROR_FAILURE;
86 nsresult rv;
87 rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
88 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
89 aContentPolicyType, aCookieJarSettings,
90 nullptr, // PerformanceStorage
91 nullptr, // loadGroup
92 nullptr, // aCallbacks
93 nsIRequest::LOAD_FROM_CACHE);
95 NS_ENSURE_SUCCESS(rv, rv);
96 rv = mChannel->AsyncOpen(this);
97 NS_ENSURE_SUCCESS(rv, rv);
98 return NS_OK;
101 //-----------------------------------------------------------------------------
102 // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
103 // IUnknown and nsIStreamListener.
104 STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
105 void** ppvResult) {
106 *ppvResult = nullptr;
107 if (IID_IUnknown == refiid || refiid == IID_IStream)
110 *ppvResult = this;
113 if (nullptr != *ppvResult) {
114 ((LPUNKNOWN)*ppvResult)->AddRef();
115 return S_OK;
118 return E_NOINTERFACE;
121 // nsIStreamListener implementation
122 NS_IMETHODIMP
123 nsDataObj::CStream::OnDataAvailable(
124 nsIRequest* aRequest, nsIInputStream* aInputStream,
125 uint64_t aOffset, // offset within the stream
126 uint32_t aCount) // bytes available on this call
128 // Extend the write buffer for the incoming data.
129 uint8_t* buffer = mChannelData.AppendElements(aCount, fallible);
130 if (!buffer) {
131 return NS_ERROR_OUT_OF_MEMORY;
133 NS_ASSERTION((mChannelData.Length() == (aOffset + aCount)),
134 "stream length mismatch w/write buffer");
136 // Read() may not return aCount on a single call, so loop until we've
137 // accumulated all the data OnDataAvailable has promised.
138 nsresult rv;
139 uint32_t odaBytesReadTotal = 0;
140 do {
141 uint32_t bytesReadByCall = 0;
142 rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), aCount,
143 &bytesReadByCall);
144 odaBytesReadTotal += bytesReadByCall;
145 } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv));
146 return rv;
149 NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest) {
150 mChannelResult = NS_OK;
151 return NS_OK;
154 NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
155 nsresult aStatusCode) {
156 mChannelRead = true;
157 mChannelResult = aStatusCode;
158 return NS_OK;
161 // Pumps thread messages while waiting for the async listener operation to
162 // complete. Failing this call will fail the stream incall from Windows
163 // and cancel the operation.
164 nsresult nsDataObj::CStream::WaitForCompletion() {
165 // We are guaranteed OnStopRequest will get called, so this should be ok.
166 SpinEventLoopUntil("widget:nsDataObj::CStream::WaitForCompletion"_ns,
167 [&]() { return mChannelRead; });
169 if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
171 return mChannelResult;
174 //-----------------------------------------------------------------------------
175 // IStream
176 STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) {
177 return E_NOTIMPL;
180 //-----------------------------------------------------------------------------
181 STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; }
183 //-----------------------------------------------------------------------------
184 STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream,
185 ULARGE_INTEGER nBytesToCopy,
186 ULARGE_INTEGER* nBytesRead,
187 ULARGE_INTEGER* nBytesWritten) {
188 return E_NOTIMPL;
191 //-----------------------------------------------------------------------------
192 STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart,
193 ULARGE_INTEGER nBytes,
194 DWORD dwFlags) {
195 return E_NOTIMPL;
198 //-----------------------------------------------------------------------------
199 STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
200 ULONG* nBytesRead) {
201 // Wait for the write into our buffer to complete via the stream listener.
202 // We can't respond to this by saying "call us back later".
203 if (NS_FAILED(WaitForCompletion())) return E_FAIL;
205 // Bytes left for Windows to read out of our buffer
206 ULONG bytesLeft = mChannelData.Length() - mStreamRead;
207 // Let Windows know what we will hand back, usually this is the entire buffer
208 *nBytesRead = std::min(bytesLeft, nBytesToRead);
209 // Copy the buffer data over
210 memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
211 // Update our bytes read tracking
212 mStreamRead += *nBytesRead;
213 return S_OK;
216 //-----------------------------------------------------------------------------
217 STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; }
219 //-----------------------------------------------------------------------------
220 STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
221 ULARGE_INTEGER* nNewPos) {
222 if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
224 if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
225 (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
226 nNewPos->LowPart = 0;
227 nNewPos->HighPart = 0;
228 return S_OK;
231 return E_NOTIMPL;
234 //-----------------------------------------------------------------------------
235 STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) {
236 return E_NOTIMPL;
239 //-----------------------------------------------------------------------------
240 STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
241 if (statstg == nullptr) return STG_E_INVALIDPOINTER;
243 if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
245 memset((void*)statstg, 0, sizeof(STATSTG));
247 if (dwFlags != STATFLAG_NONAME) {
248 nsCOMPtr<nsIURI> sourceURI;
249 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
250 return E_FAIL;
253 nsAutoCString strFileName;
254 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
255 sourceURL->GetFileName(strFileName);
257 if (strFileName.IsEmpty()) return E_FAIL;
259 NS_UnescapeURL(strFileName);
260 NS_ConvertUTF8toUTF16 wideFileName(strFileName);
262 uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
263 void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller
264 if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
266 ZeroMemory(retBuf, nMaxNameLength);
267 memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
268 statstg->pwcsName = (LPOLESTR)retBuf;
271 SYSTEMTIME st;
273 statstg->type = STGTY_STREAM;
275 GetSystemTime(&st);
276 SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
277 statstg->ctime = statstg->atime = statstg->mtime;
279 statstg->cbSize.QuadPart = mChannelData.Length();
280 statstg->grfMode = STGM_READ;
281 statstg->grfLocksSupported = LOCK_ONLYONCE;
282 statstg->clsid = CLSID_NULL;
284 return S_OK;
287 //-----------------------------------------------------------------------------
288 STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart,
289 ULARGE_INTEGER nBytes,
290 DWORD dwFlags) {
291 return E_NOTIMPL;
294 //-----------------------------------------------------------------------------
295 STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer,
296 ULONG nBytesToRead,
297 ULONG* nBytesRead) {
298 return E_NOTIMPL;
301 //-----------------------------------------------------------------------------
302 HRESULT nsDataObj::CreateStream(IStream** outStream) {
303 NS_ENSURE_TRUE(outStream, E_INVALIDARG);
305 nsresult rv = NS_ERROR_FAILURE;
306 nsAutoString wideFileName;
307 nsCOMPtr<nsIURI> sourceURI;
308 HRESULT res;
310 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
311 if (FAILED(res)) return res;
313 nsDataObj::CStream* pStream = new nsDataObj::CStream();
314 NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
316 pStream->AddRef();
318 // query the requestingPrincipal from the transferable and add it to the new
319 // channel
320 nsCOMPtr<nsIPrincipal> requestingPrincipal =
321 mTransferable->GetRequestingPrincipal();
322 MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
324 // Note that the cookieJarSettings could be null if the data object is for the
325 // image copy. We will fix this in Bug 1690532.
326 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
327 mTransferable->GetCookieJarSettings();
329 nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType();
330 rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal,
331 cookieJarSettings);
332 if (NS_FAILED(rv)) {
333 pStream->Release();
334 return E_FAIL;
336 *outStream = pStream;
338 return S_OK;
341 //-----------------------------------------------------------------------------
342 // AutoCloseEvent implementation
343 nsDataObj::AutoCloseEvent::AutoCloseEvent()
344 : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}
346 bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; }
348 void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); }
350 DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const {
351 return ::WaitForSingleObject(mEvent, aMillisec);
354 //-----------------------------------------------------------------------------
355 // AutoSetEvent implementation
356 nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent)
357 : mEvent(aEvent) {}
359 nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); }
361 void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); }
363 bool nsDataObj::AutoSetEvent::IsWaiting() const {
364 return mEvent->Wait(0) == WAIT_TIMEOUT;
367 //-----------------------------------------------------------------------------
368 // CMemStream implementation
369 Win32SRWLock nsDataObj::CMemStream::mLock;
371 //-----------------------------------------------------------------------------
372 nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength,
373 already_AddRefed<AutoCloseEvent> aEvent)
374 : mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) {
375 ::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler));
378 //-----------------------------------------------------------------------------
379 nsDataObj::CMemStream::~CMemStream() {}
381 //-----------------------------------------------------------------------------
382 // IUnknown
383 STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid,
384 void** ppvResult) {
385 *ppvResult = nullptr;
386 if (refiid == IID_IUnknown || refiid == IID_IStream ||
387 refiid == IID_IAgileObject) {
388 *ppvResult = this;
389 } else if (refiid == IID_IMarshal && mMarshaler) {
390 return mMarshaler->QueryInterface(refiid, ppvResult);
393 if (nullptr != *ppvResult) {
394 ((LPUNKNOWN)*ppvResult)->AddRef();
395 return S_OK;
398 return E_NOINTERFACE;
401 void nsDataObj::CMemStream::WaitForCompletion() {
402 if (!mEvent) {
403 // We are not waiting for obtaining the icon cache.
404 return;
406 if (!NS_IsMainThread()) {
407 mEvent->Wait(INFINITE);
408 } else {
409 // We should not block the main thread.
410 mEvent->Signal();
412 // mEvent will always be in the signaled state here.
415 //-----------------------------------------------------------------------------
416 // IStream
417 STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead,
418 ULONG* nBytesRead) {
419 // Wait until the event is signaled.
420 WaitForCompletion();
422 AutoExclusiveLock lock(mLock);
423 char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get()));
424 if (!contents) {
425 return E_OUTOFMEMORY;
428 // Bytes left for Windows to read out of our buffer
429 ULONG bytesLeft = mTotalLength - mStreamRead;
430 // Let Windows know what we will hand back, usually this is the entire buffer
431 *nBytesRead = std::min(bytesLeft, nBytesToRead);
432 // Copy the buffer data over
433 memcpy(pvBuffer, contents + mStreamRead, *nBytesRead);
434 // Update our bytes read tracking
435 mStreamRead += *nBytesRead;
437 GlobalUnlock(mGlobalMem.get());
438 return S_OK;
441 //-----------------------------------------------------------------------------
442 STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) {
443 if (statstg == nullptr) return STG_E_INVALIDPOINTER;
445 memset((void*)statstg, 0, sizeof(STATSTG));
447 if (dwFlags != STATFLAG_NONAME) {
448 constexpr size_t kMaxNameLength = sizeof(wchar_t);
449 void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller
450 if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
452 ZeroMemory(retBuf, kMaxNameLength);
453 statstg->pwcsName = (LPOLESTR)retBuf;
456 SYSTEMTIME st;
458 statstg->type = STGTY_STREAM;
460 GetSystemTime(&st);
461 SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
462 statstg->ctime = statstg->atime = statstg->mtime;
464 statstg->cbSize.QuadPart = mTotalLength;
465 statstg->grfMode = STGM_READ;
466 statstg->grfLocksSupported = LOCK_ONLYONCE;
467 statstg->clsid = CLSID_NULL;
469 return S_OK;
473 * Class nsDataObj
476 //-----------------------------------------------------
477 // construction
478 //-----------------------------------------------------
479 nsDataObj::nsDataObj(nsIURI* uri)
480 : m_cRef(0),
481 mTransferable(nullptr),
482 mIsAsyncMode(FALSE),
483 mIsInOperation(FALSE) {
484 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj",
485 LazyIdleThread::ManualShutdown);
486 m_enumFE = new CEnumFormatEtc();
487 m_enumFE->AddRef();
489 if (uri) {
490 // A URI was obtained, so pass this through to the DataObject
491 // so it can create a SourceURL for CF_HTML flavour
492 uri->GetSpec(mSourceURL);
495 //-----------------------------------------------------
496 // destruction
497 //-----------------------------------------------------
498 nsDataObj::~nsDataObj() {
499 NS_IF_RELEASE(mTransferable);
501 mDataFlavors.Clear();
503 m_enumFE->Release();
505 // Free arbitrary system formats
506 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
507 CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
508 ReleaseStgMedium(&mDataEntryList[idx]->stgm);
509 CoTaskMemFree(mDataEntryList[idx]);
513 //-----------------------------------------------------
514 // IUnknown interface methods - see inknown.h for documentation
515 //-----------------------------------------------------
516 STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
517 *ppv = nullptr;
519 if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
520 *ppv = this;
521 AddRef();
522 return S_OK;
523 } else if (IID_IDataObjectAsyncCapability == riid) {
524 *ppv = static_cast<IDataObjectAsyncCapability*>(this);
525 AddRef();
526 return S_OK;
529 return E_NOINTERFACE;
532 //-----------------------------------------------------
533 STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
534 ++m_cRef;
535 NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
537 // When the first reference is taken, hold our own internal reference.
538 if (m_cRef == 1) {
539 mKeepAlive = this;
542 return m_cRef;
545 namespace {
546 class RemoveTempFileHelper final : public nsIObserver, public nsINamed {
547 public:
548 explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
549 MOZ_ASSERT(mTempFile);
552 // The attach method is seperate from the constructor as we may be addref-ing
553 // ourself, and we want to be sure someone has a strong reference to us.
554 void Attach() {
555 // We need to listen to both the xpcom shutdown message and our timer, and
556 // fire when the first of either of these two messages is received.
557 nsresult rv;
558 rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
559 nsITimer::TYPE_ONE_SHOT);
560 if (NS_WARN_IF(NS_FAILED(rv))) {
561 return;
564 nsCOMPtr<nsIObserverService> observerService =
565 do_GetService("@mozilla.org/observer-service;1");
566 if (NS_WARN_IF(!observerService)) {
567 mTimer->Cancel();
568 mTimer = nullptr;
569 return;
571 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
574 NS_DECL_ISUPPORTS
575 NS_DECL_NSIOBSERVER
576 NS_DECL_NSINAMED
578 private:
579 ~RemoveTempFileHelper() {
580 if (mTempFile) {
581 mTempFile->Remove(false);
585 nsCOMPtr<nsIFile> mTempFile;
586 nsCOMPtr<nsITimer> mTimer;
589 NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver, nsINamed);
591 NS_IMETHODIMP
592 RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
593 const char16_t* aData) {
594 // Let's be careful and make sure that we don't die immediately
595 RefPtr<RemoveTempFileHelper> grip = this;
597 // Make sure that we aren't called again by destroying references to ourself.
598 nsCOMPtr<nsIObserverService> observerService =
599 do_GetService("@mozilla.org/observer-service;1");
600 if (observerService) {
601 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
604 if (mTimer) {
605 mTimer->Cancel();
606 mTimer = nullptr;
609 // Remove the tempfile
610 if (mTempFile) {
611 mTempFile->Remove(false);
612 mTempFile = nullptr;
614 return NS_OK;
617 NS_IMETHODIMP
618 RemoveTempFileHelper::GetName(nsACString& aName) {
619 aName.AssignLiteral("RemoveTempFileHelper");
620 return NS_OK;
622 } // namespace
624 //-----------------------------------------------------
625 STDMETHODIMP_(ULONG) nsDataObj::Release() {
626 --m_cRef;
628 NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
630 // If we hold the last reference, submit release of it to the main thread.
631 if (m_cRef == 1 && mKeepAlive) {
632 NS_ReleaseOnMainThread("nsDataObj release", mKeepAlive.forget(), true);
635 if (0 != m_cRef) return m_cRef;
637 // We have released our last ref on this object and need to delete the
638 // temp file. External app acting as drop target may still need to open the
639 // temp file. Addref a timer so it can delay deleting file and destroying
640 // this object.
641 if (mCachedTempFile) {
642 RefPtr<RemoveTempFileHelper> helper =
643 new RemoveTempFileHelper(mCachedTempFile);
644 mCachedTempFile = nullptr;
645 helper->Attach();
648 // In case the destructor ever AddRef/Releases, ensure we don't delete twice
649 // or take mKeepAlive as another reference.
650 m_cRef = 1;
652 delete this;
654 return 0;
657 //-----------------------------------------------------
658 BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
659 const FORMATETC& target) const {
660 if ((source.cfFormat == target.cfFormat) &&
661 (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
662 return TRUE;
663 } else {
664 return FALSE;
668 //-----------------------------------------------------
669 // IDataObject methods
670 //-----------------------------------------------------
671 STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
672 if (!mTransferable) return DV_E_FORMATETC;
674 // Hold an extra reference in case we end up spinning the event loop.
675 RefPtr<nsDataObj> keepAliveDuringGetData(this);
677 uint32_t dfInx = 0;
679 static CLIPFORMAT fileDescriptorFlavorA =
680 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
681 static CLIPFORMAT fileDescriptorFlavorW =
682 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
683 static CLIPFORMAT uniformResourceLocatorA =
684 ::RegisterClipboardFormat(CFSTR_INETURLA);
685 static CLIPFORMAT uniformResourceLocatorW =
686 ::RegisterClipboardFormat(CFSTR_INETURLW);
687 static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
688 static CLIPFORMAT PreferredDropEffect =
689 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
691 // Arbitrary system formats are used for image feedback during drag
692 // and drop. We are responsible for storing these internally during
693 // drag operations.
694 LPDATAENTRY pde;
695 if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
696 return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
697 : E_UNEXPECTED;
700 // Firefox internal formats
701 ULONG count;
702 FORMATETC fe;
703 m_enumFE->Reset();
704 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
705 dfInx < mDataFlavors.Length()) {
706 nsCString& df = mDataFlavors.ElementAt(dfInx);
707 if (FormatsMatch(fe, *aFormat)) {
708 pSTM->pUnkForRelease =
709 nullptr; // caller is responsible for deleting this data
710 CLIPFORMAT format = aFormat->cfFormat;
711 switch (format) {
712 // Someone is asking for plain or unicode text
713 case CF_TEXT:
714 case CF_UNICODETEXT:
715 return GetText(df, *aFormat, *pSTM);
717 // Some 3rd party apps that receive drag and drop files from the browser
718 // window require support for this.
719 case CF_HDROP:
720 return GetFile(*aFormat, *pSTM);
722 // Someone is asking for an image
723 case CF_DIBV5:
724 case CF_DIB:
725 return GetDib(df, *aFormat, *pSTM);
727 default:
728 if (format == fileDescriptorFlavorA)
729 return GetFileDescriptor(*aFormat, *pSTM, false);
730 if (format == fileDescriptorFlavorW)
731 return GetFileDescriptor(*aFormat, *pSTM, true);
732 if (format == uniformResourceLocatorA)
733 return GetUniformResourceLocator(*aFormat, *pSTM, false);
734 if (format == uniformResourceLocatorW)
735 return GetUniformResourceLocator(*aFormat, *pSTM, true);
736 if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
737 if (format == PreferredDropEffect)
738 return GetPreferredDropEffect(*aFormat, *pSTM);
739 // MOZ_LOG(gWindowsLog, LogLevel::Info,
740 // ("***** nsDataObj::GetData - Unknown format %u\n", format));
741 return GetText(df, *aFormat, *pSTM);
742 } // switch
743 } // if
744 dfInx++;
745 } // while
747 return DATA_E_FORMATETC;
750 //-----------------------------------------------------
751 STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
752 return E_FAIL;
755 //-----------------------------------------------------
756 // Other objects querying to see if we support a
757 // particular format
758 //-----------------------------------------------------
759 STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
760 // Arbitrary system formats are used for image feedback during drag
761 // and drop. We are responsible for storing these internally during
762 // drag operations.
763 LPDATAENTRY pde;
764 if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
766 // Firefox internal formats
767 ULONG count;
768 FORMATETC fe;
769 m_enumFE->Reset();
770 while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
771 if (fe.cfFormat == pFE->cfFormat) {
772 return S_OK;
775 return E_FAIL;
778 //-----------------------------------------------------
779 STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
780 LPFORMATETC pFEOut) {
781 return E_NOTIMPL;
784 //-----------------------------------------------------
785 STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
786 BOOL shouldRel) {
787 // Arbitrary system formats are used for image feedback during drag
788 // and drop. We are responsible for storing these internally during
789 // drag operations.
790 LPDATAENTRY pde;
791 if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
792 // Release the old data the lookup handed us for this format. This
793 // may have been set in CopyMediumData when we originally stored the
794 // data.
795 if (pde->stgm.tymed) {
796 ReleaseStgMedium(&pde->stgm);
797 memset(&pde->stgm, 0, sizeof(STGMEDIUM));
800 bool result = true;
801 if (shouldRel) {
802 // If shouldRel is TRUE, the data object called owns the storage medium
803 // after the call returns. Store the incoming data in our data array for
804 // release when we are destroyed. This is the common case with arbitrary
805 // data from explorer.
806 pde->stgm = *aMedium;
807 } else {
808 // Copy the incoming data into our data array. (AFAICT, this never gets
809 // called with arbitrary formats for drag images.)
810 result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
812 pde->fe.tymed = pde->stgm.tymed;
814 return result ? S_OK : DV_E_TYMED;
817 if (shouldRel) ReleaseStgMedium(aMedium);
819 return S_OK;
822 bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
823 LPDATAENTRY* aDataEntry,
824 BOOL aAddorUpdate) {
825 *aDataEntry = nullptr;
827 if (aFormat->ptd != nullptr) return false;
829 // See if it's already in our list. If so return the data entry.
830 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
831 if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
832 mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
833 mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
834 if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
835 // If the caller requests we update, or if the
836 // medium type matches, return the entry.
837 *aDataEntry = mDataEntryList[idx];
838 return true;
839 } else {
840 // Medium does not match, not found.
841 return false;
846 if (!aAddorUpdate) return false;
848 // Add another entry to mDataEntryList
849 LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
850 if (!dataEntry) return false;
852 dataEntry->fe = *aFormat;
853 *aDataEntry = dataEntry;
854 memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
856 // Add this to our IEnumFORMATETC impl. so we can return it when
857 // it's requested.
858 m_enumFE->AddFormatEtc(aFormat);
860 // Store a copy internally in the arbitrary formats array.
861 mDataEntryList.AppendElement(dataEntry);
863 return true;
866 bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
867 LPFORMATETC aFormat, BOOL aSetData) {
868 STGMEDIUM stgmOut = *aMediumSrc;
870 switch (stgmOut.tymed) {
871 case TYMED_ISTREAM:
872 stgmOut.pstm->AddRef();
873 break;
874 case TYMED_ISTORAGE:
875 stgmOut.pstg->AddRef();
876 break;
877 case TYMED_HGLOBAL:
878 if (!aMediumSrc->pUnkForRelease) {
879 if (aSetData) {
880 if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
881 stgmOut.hGlobal =
882 OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
883 if (!stgmOut.hGlobal) return false;
884 } else {
885 // We are returning this data from LookupArbitraryFormat, indicate to
886 // the shell we hold it and will free it.
887 stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
890 break;
891 default:
892 return false;
895 if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
897 *aMediumDst = stgmOut;
899 return true;
902 //-----------------------------------------------------
903 STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
904 switch (dwDir) {
905 case DATADIR_GET:
906 m_enumFE->Clone(ppEnum);
907 break;
908 case DATADIR_SET:
909 // fall through
910 default:
911 *ppEnum = nullptr;
912 } // switch
914 if (nullptr == *ppEnum) return E_FAIL;
916 (*ppEnum)->Reset();
917 // Clone already AddRefed the result so don't addref it again.
918 return NOERROR;
921 //-----------------------------------------------------
922 STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
923 LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
924 return OLE_E_ADVISENOTSUPPORTED;
927 //-----------------------------------------------------
928 STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
929 return OLE_E_ADVISENOTSUPPORTED;
932 //-----------------------------------------------------
933 STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
934 return OLE_E_ADVISENOTSUPPORTED;
937 // IDataObjectAsyncCapability methods
938 STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
939 DWORD dwEffects) {
940 mIsInOperation = FALSE;
941 return S_OK;
944 STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
945 *pfIsOpAsync = mIsAsyncMode;
947 return S_OK;
950 STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
951 *pfInAsyncOp = mIsInOperation;
953 return S_OK;
956 STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
957 mIsAsyncMode = fDoOpAsync;
958 return S_OK;
961 STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
962 mIsInOperation = TRUE;
963 return S_OK;
967 // GetDIB
969 // Someone is asking for a bitmap. The data in the transferable will be a
970 // straight imgIContainer, so just QI it.
972 HRESULT
973 nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
974 STGMEDIUM& aSTG) {
975 nsCOMPtr<nsISupports> genericDataWrapper;
976 if (NS_FAILED(
977 mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
978 getter_AddRefs(genericDataWrapper)))) {
979 return E_FAIL;
982 nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
983 if (!image) {
984 return E_FAIL;
987 nsCOMPtr<imgITools> imgTools =
988 do_CreateInstance("@mozilla.org/image/tools;1");
990 nsAutoString options(u"bpp=32;"_ns);
991 if (aFormat.cfFormat == CF_DIBV5) {
992 options.AppendLiteral("version=5");
993 } else {
994 options.AppendLiteral("version=3");
997 nsCOMPtr<nsIInputStream> inputStream;
998 nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
999 options, getter_AddRefs(inputStream));
1000 if (NS_FAILED(rv) || !inputStream) {
1001 return E_FAIL;
1004 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1005 if (!encoder) {
1006 return E_FAIL;
1009 uint32_t size = 0;
1010 rv = encoder->GetImageBufferUsed(&size);
1011 if (NS_FAILED(rv) || size <= BFH_LENGTH) {
1012 return E_FAIL;
1015 char* src = nullptr;
1016 rv = encoder->GetImageBuffer(&src);
1017 if (NS_FAILED(rv) || !src) {
1018 return E_FAIL;
1021 // We don't want the file header.
1022 src += BFH_LENGTH;
1023 size -= BFH_LENGTH;
1025 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
1026 if (!glob) {
1027 return E_FAIL;
1030 char* dst = (char*)::GlobalLock(glob);
1031 ::CopyMemory(dst, src, size);
1032 ::GlobalUnlock(glob);
1034 aSTG.hGlobal = glob;
1035 aSTG.tymed = TYMED_HGLOBAL;
1036 return S_OK;
1040 // GetFileDescriptor
1043 HRESULT
1044 nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
1045 bool aIsUnicode) {
1046 HRESULT res = S_OK;
1048 // How we handle this depends on if we're dealing with an internet
1049 // shortcut, since those are done under the covers.
1050 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
1051 if (aIsUnicode)
1052 return GetFileDescriptor_IStreamW(aFE, aSTG);
1053 else
1054 return GetFileDescriptor_IStreamA(aFE, aSTG);
1055 } else if (IsFlavourPresent(kURLMime)) {
1056 if (aIsUnicode)
1057 res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
1058 else
1059 res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
1060 } else
1061 NS_WARNING("Not yet implemented\n");
1063 return res;
1064 } // GetFileDescriptor
1067 HRESULT
1068 nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
1069 HRESULT res = S_OK;
1071 // How we handle this depends on if we're dealing with an internet
1072 // shortcut, since those are done under the covers.
1073 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
1074 return GetFileContents_IStream(aFE, aSTG);
1075 else if (IsFlavourPresent(kURLMime))
1076 return GetFileContentsInternetShortcut(aFE, aSTG);
1077 else
1078 NS_WARNING("Not yet implemented\n");
1080 return res;
1082 } // GetFileContents
1084 // Ensure that the supplied name doesn't have invalid characters.
1085 static void ValidateFilename(nsString& aFilename, bool isShortcut) {
1086 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
1087 if (NS_WARN_IF(!mimeService)) {
1088 aFilename.Truncate();
1089 return;
1092 uint32_t flags = nsIMIMEService::VALIDATE_SANITIZE_ONLY;
1093 if (isShortcut) {
1094 flags |= nsIMIMEService::VALIDATE_ALLOW_INVALID_FILENAMES;
1097 nsAutoString outFilename;
1098 mimeService->ValidateFileNameForSaving(aFilename, EmptyCString(), flags,
1099 outFilename);
1100 aFilename = outFilename;
1104 // Given a unicode string, convert it to a valid local charset filename
1105 // and append the .url extension to be used for a shortcut file.
1106 // This ensures that we do not cut MBCS characters in the middle.
1108 // It would seem that this is more functionality suited to being in nsIFile.
1110 static bool CreateURLFilenameFromTextA(nsAutoString& aText, char* aFilename) {
1111 if (aText.IsEmpty()) {
1112 return false;
1114 aText.AppendLiteral(".url");
1115 ValidateFilename(aText, true);
1116 if (aText.IsEmpty()) {
1117 return false;
1120 // ValidateFilename should already be checking the filename length, but do
1121 // an extra check to verify for the local code page that the converted text
1122 // doesn't go over MAX_PATH and just return false if it does.
1123 char defaultChar = '_';
1124 int currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
1125 aText.get(), -1, aFilename, MAX_PATH,
1126 &defaultChar, nullptr);
1127 return currLen != 0;
1130 // Wide character version of CreateURLFilenameFromTextA
1131 static bool CreateURLFilenameFromTextW(nsAutoString& aText,
1132 wchar_t* aFilename) {
1133 if (aText.IsEmpty()) {
1134 return false;
1136 aText.AppendLiteral(".url");
1137 ValidateFilename(aText, true);
1138 if (aText.IsEmpty() || aText.Length() >= MAX_PATH) {
1139 return false;
1142 wcscpy(&aFilename[0], aText.get());
1143 return true;
1146 #define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
1148 static bool GetLocalizedString(const char* aName, nsAString& aString) {
1149 nsCOMPtr<nsIStringBundleService> stringService =
1150 mozilla::components::StringBundle::Service();
1151 if (!stringService) return false;
1153 nsCOMPtr<nsIStringBundle> stringBundle;
1154 nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
1155 getter_AddRefs(stringBundle));
1156 if (NS_FAILED(rv)) return false;
1158 rv = stringBundle->GetStringFromName(aName, aString);
1159 return NS_SUCCEEDED(rv);
1163 // GetFileDescriptorInternetShortcut
1165 // Create the special format for an internet shortcut and build up the data
1166 // structures the shell is expecting.
1168 HRESULT
1169 nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
1170 STGMEDIUM& aSTG) {
1171 // get the title of the shortcut
1172 nsAutoString title;
1173 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1175 HGLOBAL fileGroupDescHandle =
1176 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
1177 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1179 LPFILEGROUPDESCRIPTORA fileGroupDescA =
1180 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
1181 ::GlobalLock(fileGroupDescHandle));
1182 if (!fileGroupDescA) {
1183 ::GlobalFree(fileGroupDescHandle);
1184 return E_OUTOFMEMORY;
1187 // get a valid filename in the following order: 1) from the page title,
1188 // 2) localized string for an untitled page, 3) just use "Untitled.url"
1189 if (!CreateURLFilenameFromTextA(title, fileGroupDescA->fgd[0].cFileName)) {
1190 nsAutoString untitled;
1191 if (!GetLocalizedString("noPageTitle", untitled) ||
1192 !CreateURLFilenameFromTextA(untitled,
1193 fileGroupDescA->fgd[0].cFileName)) {
1194 strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.url");
1198 // one file in the file block
1199 fileGroupDescA->cItems = 1;
1200 fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
1202 ::GlobalUnlock(fileGroupDescHandle);
1203 aSTG.hGlobal = fileGroupDescHandle;
1204 aSTG.tymed = TYMED_HGLOBAL;
1206 return S_OK;
1207 } // GetFileDescriptorInternetShortcutA
1209 HRESULT
1210 nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
1211 STGMEDIUM& aSTG) {
1212 // get the title of the shortcut
1213 nsAutoString title;
1214 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1216 HGLOBAL fileGroupDescHandle =
1217 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
1218 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1220 LPFILEGROUPDESCRIPTORW fileGroupDescW =
1221 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
1222 ::GlobalLock(fileGroupDescHandle));
1223 if (!fileGroupDescW) {
1224 ::GlobalFree(fileGroupDescHandle);
1225 return E_OUTOFMEMORY;
1228 // get a valid filename in the following order: 1) from the page title,
1229 // 2) localized string for an untitled page, 3) just use "Untitled.url"
1230 if (!CreateURLFilenameFromTextW(title, fileGroupDescW->fgd[0].cFileName)) {
1231 nsAutoString untitled;
1232 if (!GetLocalizedString("noPageTitle", untitled) ||
1233 !CreateURLFilenameFromTextW(untitled,
1234 fileGroupDescW->fgd[0].cFileName)) {
1235 wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.url");
1239 // one file in the file block
1240 fileGroupDescW->cItems = 1;
1241 fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
1243 ::GlobalUnlock(fileGroupDescHandle);
1244 aSTG.hGlobal = fileGroupDescHandle;
1245 aSTG.tymed = TYMED_HGLOBAL;
1247 return S_OK;
1248 } // GetFileDescriptorInternetShortcutW
1251 // GetFileContentsInternetShortcut
1253 // Create the special format for an internet shortcut and build up the data
1254 // structures the shell is expecting.
1256 HRESULT
1257 nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
1258 static const char* kShellIconPref = "browser.shell.shortcutFavicons";
1259 nsAutoString url;
1260 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1262 nsCOMPtr<nsIURI> aUri;
1263 nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
1264 if (NS_FAILED(rv)) {
1265 return E_FAIL;
1268 nsAutoCString asciiUrl;
1269 rv = aUri->GetAsciiSpec(asciiUrl);
1270 if (NS_FAILED(rv)) {
1271 return E_FAIL;
1274 RefPtr<AutoCloseEvent> event;
1276 const char* shortcutFormatStr;
1277 int totalLen;
1278 nsCString asciiPath;
1279 if (!Preferences::GetBool(kShellIconPref, true)) {
1280 shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
1281 const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
1282 totalLen = formatLen + asciiUrl.Length(); // don't include null character
1283 } else {
1284 nsCOMPtr<nsIFile> icoFile;
1286 nsAutoString aUriHash;
1288 event = new AutoCloseEvent();
1289 if (!event->IsInited()) {
1290 return E_FAIL;
1293 RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event));
1294 mozilla::widget::FaviconHelper::ObtainCachedIconFile(
1295 aUri, aUriHash, mIOThread, true,
1296 NS_NewRunnableFunction(
1297 "FaviconHelper::RefreshDesktop", [e = std::move(e)] {
1298 if (e->IsWaiting()) {
1299 // Unblock IStream:::Read.
1300 e->Signal();
1301 } else {
1302 // We could not wait until the favicon was available. We have
1303 // to refresh to refect the favicon.
1304 SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
1305 SPI_SETNONCLIENTMETRICS, 0);
1307 }));
1309 rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
1310 NS_ENSURE_SUCCESS(rv, E_FAIL);
1311 nsString path;
1312 rv = icoFile->GetPath(path);
1313 NS_ENSURE_SUCCESS(rv, E_FAIL);
1315 if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
1316 LossyCopyUTF16toASCII(path, asciiPath);
1317 shortcutFormatStr =
1318 "[InternetShortcut]\r\nURL=%s\r\n"
1319 "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
1320 "IconIndex=0\r\n";
1321 } else {
1322 int len =
1323 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1324 path.Length(), nullptr, 0, nullptr, nullptr);
1325 NS_ENSURE_TRUE(len > 0, E_FAIL);
1326 asciiPath.SetLength(len);
1327 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1328 path.Length(), asciiPath.BeginWriting(), len, nullptr,
1329 nullptr);
1330 shortcutFormatStr =
1331 "[InternetShortcut]\r\nURL=%s\r\n"
1332 "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
1333 "[InternetShortcut.W]\r\nIconFile=%s\r\n";
1335 const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
1336 totalLen = formatLen + asciiUrl.Length() +
1337 asciiPath.Length(); // we don't want a null character on the end
1340 // create a global memory area and build up the file contents w/in it
1341 nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen)));
1342 if (!globalMem) return E_OUTOFMEMORY;
1344 char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get()));
1345 if (!contents) {
1346 return E_OUTOFMEMORY;
1349 // NOTE: we intentionally use the Microsoft version of snprintf here because
1350 // it does NOT null
1351 // terminate strings which reach the maximum size of the buffer. Since we know
1352 // that the formatted length here is totalLen, this call to _snprintf will
1353 // format the string into the buffer without appending the null character.
1355 if (!Preferences::GetBool(kShellIconPref, true)) {
1356 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
1357 } else {
1358 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
1359 asciiPath.get());
1362 ::GlobalUnlock(globalMem.get());
1364 if (aFE.tymed & TYMED_ISTREAM) {
1365 if (!mIsInOperation) {
1366 // The drop target didn't initiate an async operation.
1367 // We can't block CMemStream::Read.
1368 event = nullptr;
1370 RefPtr<IStream> stream =
1371 new CMemStream(globalMem.disown(), totalLen, event.forget());
1372 stream.forget(&aSTG.pstm);
1373 aSTG.tymed = TYMED_ISTREAM;
1374 } else {
1375 if (event && event->IsInited()) {
1376 event->Signal(); // We can't block reading the global memory
1378 aSTG.hGlobal = globalMem.disown();
1379 aSTG.tymed = TYMED_HGLOBAL;
1382 return S_OK;
1383 } // GetFileContentsInternetShortcut
1385 // check if specified flavour is present in the transferable
1386 bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
1387 bool retval = false;
1388 NS_ENSURE_TRUE(mTransferable, false);
1390 // get the list of flavors available in the transferable
1391 nsTArray<nsCString> flavors;
1392 nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
1393 NS_ENSURE_SUCCESS(rv, false);
1395 // try to find requested flavour
1396 for (uint32_t i = 0; i < flavors.Length(); ++i) {
1397 if (flavors[i].Equals(inFlavour)) {
1398 retval = true; // found it!
1399 break;
1401 } // for each flavor
1403 return retval;
1406 HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
1407 HRESULT res = S_OK;
1408 aSTG.tymed = TYMED_HGLOBAL;
1409 aSTG.pUnkForRelease = nullptr;
1410 HGLOBAL hGlobalMemory = nullptr;
1411 hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
1412 if (hGlobalMemory) {
1413 DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
1414 // The PreferredDropEffect clipboard format is only registered if a
1415 // drag/drop of an image happens from Mozilla to the desktop. We want its
1416 // value to be DROPEFFECT_MOVE in that case so that the file is moved from
1417 // the temporary location, not copied. This value should, ideally, be set on
1418 // the data object via SetData() but our IDataObject implementation doesn't
1419 // implement SetData. It adds data to the data object lazily only when the
1420 // drop target asks for it.
1421 *pdw = (DWORD)DROPEFFECT_MOVE;
1422 GlobalUnlock(hGlobalMemory);
1423 } else {
1424 res = E_OUTOFMEMORY;
1426 aSTG.hGlobal = hGlobalMemory;
1427 return res;
1430 //-----------------------------------------------------
1431 HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
1432 STGMEDIUM& aSTG) {
1433 void* data = nullptr;
1435 const nsPromiseFlatCString& flavorStr = PromiseFlatCString(aDataFlavor);
1437 // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
1438 nsCOMPtr<nsISupports> genericDataWrapper;
1439 nsresult rv = mTransferable->GetTransferData(
1440 flavorStr.get(), getter_AddRefs(genericDataWrapper));
1441 if (NS_FAILED(rv) || !genericDataWrapper) {
1442 return E_FAIL;
1445 uint32_t len;
1446 nsPrimitiveHelpers::CreateDataFromPrimitive(
1447 nsDependentCString(flavorStr.get()), genericDataWrapper, &data, &len);
1448 if (!data) return E_FAIL;
1450 HGLOBAL hGlobalMemory = nullptr;
1452 aSTG.tymed = TYMED_HGLOBAL;
1453 aSTG.pUnkForRelease = nullptr;
1455 // We play games under the hood and advertise flavors that we know we
1456 // can support, only they require a bit of conversion or munging of the data.
1457 // Do that here.
1459 // The transferable gives us data that is null-terminated, but this isn't
1460 // reflected in the |len| parameter. Windoze apps expect this null to be there
1461 // so bump our data buffer by the appropriate size to account for the null
1462 // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
1463 DWORD allocLen = (DWORD)len;
1464 if (aFE.cfFormat == CF_TEXT) {
1465 // Someone is asking for text/plain; convert the unicode (assuming it's
1466 // present) to text with the correct platform encoding.
1467 size_t bufferSize = sizeof(char) * (len + 2);
1468 char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
1469 char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
1470 int32_t plainTextLen =
1471 WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
1472 plainTextData, bufferSize, NULL, NULL);
1473 // replace the unicode data with our plaintext data. Recall that
1474 // |plainTextLen| doesn't include the null in the length.
1475 free(data);
1476 if (plainTextLen) {
1477 data = plainTextData;
1478 allocLen = plainTextLen;
1479 } else {
1480 free(plainTextData);
1481 NS_WARNING("Oh no, couldn't convert unicode to plain text");
1482 return S_OK;
1484 } else if (aFE.cfFormat == nsClipboard::GetHtmlClipboardFormat()) {
1485 // Someone is asking for win32's HTML flavor. Convert our html fragment
1486 // from unicode to UTF-8 then put it into a format specified by msft.
1487 NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
1488 char* utf8HTML = nullptr;
1489 nsresult rv =
1490 BuildPlatformHTML(converter.get(), &utf8HTML); // null terminates
1492 free(data);
1493 if (NS_SUCCEEDED(rv) && utf8HTML) {
1494 // replace the unicode data with our HTML data. Don't forget the null.
1495 data = utf8HTML;
1496 allocLen = strlen(utf8HTML) + sizeof(char);
1497 } else {
1498 NS_WARNING("Oh no, couldn't convert to HTML");
1499 return S_OK;
1501 } else if (aFE.cfFormat != nsClipboard::GetCustomClipboardFormat()) {
1502 // we assume that any data that isn't caught above is unicode. This may
1503 // be an erroneous assumption, but is true so far.
1504 allocLen += sizeof(char16_t);
1507 hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
1509 // Copy text to Global Memory Area
1510 if (hGlobalMemory) {
1511 char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
1512 char* source = reinterpret_cast<char*>(data);
1513 memcpy(dest, source, allocLen); // copies the null as well
1514 GlobalUnlock(hGlobalMemory);
1516 aSTG.hGlobal = hGlobalMemory;
1518 // Now, delete the memory that was created by CreateDataFromPrimitive (or our
1519 // text/plain data)
1520 free(data);
1522 return S_OK;
1525 //-----------------------------------------------------
1526 HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1527 uint32_t dfInx = 0;
1528 ULONG count;
1529 FORMATETC fe;
1530 m_enumFE->Reset();
1531 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
1532 dfInx < mDataFlavors.Length()) {
1533 if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
1534 return DropImage(aFE, aSTG);
1535 if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
1536 return DropFile(aFE, aSTG);
1537 if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
1538 return DropTempFile(aFE, aSTG);
1539 dfInx++;
1541 return E_FAIL;
1544 HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1545 nsresult rv;
1546 nsCOMPtr<nsISupports> genericDataWrapper;
1548 if (NS_FAILED(mTransferable->GetTransferData(
1549 kFileMime, getter_AddRefs(genericDataWrapper)))) {
1550 return E_FAIL;
1552 nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
1553 if (!file) return E_FAIL;
1555 aSTG.tymed = TYMED_HGLOBAL;
1556 aSTG.pUnkForRelease = nullptr;
1558 nsAutoString path;
1559 rv = file->GetPath(path);
1560 if (NS_FAILED(rv)) return E_FAIL;
1562 uint32_t allocLen = path.Length() + 2;
1563 HGLOBAL hGlobalMemory = nullptr;
1564 char16_t* dest;
1566 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1567 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1568 if (!hGlobalMemory) return E_FAIL;
1570 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1572 // First, populate the drop file structure
1573 pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string
1574 pDropFile->fNC = 0;
1575 pDropFile->pt.x = 0;
1576 pDropFile->pt.y = 0;
1577 pDropFile->fWide = TRUE;
1579 // Copy the filename right after the DROPFILES structure
1580 dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1581 memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
1583 // Two null characters are needed at the end of the file name.
1584 // Lookup the CF_HDROP shell clipboard format for more info.
1585 // Add the second null character right after the first one.
1586 dest[allocLen - 1] = L'\0';
1588 GlobalUnlock(hGlobalMemory);
1590 aSTG.hGlobal = hGlobalMemory;
1592 return S_OK;
1595 HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
1596 nsresult rv;
1597 if (!mCachedTempFile) {
1598 nsCOMPtr<nsISupports> genericDataWrapper;
1600 if (NS_FAILED(mTransferable->GetTransferData(
1601 kNativeImageMime, getter_AddRefs(genericDataWrapper)))) {
1602 return E_FAIL;
1604 nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
1605 if (!image) return E_FAIL;
1607 nsCOMPtr<imgITools> imgTools =
1608 do_CreateInstance("@mozilla.org/image/tools;1");
1609 nsCOMPtr<nsIInputStream> inputStream;
1610 rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
1611 u"bpp=32;version=3"_ns,
1612 getter_AddRefs(inputStream));
1613 if (NS_FAILED(rv) || !inputStream) {
1614 return E_FAIL;
1617 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1618 if (!encoder) {
1619 return E_FAIL;
1622 uint32_t size = 0;
1623 rv = encoder->GetImageBufferUsed(&size);
1624 if (NS_FAILED(rv)) {
1625 return E_FAIL;
1628 char* src = nullptr;
1629 rv = encoder->GetImageBuffer(&src);
1630 if (NS_FAILED(rv) || !src) {
1631 return E_FAIL;
1634 // Save the bitmap to a temporary location.
1635 nsCOMPtr<nsIFile> dropFile;
1636 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1637 if (!dropFile) {
1638 return E_FAIL;
1641 // Filename must be random so as not to confuse apps like
1642 // Photoshop which handle multiple drags into a single window.
1643 char buf[13];
1644 nsCString filename;
1645 NS_MakeRandomString(buf, 8);
1646 memcpy(buf + 8, ".bmp", 5);
1647 filename.Append(nsDependentCString(buf, 12));
1648 dropFile->AppendNative(filename);
1649 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1650 if (NS_FAILED(rv)) {
1651 return E_FAIL;
1654 // Cache the temp file so we can delete it later and so
1655 // it doesn't get recreated over and over on multiple calls
1656 // which does occur from windows shell.
1657 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1659 // Write the data to disk.
1660 nsCOMPtr<nsIOutputStream> outStream;
1661 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1662 if (NS_FAILED(rv)) {
1663 return E_FAIL;
1666 uint32_t written = 0;
1667 rv = outStream->Write(src, size, &written);
1668 if (NS_FAILED(rv) || written != size) {
1669 return E_FAIL;
1672 outStream->Close();
1675 // Pass the file name back to the drop target so that it can access the file.
1676 nsAutoString path;
1677 rv = mCachedTempFile->GetPath(path);
1678 if (NS_FAILED(rv)) return E_FAIL;
1680 // Two null characters are needed to terminate the file name list.
1681 HGLOBAL hGlobalMemory = nullptr;
1683 uint32_t allocLen = path.Length() + 2;
1685 aSTG.tymed = TYMED_HGLOBAL;
1686 aSTG.pUnkForRelease = nullptr;
1688 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1689 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1690 if (!hGlobalMemory) return E_FAIL;
1692 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1694 // First, populate the drop file structure.
1695 pDropFile->pFiles =
1696 sizeof(DROPFILES); // Offset to start of file name char array.
1697 pDropFile->fNC = 0;
1698 pDropFile->pt.x = 0;
1699 pDropFile->pt.y = 0;
1700 pDropFile->fWide = TRUE;
1702 // Copy the filename right after the DROPFILES structure.
1703 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1704 memcpy(dest, path.get(),
1705 (allocLen - 1) *
1706 sizeof(char16_t)); // Copies the null character in path as well.
1708 // Two null characters are needed at the end of the file name.
1709 // Lookup the CF_HDROP shell clipboard format for more info.
1710 // Add the second null character right after the first one.
1711 dest[allocLen - 1] = L'\0';
1713 GlobalUnlock(hGlobalMemory);
1715 aSTG.hGlobal = hGlobalMemory;
1717 return S_OK;
1720 HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1721 nsresult rv;
1722 if (!mCachedTempFile) {
1723 // Tempfile will need a temporary location.
1724 nsCOMPtr<nsIFile> dropFile;
1725 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1726 if (!dropFile) return E_FAIL;
1728 // Filename must be random
1729 nsCString filename;
1730 nsAutoString wideFileName;
1731 nsCOMPtr<nsIURI> sourceURI;
1732 HRESULT res;
1733 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
1734 if (FAILED(res)) return res;
1735 NS_CopyUnicodeToNative(wideFileName, filename);
1737 dropFile->AppendNative(filename);
1738 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1739 if (NS_FAILED(rv)) return E_FAIL;
1741 // Cache the temp file so we can delete it later and so
1742 // it doesn't get recreated over and over on multiple calls
1743 // which does occur from windows shell.
1744 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1746 // Write the data to disk.
1747 nsCOMPtr<nsIOutputStream> outStream;
1748 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1749 if (NS_FAILED(rv)) return E_FAIL;
1751 IStream* pStream = nullptr;
1752 nsDataObj::CreateStream(&pStream);
1753 NS_ENSURE_TRUE(pStream, E_FAIL);
1755 char buffer[512];
1756 ULONG readCount = 0;
1757 uint32_t writeCount = 0;
1758 while (1) {
1759 HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
1760 if (FAILED(hres)) return E_FAIL;
1761 if (readCount == 0) break;
1762 rv = outStream->Write(buffer, readCount, &writeCount);
1763 if (NS_FAILED(rv)) return E_FAIL;
1765 outStream->Close();
1766 pStream->Release();
1769 // Pass the file name back to the drop target so that it can access the file.
1770 nsAutoString path;
1771 rv = mCachedTempFile->GetPath(path);
1772 if (NS_FAILED(rv)) return E_FAIL;
1774 uint32_t allocLen = path.Length() + 2;
1776 // Two null characters are needed to terminate the file name list.
1777 HGLOBAL hGlobalMemory = nullptr;
1779 aSTG.tymed = TYMED_HGLOBAL;
1780 aSTG.pUnkForRelease = nullptr;
1782 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1783 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1784 if (!hGlobalMemory) return E_FAIL;
1786 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1788 // First, populate the drop file structure.
1789 pDropFile->pFiles =
1790 sizeof(DROPFILES); // Offset to start of file name char array.
1791 pDropFile->fNC = 0;
1792 pDropFile->pt.x = 0;
1793 pDropFile->pt.y = 0;
1794 pDropFile->fWide = TRUE;
1796 // Copy the filename right after the DROPFILES structure.
1797 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1798 memcpy(dest, path.get(),
1799 (allocLen - 1) *
1800 sizeof(char16_t)); // Copies the null character in path as well.
1802 // Two null characters are needed at the end of the file name.
1803 // Lookup the CF_HDROP shell clipboard format for more info.
1804 // Add the second null character right after the first one.
1805 dest[allocLen - 1] = L'\0';
1807 GlobalUnlock(hGlobalMemory);
1809 aSTG.hGlobal = hGlobalMemory;
1811 return S_OK;
1814 //-----------------------------------------------------
1815 // Registers the DataFlavor/FE pair.
1816 //-----------------------------------------------------
1817 void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
1818 // These two lists are the mapping to and from data flavors and FEs.
1819 // Later, OLE will tell us it needs a certain type of FORMATETC (text,
1820 // unicode, etc) unicode, etc), so we will look up the data flavor that
1821 // corresponds to the FE and then ask the transferable for that type of data.
1822 mDataFlavors.AppendElement(aDataFlavor);
1823 m_enumFE->AddFormatEtc(aFE);
1826 //-----------------------------------------------------
1827 // Sets the transferable object
1828 //-----------------------------------------------------
1829 void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
1830 NS_IF_RELEASE(mTransferable);
1832 mTransferable = aTransferable;
1833 if (nullptr == mTransferable) {
1834 return;
1837 NS_ADDREF(mTransferable);
1839 return;
1843 // ExtractURL
1845 // Roots around in the transferable for the appropriate flavor that indicates
1846 // a url and pulls out the url portion of the data. Used mostly for creating
1847 // internet shortcuts on the desktop. The url flavor is of the format:
1849 // <url> <linefeed> <page title>
1851 nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
1852 NS_ASSERTION(mTransferable, "We don't have a good transferable");
1853 nsresult rv = NS_ERROR_FAILURE;
1855 nsCOMPtr<nsISupports> genericURL;
1856 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1857 kURLMime, getter_AddRefs(genericURL)))) {
1858 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1859 if (urlObject) {
1860 nsAutoString url;
1861 urlObject->GetData(url);
1862 outURL = url;
1864 // find the first linefeed in the data, that's where the url ends. trunc
1865 // the result string at that point.
1866 int32_t lineIndex = outURL.FindChar('\n');
1867 NS_ASSERTION(lineIndex > 0,
1868 "Format for url flavor is <url> <linefeed> <page title>");
1869 if (lineIndex > 0) {
1870 outURL.Truncate(lineIndex);
1871 rv = NS_OK;
1874 } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
1875 kURLDataMime, getter_AddRefs(genericURL))) ||
1876 NS_SUCCEEDED(mTransferable->GetTransferData(
1877 kURLPrivateMime, getter_AddRefs(genericURL)))) {
1878 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1879 if (urlObject) {
1880 nsAutoString url;
1881 urlObject->GetData(url);
1882 outURL = url;
1884 rv = NS_OK;
1887 } // if found flavor
1889 return rv;
1891 } // ExtractShortcutURL
1894 // ExtractShortcutTitle
1896 // Roots around in the transferable for the appropriate flavor that indicates
1897 // a url and pulls out the title portion of the data. Used mostly for creating
1898 // internet shortcuts on the desktop. The url flavor is of the format:
1900 // <url> <linefeed> <page title>
1902 nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
1903 NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
1904 nsresult rv = NS_ERROR_FAILURE;
1906 nsCOMPtr<nsISupports> genericURL;
1907 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1908 kURLMime, getter_AddRefs(genericURL)))) {
1909 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1910 if (urlObject) {
1911 nsAutoString url;
1912 urlObject->GetData(url);
1914 // find the first linefeed in the data, that's where the url ends. we want
1915 // everything after that linefeed. FindChar() returns -1 if we can't find
1916 int32_t lineIndex = url.FindChar('\n');
1917 NS_ASSERTION(lineIndex != -1,
1918 "Format for url flavor is <url> <linefeed> <page title>");
1919 if (lineIndex != -1) {
1920 url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
1921 rv = NS_OK;
1924 } // if found flavor
1926 return rv;
1928 } // ExtractShortcutTitle
1931 // BuildPlatformHTML
1933 // Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
1934 // header information on it. This will null-terminate |outPlatformHTML|. See
1935 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
1936 // for details.
1938 // We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
1939 // or <BODY> tags). We'll wrap the fragment with them to make other apps
1940 // happy.
1942 nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
1943 char** outPlatformHTML) {
1944 *outPlatformHTML = nullptr;
1945 nsDependentCString inHTMLString(inOurHTML);
1947 // Do we already have mSourceURL from a drag?
1948 if (mSourceURL.IsEmpty()) {
1949 nsAutoString url;
1950 ExtractShortcutURL(url);
1952 AppendUTF16toUTF8(url, mSourceURL);
1955 constexpr auto kStartHTMLPrefix = "Version:0.9\r\nStartHTML:"_ns;
1956 constexpr auto kEndHTMLPrefix = "\r\nEndHTML:"_ns;
1957 constexpr auto kStartFragPrefix = "\r\nStartFragment:"_ns;
1958 constexpr auto kEndFragPrefix = "\r\nEndFragment:"_ns;
1959 constexpr auto kStartSourceURLPrefix = "\r\nSourceURL:"_ns;
1960 constexpr auto kEndFragTrailer = "\r\n"_ns;
1962 // The CF_HTML's size is embedded in the fragment, in such a way that the
1963 // number of digits in the size is part of the size itself. While it _is_
1964 // technically possible to compute the necessary size of the size-field
1965 // precisely -- by trial and error, if nothing else -- it's simpler just to
1966 // pick a rough but generous estimate and zero-pad it. (Zero-padding is
1967 // explicitly permitted by the format definition.)
1969 // Originally, in 2001, the "rough but generous estimate" was 8 digits. While
1970 // a maximum size of (10**9 - 1) bytes probably would have covered all
1971 // possible use-cases at the time, it's somewhat more likely to overflow
1972 // nowadays. Nonetheless, for the sake of backwards compatibility with any
1973 // misbehaving consumers of our existing CF_HTML output, we retain exactly
1974 // that padding for (most) fragments where it suffices. (No such misbehaving
1975 // consumers are actually known, so this is arguably paranoia.)
1977 // It is now 2022. A padding size of 16 will cover up to about 8.8 petabytes,
1978 // which should be enough for at least the next few years or so.
1979 const size_t numberLength = inHTMLString.Length() < 9999'0000 ? 8 : 16;
1981 const size_t sourceURLLength = mSourceURL.Length();
1983 const size_t fixedHeaderLen =
1984 kStartHTMLPrefix.Length() + kEndHTMLPrefix.Length() +
1985 kStartFragPrefix.Length() + kEndFragPrefix.Length() +
1986 kEndFragTrailer.Length() + (4 * numberLength);
1988 const size_t totalHeaderLen =
1989 fixedHeaderLen + (sourceURLLength > 0
1990 ? kStartSourceURLPrefix.Length() + sourceURLLength
1991 : 0);
1993 constexpr auto kHeaderString = "<html><body>\r\n<!--StartFragment-->"_ns;
1994 constexpr auto kTrailingString =
1995 "<!--EndFragment-->\r\n"
1996 "</body>\r\n"
1997 "</html>"_ns;
1999 // calculate the offsets
2000 size_t startHTMLOffset = totalHeaderLen;
2001 size_t startFragOffset = startHTMLOffset + kHeaderString.Length();
2003 size_t endFragOffset = startFragOffset + inHTMLString.Length();
2004 size_t endHTMLOffset = endFragOffset + kTrailingString.Length();
2006 // now build the final version
2007 nsCString clipboardString;
2008 clipboardString.SetCapacity(endHTMLOffset);
2010 const int numberLengthInt = static_cast<int>(numberLength);
2011 clipboardString.Append(kStartHTMLPrefix);
2012 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startHTMLOffset);
2014 clipboardString.Append(kEndHTMLPrefix);
2015 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endHTMLOffset);
2017 clipboardString.Append(kStartFragPrefix);
2018 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startFragOffset);
2020 clipboardString.Append(kEndFragPrefix);
2021 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endFragOffset);
2023 if (sourceURLLength > 0) {
2024 clipboardString.Append(kStartSourceURLPrefix);
2025 clipboardString.Append(mSourceURL);
2028 clipboardString.Append(kEndFragTrailer);
2030 // Assert that the positional values were correct as we pass by their
2031 // corresponding positions.
2032 MOZ_ASSERT(clipboardString.Length() == startHTMLOffset);
2033 clipboardString.Append(kHeaderString);
2034 MOZ_ASSERT(clipboardString.Length() == startFragOffset);
2035 clipboardString.Append(inHTMLString);
2036 MOZ_ASSERT(clipboardString.Length() == endFragOffset);
2037 clipboardString.Append(kTrailingString);
2038 MOZ_ASSERT(clipboardString.Length() == endHTMLOffset);
2040 *outPlatformHTML = ToNewCString(clipboardString, mozilla::fallible);
2041 if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
2043 return NS_OK;
2046 HRESULT
2047 nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
2048 bool aIsUnicode) {
2049 HRESULT res = S_OK;
2050 if (IsFlavourPresent(kURLMime)) {
2051 if (aIsUnicode)
2052 res = ExtractUniformResourceLocatorW(aFE, aSTG);
2053 else
2054 res = ExtractUniformResourceLocatorA(aFE, aSTG);
2055 } else
2056 NS_WARNING("Not yet implemented\n");
2057 return res;
2060 HRESULT
2061 nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2062 HRESULT result = S_OK;
2064 nsAutoString url;
2065 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2067 NS_LossyConvertUTF16toASCII asciiUrl(url);
2068 const int totalLen = asciiUrl.Length() + 1;
2069 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2070 if (!hGlobalMemory) return E_OUTOFMEMORY;
2072 char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
2073 if (!contents) {
2074 GlobalFree(hGlobalMemory);
2075 return E_OUTOFMEMORY;
2078 strcpy(contents, asciiUrl.get());
2079 GlobalUnlock(hGlobalMemory);
2080 aSTG.hGlobal = hGlobalMemory;
2081 aSTG.tymed = TYMED_HGLOBAL;
2083 return result;
2086 HRESULT
2087 nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2088 HRESULT result = S_OK;
2090 nsAutoString url;
2091 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2093 const int totalLen = (url.Length() + 1) * sizeof(char16_t);
2094 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2095 if (!hGlobalMemory) return E_OUTOFMEMORY;
2097 wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
2098 if (!contents) {
2099 GlobalFree(hGlobalMemory);
2100 return E_OUTOFMEMORY;
2103 wcscpy(contents, url.get());
2104 GlobalUnlock(hGlobalMemory);
2105 aSTG.hGlobal = hGlobalMemory;
2106 aSTG.tymed = TYMED_HGLOBAL;
2108 return result;
2111 // Gets the filename from the kFilePromiseURLMime flavour
2112 HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
2113 nsAString& aFilename) {
2114 *aSourceURI = nullptr;
2116 NS_ENSURE_TRUE(mTransferable, E_FAIL);
2118 // get the URI from the kFilePromiseURLMime flavor
2119 nsCOMPtr<nsISupports> urlPrimitive;
2120 nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
2121 getter_AddRefs(urlPrimitive));
2122 NS_ENSURE_SUCCESS(rv, E_FAIL);
2123 nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
2124 NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
2126 nsAutoString srcUri;
2127 srcUrlPrimitive->GetData(srcUri);
2128 if (srcUri.IsEmpty()) return E_FAIL;
2129 nsCOMPtr<nsIURI> sourceURI;
2130 NS_NewURI(getter_AddRefs(sourceURI), srcUri);
2132 nsAutoString srcFileName;
2133 nsCOMPtr<nsISupports> fileNamePrimitive;
2134 Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
2135 getter_AddRefs(fileNamePrimitive));
2136 nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
2137 do_QueryInterface(fileNamePrimitive);
2138 if (srcFileNamePrimitive) {
2139 srcFileNamePrimitive->GetData(srcFileName);
2140 } else {
2141 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
2142 if (!sourceURL) return E_FAIL;
2144 nsAutoCString urlFileName;
2145 sourceURL->GetFileName(urlFileName);
2146 NS_UnescapeURL(urlFileName);
2147 CopyUTF8toUTF16(urlFileName, srcFileName);
2150 // make the name safe for the filesystem
2151 ValidateFilename(srcFileName, false);
2152 if (srcFileName.IsEmpty()) return E_FAIL;
2154 sourceURI.swap(*aSourceURI);
2155 aFilename = srcFileName;
2156 return S_OK;
2159 HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2160 HGLOBAL fileGroupDescHandle =
2161 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2162 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2164 LPFILEGROUPDESCRIPTORA fileGroupDescA =
2165 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
2166 if (!fileGroupDescA) {
2167 ::GlobalFree(fileGroupDescHandle);
2168 return E_OUTOFMEMORY;
2171 nsAutoString wideFileName;
2172 HRESULT res;
2173 nsCOMPtr<nsIURI> sourceURI;
2174 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2175 if (FAILED(res)) {
2176 ::GlobalFree(fileGroupDescHandle);
2177 return res;
2180 nsAutoCString nativeFileName;
2181 NS_CopyUnicodeToNative(wideFileName, nativeFileName);
2183 strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), MAX_PATH - 1);
2184 fileGroupDescA->fgd[0].cFileName[MAX_PATH - 1] = '\0';
2186 // one file in the file block
2187 fileGroupDescA->cItems = 1;
2188 fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
2190 GlobalUnlock(fileGroupDescHandle);
2191 aSTG.hGlobal = fileGroupDescHandle;
2192 aSTG.tymed = TYMED_HGLOBAL;
2194 return S_OK;
2197 HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2198 HGLOBAL fileGroupDescHandle =
2199 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2200 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2202 LPFILEGROUPDESCRIPTORW fileGroupDescW =
2203 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
2204 if (!fileGroupDescW) {
2205 ::GlobalFree(fileGroupDescHandle);
2206 return E_OUTOFMEMORY;
2209 nsAutoString wideFileName;
2210 HRESULT res;
2211 nsCOMPtr<nsIURI> sourceURI;
2212 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2213 if (FAILED(res)) {
2214 ::GlobalFree(fileGroupDescHandle);
2215 return res;
2218 wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(), MAX_PATH - 1);
2219 fileGroupDescW->fgd[0].cFileName[MAX_PATH - 1] = '\0';
2220 // one file in the file block
2221 fileGroupDescW->cItems = 1;
2222 fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
2224 GlobalUnlock(fileGroupDescHandle);
2225 aSTG.hGlobal = fileGroupDescHandle;
2226 aSTG.tymed = TYMED_HGLOBAL;
2228 return S_OK;
2231 HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
2232 IStream* pStream = nullptr;
2234 nsDataObj::CreateStream(&pStream);
2235 NS_ENSURE_TRUE(pStream, E_FAIL);
2237 aSTG.tymed = TYMED_ISTREAM;
2238 aSTG.pstm = pStream;
2239 aSTG.pUnkForRelease = nullptr;
2241 return S_OK;