Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / widget / windows / nsDataObj.cpp
blob5ad298ea5b186cc268591f62d2ab7ed6aacd714b
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 * deliberately not using MAX_PATH. This is because on platforms < XP
474 * a file created with a long filename may be mishandled by the shell
475 * resulting in it not being able to be deleted or moved.
476 * See bug 250392 for more details.
478 #define NS_MAX_FILEDESCRIPTOR 128 + 1
481 * Class nsDataObj
484 //-----------------------------------------------------
485 // construction
486 //-----------------------------------------------------
487 nsDataObj::nsDataObj(nsIURI* uri)
488 : m_cRef(0),
489 mTransferable(nullptr),
490 mIsAsyncMode(FALSE),
491 mIsInOperation(FALSE) {
492 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj"_ns,
493 LazyIdleThread::ManualShutdown);
494 m_enumFE = new CEnumFormatEtc();
495 m_enumFE->AddRef();
497 if (uri) {
498 // A URI was obtained, so pass this through to the DataObject
499 // so it can create a SourceURL for CF_HTML flavour
500 uri->GetSpec(mSourceURL);
503 //-----------------------------------------------------
504 // destruction
505 //-----------------------------------------------------
506 nsDataObj::~nsDataObj() {
507 NS_IF_RELEASE(mTransferable);
509 mDataFlavors.Clear();
511 m_enumFE->Release();
513 // Free arbitrary system formats
514 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
515 CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
516 ReleaseStgMedium(&mDataEntryList[idx]->stgm);
517 CoTaskMemFree(mDataEntryList[idx]);
521 //-----------------------------------------------------
522 // IUnknown interface methods - see inknown.h for documentation
523 //-----------------------------------------------------
524 STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
525 *ppv = nullptr;
527 if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
528 *ppv = this;
529 AddRef();
530 return S_OK;
531 } else if (IID_IDataObjectAsyncCapability == riid) {
532 *ppv = static_cast<IDataObjectAsyncCapability*>(this);
533 AddRef();
534 return S_OK;
537 return E_NOINTERFACE;
540 //-----------------------------------------------------
541 STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
542 ++m_cRef;
543 NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
545 // When the first reference is taken, hold our own internal reference.
546 if (m_cRef == 1) {
547 mKeepAlive = this;
550 return m_cRef;
553 namespace {
554 class RemoveTempFileHelper final : public nsIObserver, public nsINamed {
555 public:
556 explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
557 MOZ_ASSERT(mTempFile);
560 // The attach method is seperate from the constructor as we may be addref-ing
561 // ourself, and we want to be sure someone has a strong reference to us.
562 void Attach() {
563 // We need to listen to both the xpcom shutdown message and our timer, and
564 // fire when the first of either of these two messages is received.
565 nsresult rv;
566 rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
567 nsITimer::TYPE_ONE_SHOT);
568 if (NS_WARN_IF(NS_FAILED(rv))) {
569 return;
572 nsCOMPtr<nsIObserverService> observerService =
573 do_GetService("@mozilla.org/observer-service;1");
574 if (NS_WARN_IF(!observerService)) {
575 mTimer->Cancel();
576 mTimer = nullptr;
577 return;
579 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
582 NS_DECL_ISUPPORTS
583 NS_DECL_NSIOBSERVER
584 NS_DECL_NSINAMED
586 private:
587 ~RemoveTempFileHelper() {
588 if (mTempFile) {
589 mTempFile->Remove(false);
593 nsCOMPtr<nsIFile> mTempFile;
594 nsCOMPtr<nsITimer> mTimer;
597 NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver, nsINamed);
599 NS_IMETHODIMP
600 RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
601 const char16_t* aData) {
602 // Let's be careful and make sure that we don't die immediately
603 RefPtr<RemoveTempFileHelper> grip = this;
605 // Make sure that we aren't called again by destroying references to ourself.
606 nsCOMPtr<nsIObserverService> observerService =
607 do_GetService("@mozilla.org/observer-service;1");
608 if (observerService) {
609 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
612 if (mTimer) {
613 mTimer->Cancel();
614 mTimer = nullptr;
617 // Remove the tempfile
618 if (mTempFile) {
619 mTempFile->Remove(false);
620 mTempFile = nullptr;
622 return NS_OK;
625 NS_IMETHODIMP
626 RemoveTempFileHelper::GetName(nsACString& aName) {
627 aName.AssignLiteral("RemoveTempFileHelper");
628 return NS_OK;
630 } // namespace
632 //-----------------------------------------------------
633 STDMETHODIMP_(ULONG) nsDataObj::Release() {
634 --m_cRef;
636 NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
638 // If we hold the last reference, submit release of it to the main thread.
639 if (m_cRef == 1 && mKeepAlive) {
640 NS_ReleaseOnMainThread("nsDataObj release", mKeepAlive.forget(), true);
643 if (0 != m_cRef) return m_cRef;
645 // We have released our last ref on this object and need to delete the
646 // temp file. External app acting as drop target may still need to open the
647 // temp file. Addref a timer so it can delay deleting file and destroying
648 // this object.
649 if (mCachedTempFile) {
650 RefPtr<RemoveTempFileHelper> helper =
651 new RemoveTempFileHelper(mCachedTempFile);
652 mCachedTempFile = nullptr;
653 helper->Attach();
656 // In case the destructor ever AddRef/Releases, ensure we don't delete twice
657 // or take mKeepAlive as another reference.
658 m_cRef = 1;
660 delete this;
662 return 0;
665 //-----------------------------------------------------
666 BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
667 const FORMATETC& target) const {
668 if ((source.cfFormat == target.cfFormat) &&
669 (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
670 return TRUE;
671 } else {
672 return FALSE;
676 //-----------------------------------------------------
677 // IDataObject methods
678 //-----------------------------------------------------
679 STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
680 if (!mTransferable) return DV_E_FORMATETC;
682 // Hold an extra reference in case we end up spinning the event loop.
683 RefPtr<nsDataObj> keepAliveDuringGetData(this);
685 uint32_t dfInx = 0;
687 static CLIPFORMAT fileDescriptorFlavorA =
688 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
689 static CLIPFORMAT fileDescriptorFlavorW =
690 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
691 static CLIPFORMAT uniformResourceLocatorA =
692 ::RegisterClipboardFormat(CFSTR_INETURLA);
693 static CLIPFORMAT uniformResourceLocatorW =
694 ::RegisterClipboardFormat(CFSTR_INETURLW);
695 static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
696 static CLIPFORMAT PreferredDropEffect =
697 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
699 // Arbitrary system formats are used for image feedback during drag
700 // and drop. We are responsible for storing these internally during
701 // drag operations.
702 LPDATAENTRY pde;
703 if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
704 return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
705 : E_UNEXPECTED;
708 // Firefox internal formats
709 ULONG count;
710 FORMATETC fe;
711 m_enumFE->Reset();
712 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
713 dfInx < mDataFlavors.Length()) {
714 nsCString& df = mDataFlavors.ElementAt(dfInx);
715 if (FormatsMatch(fe, *aFormat)) {
716 pSTM->pUnkForRelease =
717 nullptr; // caller is responsible for deleting this data
718 CLIPFORMAT format = aFormat->cfFormat;
719 switch (format) {
720 // Someone is asking for plain or unicode text
721 case CF_TEXT:
722 case CF_UNICODETEXT:
723 return GetText(df, *aFormat, *pSTM);
725 // Some 3rd party apps that receive drag and drop files from the browser
726 // window require support for this.
727 case CF_HDROP:
728 return GetFile(*aFormat, *pSTM);
730 // Someone is asking for an image
731 case CF_DIBV5:
732 case CF_DIB:
733 return GetDib(df, *aFormat, *pSTM);
735 default:
736 if (format == fileDescriptorFlavorA)
737 return GetFileDescriptor(*aFormat, *pSTM, false);
738 if (format == fileDescriptorFlavorW)
739 return GetFileDescriptor(*aFormat, *pSTM, true);
740 if (format == uniformResourceLocatorA)
741 return GetUniformResourceLocator(*aFormat, *pSTM, false);
742 if (format == uniformResourceLocatorW)
743 return GetUniformResourceLocator(*aFormat, *pSTM, true);
744 if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
745 if (format == PreferredDropEffect)
746 return GetPreferredDropEffect(*aFormat, *pSTM);
747 // MOZ_LOG(gWindowsLog, LogLevel::Info,
748 // ("***** nsDataObj::GetData - Unknown format %u\n", format));
749 return GetText(df, *aFormat, *pSTM);
750 } // switch
751 } // if
752 dfInx++;
753 } // while
755 return DATA_E_FORMATETC;
758 //-----------------------------------------------------
759 STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
760 return E_FAIL;
763 //-----------------------------------------------------
764 // Other objects querying to see if we support a
765 // particular format
766 //-----------------------------------------------------
767 STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
768 // Arbitrary system formats are used for image feedback during drag
769 // and drop. We are responsible for storing these internally during
770 // drag operations.
771 LPDATAENTRY pde;
772 if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
774 // Firefox internal formats
775 ULONG count;
776 FORMATETC fe;
777 m_enumFE->Reset();
778 while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
779 if (fe.cfFormat == pFE->cfFormat) {
780 return S_OK;
783 return E_FAIL;
786 //-----------------------------------------------------
787 STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
788 LPFORMATETC pFEOut) {
789 return E_NOTIMPL;
792 //-----------------------------------------------------
793 STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
794 BOOL shouldRel) {
795 // Arbitrary system formats are used for image feedback during drag
796 // and drop. We are responsible for storing these internally during
797 // drag operations.
798 LPDATAENTRY pde;
799 if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
800 // Release the old data the lookup handed us for this format. This
801 // may have been set in CopyMediumData when we originally stored the
802 // data.
803 if (pde->stgm.tymed) {
804 ReleaseStgMedium(&pde->stgm);
805 memset(&pde->stgm, 0, sizeof(STGMEDIUM));
808 bool result = true;
809 if (shouldRel) {
810 // If shouldRel is TRUE, the data object called owns the storage medium
811 // after the call returns. Store the incoming data in our data array for
812 // release when we are destroyed. This is the common case with arbitrary
813 // data from explorer.
814 pde->stgm = *aMedium;
815 } else {
816 // Copy the incoming data into our data array. (AFAICT, this never gets
817 // called with arbitrary formats for drag images.)
818 result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
820 pde->fe.tymed = pde->stgm.tymed;
822 return result ? S_OK : DV_E_TYMED;
825 if (shouldRel) ReleaseStgMedium(aMedium);
827 return S_OK;
830 bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
831 LPDATAENTRY* aDataEntry,
832 BOOL aAddorUpdate) {
833 *aDataEntry = nullptr;
835 if (aFormat->ptd != nullptr) return false;
837 // See if it's already in our list. If so return the data entry.
838 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
839 if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
840 mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
841 mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
842 if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
843 // If the caller requests we update, or if the
844 // medium type matches, return the entry.
845 *aDataEntry = mDataEntryList[idx];
846 return true;
847 } else {
848 // Medium does not match, not found.
849 return false;
854 if (!aAddorUpdate) return false;
856 // Add another entry to mDataEntryList
857 LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
858 if (!dataEntry) return false;
860 dataEntry->fe = *aFormat;
861 *aDataEntry = dataEntry;
862 memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
864 // Add this to our IEnumFORMATETC impl. so we can return it when
865 // it's requested.
866 m_enumFE->AddFormatEtc(aFormat);
868 // Store a copy internally in the arbitrary formats array.
869 mDataEntryList.AppendElement(dataEntry);
871 return true;
874 bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
875 LPFORMATETC aFormat, BOOL aSetData) {
876 STGMEDIUM stgmOut = *aMediumSrc;
878 switch (stgmOut.tymed) {
879 case TYMED_ISTREAM:
880 stgmOut.pstm->AddRef();
881 break;
882 case TYMED_ISTORAGE:
883 stgmOut.pstg->AddRef();
884 break;
885 case TYMED_HGLOBAL:
886 if (!aMediumSrc->pUnkForRelease) {
887 if (aSetData) {
888 if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
889 stgmOut.hGlobal =
890 OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
891 if (!stgmOut.hGlobal) return false;
892 } else {
893 // We are returning this data from LookupArbitraryFormat, indicate to
894 // the shell we hold it and will free it.
895 stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
898 break;
899 default:
900 return false;
903 if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
905 *aMediumDst = stgmOut;
907 return true;
910 //-----------------------------------------------------
911 STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
912 switch (dwDir) {
913 case DATADIR_GET:
914 m_enumFE->Clone(ppEnum);
915 break;
916 case DATADIR_SET:
917 // fall through
918 default:
919 *ppEnum = nullptr;
920 } // switch
922 if (nullptr == *ppEnum) return E_FAIL;
924 (*ppEnum)->Reset();
925 // Clone already AddRefed the result so don't addref it again.
926 return NOERROR;
929 //-----------------------------------------------------
930 STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
931 LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
932 return OLE_E_ADVISENOTSUPPORTED;
935 //-----------------------------------------------------
936 STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
937 return OLE_E_ADVISENOTSUPPORTED;
940 //-----------------------------------------------------
941 STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
942 return OLE_E_ADVISENOTSUPPORTED;
945 // IDataObjectAsyncCapability methods
946 STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
947 DWORD dwEffects) {
948 mIsInOperation = FALSE;
949 return S_OK;
952 STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
953 *pfIsOpAsync = mIsAsyncMode;
955 return S_OK;
958 STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
959 *pfInAsyncOp = mIsInOperation;
961 return S_OK;
964 STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
965 mIsAsyncMode = fDoOpAsync;
966 return S_OK;
969 STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
970 mIsInOperation = TRUE;
971 return S_OK;
975 // GetDIB
977 // Someone is asking for a bitmap. The data in the transferable will be a
978 // straight imgIContainer, so just QI it.
980 HRESULT
981 nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
982 STGMEDIUM& aSTG) {
983 nsCOMPtr<nsISupports> genericDataWrapper;
984 if (NS_FAILED(
985 mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
986 getter_AddRefs(genericDataWrapper)))) {
987 return E_FAIL;
990 nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
991 if (!image) {
992 return E_FAIL;
995 nsCOMPtr<imgITools> imgTools =
996 do_CreateInstance("@mozilla.org/image/tools;1");
998 nsAutoString options(u"bpp=32;"_ns);
999 if (aFormat.cfFormat == CF_DIBV5) {
1000 options.AppendLiteral("version=5");
1001 } else {
1002 options.AppendLiteral("version=3");
1005 nsCOMPtr<nsIInputStream> inputStream;
1006 nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
1007 options, getter_AddRefs(inputStream));
1008 if (NS_FAILED(rv) || !inputStream) {
1009 return E_FAIL;
1012 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1013 if (!encoder) {
1014 return E_FAIL;
1017 uint32_t size = 0;
1018 rv = encoder->GetImageBufferUsed(&size);
1019 if (NS_FAILED(rv) || size <= BFH_LENGTH) {
1020 return E_FAIL;
1023 char* src = nullptr;
1024 rv = encoder->GetImageBuffer(&src);
1025 if (NS_FAILED(rv) || !src) {
1026 return E_FAIL;
1029 // We don't want the file header.
1030 src += BFH_LENGTH;
1031 size -= BFH_LENGTH;
1033 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
1034 if (!glob) {
1035 return E_FAIL;
1038 char* dst = (char*)::GlobalLock(glob);
1039 ::CopyMemory(dst, src, size);
1040 ::GlobalUnlock(glob);
1042 aSTG.hGlobal = glob;
1043 aSTG.tymed = TYMED_HGLOBAL;
1044 return S_OK;
1048 // GetFileDescriptor
1051 HRESULT
1052 nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
1053 bool aIsUnicode) {
1054 HRESULT res = S_OK;
1056 // How we handle this depends on if we're dealing with an internet
1057 // shortcut, since those are done under the covers.
1058 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
1059 if (aIsUnicode)
1060 return GetFileDescriptor_IStreamW(aFE, aSTG);
1061 else
1062 return GetFileDescriptor_IStreamA(aFE, aSTG);
1063 } else if (IsFlavourPresent(kURLMime)) {
1064 if (aIsUnicode)
1065 res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
1066 else
1067 res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
1068 } else
1069 NS_WARNING("Not yet implemented\n");
1071 return res;
1072 } // GetFileDescriptor
1075 HRESULT
1076 nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
1077 HRESULT res = S_OK;
1079 // How we handle this depends on if we're dealing with an internet
1080 // shortcut, since those are done under the covers.
1081 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
1082 return GetFileContents_IStream(aFE, aSTG);
1083 else if (IsFlavourPresent(kURLMime))
1084 return GetFileContentsInternetShortcut(aFE, aSTG);
1085 else
1086 NS_WARNING("Not yet implemented\n");
1088 return res;
1090 } // GetFileContents
1092 // Ensure that the supplied name doesn't have invalid characters.
1093 static void ValidateFilename(nsString& aFilename) {
1094 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
1095 if (NS_WARN_IF(!mimeService)) {
1096 aFilename.Truncate();
1097 return;
1100 nsAutoString outFilename;
1101 mimeService->ValidateFileNameForSaving(aFilename, EmptyCString(),
1102 nsIMIMEService::VALIDATE_SANITIZE_ONLY,
1103 outFilename);
1104 aFilename = outFilename;
1108 // Given a unicode string, convert it down to a valid local charset filename
1109 // with the supplied extension. This ensures that we do not cut MBCS characters
1110 // in the middle.
1112 // It would seem that this is more functionality suited to being in nsIFile.
1114 static bool CreateFilenameFromTextA(nsString& aText, const char* aExtension,
1115 char* aFilename, uint32_t aFilenameLen) {
1116 ValidateFilename(aText);
1117 if (aText.IsEmpty()) return false;
1119 // repeatably call WideCharToMultiByte as long as the title doesn't fit in the
1120 // buffer available to us. Continually reduce the length of the source title
1121 // until the MBCS version will fit in the buffer with room for the supplied
1122 // extension. Doing it this way ensures that even in MBCS environments there
1123 // will be a valid MBCS filename of the correct length.
1124 int maxUsableFilenameLen =
1125 aFilenameLen - strlen(aExtension) - 1; // space for ext + null byte
1126 int currLen, textLen = (int)std::min<uint32_t>(aText.Length(), aFilenameLen);
1127 char defaultChar = '_';
1128 do {
1129 currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
1130 aText.get(), textLen--, aFilename,
1131 maxUsableFilenameLen, &defaultChar, nullptr);
1132 } while (currLen == 0 && textLen > 0 &&
1133 GetLastError() == ERROR_INSUFFICIENT_BUFFER);
1134 if (currLen > 0 && textLen > 0) {
1135 strcpy(&aFilename[currLen], aExtension);
1136 return true;
1137 } else {
1138 // empty names aren't permitted
1139 return false;
1143 static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
1144 wchar_t* aFilename, uint32_t aFilenameLen) {
1145 ValidateFilename(aText);
1146 if (aText.IsEmpty()) return false;
1148 const int extensionLen = wcslen(aExtension);
1149 if (aText.Length() + extensionLen + 1 > aFilenameLen)
1150 aText.Truncate(aFilenameLen - extensionLen - 1);
1151 wcscpy(&aFilename[0], aText.get());
1152 wcscpy(&aFilename[aText.Length()], aExtension);
1153 return true;
1156 #define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
1158 static bool GetLocalizedString(const char* aName, nsAString& aString) {
1159 nsCOMPtr<nsIStringBundleService> stringService =
1160 mozilla::components::StringBundle::Service();
1161 if (!stringService) return false;
1163 nsCOMPtr<nsIStringBundle> stringBundle;
1164 nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
1165 getter_AddRefs(stringBundle));
1166 if (NS_FAILED(rv)) return false;
1168 rv = stringBundle->GetStringFromName(aName, aString);
1169 return NS_SUCCEEDED(rv);
1173 // GetFileDescriptorInternetShortcut
1175 // Create the special format for an internet shortcut and build up the data
1176 // structures the shell is expecting.
1178 HRESULT
1179 nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
1180 STGMEDIUM& aSTG) {
1181 // get the title of the shortcut
1182 nsAutoString title;
1183 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1185 HGLOBAL fileGroupDescHandle =
1186 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
1187 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1189 LPFILEGROUPDESCRIPTORA fileGroupDescA =
1190 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
1191 ::GlobalLock(fileGroupDescHandle));
1192 if (!fileGroupDescA) {
1193 ::GlobalFree(fileGroupDescHandle);
1194 return E_OUTOFMEMORY;
1197 // get a valid filename in the following order: 1) from the page title,
1198 // 2) localized string for an untitled page, 3) just use "Untitled.URL"
1199 if (!CreateFilenameFromTextA(title, ".URL", fileGroupDescA->fgd[0].cFileName,
1200 NS_MAX_FILEDESCRIPTOR)) {
1201 nsAutoString untitled;
1202 if (!GetLocalizedString("noPageTitle", untitled) ||
1203 !CreateFilenameFromTextA(untitled, ".URL",
1204 fileGroupDescA->fgd[0].cFileName,
1205 NS_MAX_FILEDESCRIPTOR)) {
1206 strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.URL");
1210 // one file in the file block
1211 fileGroupDescA->cItems = 1;
1212 fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
1214 ::GlobalUnlock(fileGroupDescHandle);
1215 aSTG.hGlobal = fileGroupDescHandle;
1216 aSTG.tymed = TYMED_HGLOBAL;
1218 return S_OK;
1219 } // GetFileDescriptorInternetShortcutA
1221 HRESULT
1222 nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
1223 STGMEDIUM& aSTG) {
1224 // get the title of the shortcut
1225 nsAutoString title;
1226 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1228 HGLOBAL fileGroupDescHandle =
1229 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
1230 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1232 LPFILEGROUPDESCRIPTORW fileGroupDescW =
1233 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
1234 ::GlobalLock(fileGroupDescHandle));
1235 if (!fileGroupDescW) {
1236 ::GlobalFree(fileGroupDescHandle);
1237 return E_OUTOFMEMORY;
1240 // get a valid filename in the following order: 1) from the page title,
1241 // 2) localized string for an untitled page, 3) just use "Untitled.URL"
1242 if (!CreateFilenameFromTextW(title, L".URL", fileGroupDescW->fgd[0].cFileName,
1243 NS_MAX_FILEDESCRIPTOR)) {
1244 nsAutoString untitled;
1245 if (!GetLocalizedString("noPageTitle", untitled) ||
1246 !CreateFilenameFromTextW(untitled, L".URL",
1247 fileGroupDescW->fgd[0].cFileName,
1248 NS_MAX_FILEDESCRIPTOR)) {
1249 wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.URL");
1253 // one file in the file block
1254 fileGroupDescW->cItems = 1;
1255 fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
1257 ::GlobalUnlock(fileGroupDescHandle);
1258 aSTG.hGlobal = fileGroupDescHandle;
1259 aSTG.tymed = TYMED_HGLOBAL;
1261 return S_OK;
1262 } // GetFileDescriptorInternetShortcutW
1265 // GetFileContentsInternetShortcut
1267 // Create the special format for an internet shortcut and build up the data
1268 // structures the shell is expecting.
1270 HRESULT
1271 nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
1272 static const char* kShellIconPref = "browser.shell.shortcutFavicons";
1273 nsAutoString url;
1274 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1276 nsCOMPtr<nsIURI> aUri;
1277 nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
1278 if (NS_FAILED(rv)) {
1279 return E_FAIL;
1282 nsAutoCString asciiUrl;
1283 rv = aUri->GetAsciiSpec(asciiUrl);
1284 if (NS_FAILED(rv)) {
1285 return E_FAIL;
1288 RefPtr<AutoCloseEvent> event;
1290 const char* shortcutFormatStr;
1291 int totalLen;
1292 nsCString asciiPath;
1293 if (!Preferences::GetBool(kShellIconPref, true)) {
1294 shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
1295 const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
1296 totalLen = formatLen + asciiUrl.Length(); // don't include null character
1297 } else {
1298 nsCOMPtr<nsIFile> icoFile;
1300 nsAutoString aUriHash;
1302 event = new AutoCloseEvent();
1303 if (!event->IsInited()) {
1304 return E_FAIL;
1307 RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event));
1308 mozilla::widget::FaviconHelper::ObtainCachedIconFile(
1309 aUri, aUriHash, mIOThread, true,
1310 NS_NewRunnableFunction(
1311 "FaviconHelper::RefreshDesktop", [e = std::move(e)] {
1312 if (e->IsWaiting()) {
1313 // Unblock IStream:::Read.
1314 e->Signal();
1315 } else {
1316 // We could not wait until the favicon was available. We have
1317 // to refresh to refect the favicon.
1318 SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
1319 SPI_SETNONCLIENTMETRICS, 0);
1321 }));
1323 rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
1324 NS_ENSURE_SUCCESS(rv, E_FAIL);
1325 nsString path;
1326 rv = icoFile->GetPath(path);
1327 NS_ENSURE_SUCCESS(rv, E_FAIL);
1329 if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
1330 LossyCopyUTF16toASCII(path, asciiPath);
1331 shortcutFormatStr =
1332 "[InternetShortcut]\r\nURL=%s\r\n"
1333 "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
1334 "IconIndex=0\r\n";
1335 } else {
1336 int len =
1337 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1338 path.Length(), nullptr, 0, nullptr, nullptr);
1339 NS_ENSURE_TRUE(len > 0, E_FAIL);
1340 asciiPath.SetLength(len);
1341 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1342 path.Length(), asciiPath.BeginWriting(), len, nullptr,
1343 nullptr);
1344 shortcutFormatStr =
1345 "[InternetShortcut]\r\nURL=%s\r\n"
1346 "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
1347 "[InternetShortcut.W]\r\nIconFile=%s\r\n";
1349 const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
1350 totalLen = formatLen + asciiUrl.Length() +
1351 asciiPath.Length(); // we don't want a null character on the end
1354 // create a global memory area and build up the file contents w/in it
1355 nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen)));
1356 if (!globalMem) return E_OUTOFMEMORY;
1358 char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get()));
1359 if (!contents) {
1360 return E_OUTOFMEMORY;
1363 // NOTE: we intentionally use the Microsoft version of snprintf here because
1364 // it does NOT null
1365 // terminate strings which reach the maximum size of the buffer. Since we know
1366 // that the formatted length here is totalLen, this call to _snprintf will
1367 // format the string into the buffer without appending the null character.
1369 if (!Preferences::GetBool(kShellIconPref, true)) {
1370 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
1371 } else {
1372 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
1373 asciiPath.get());
1376 ::GlobalUnlock(globalMem.get());
1378 if (aFE.tymed & TYMED_ISTREAM) {
1379 if (!mIsInOperation) {
1380 // The drop target didn't initiate an async operation.
1381 // We can't block CMemStream::Read.
1382 event = nullptr;
1384 RefPtr<IStream> stream =
1385 new CMemStream(globalMem.disown(), totalLen, event.forget());
1386 stream.forget(&aSTG.pstm);
1387 aSTG.tymed = TYMED_ISTREAM;
1388 } else {
1389 if (event && event->IsInited()) {
1390 event->Signal(); // We can't block reading the global memory
1392 aSTG.hGlobal = globalMem.disown();
1393 aSTG.tymed = TYMED_HGLOBAL;
1396 return S_OK;
1397 } // GetFileContentsInternetShortcut
1399 // check if specified flavour is present in the transferable
1400 bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
1401 bool retval = false;
1402 NS_ENSURE_TRUE(mTransferable, false);
1404 // get the list of flavors available in the transferable
1405 nsTArray<nsCString> flavors;
1406 nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
1407 NS_ENSURE_SUCCESS(rv, false);
1409 // try to find requested flavour
1410 for (uint32_t i = 0; i < flavors.Length(); ++i) {
1411 if (flavors[i].Equals(inFlavour)) {
1412 retval = true; // found it!
1413 break;
1415 } // for each flavor
1417 return retval;
1420 HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
1421 HRESULT res = S_OK;
1422 aSTG.tymed = TYMED_HGLOBAL;
1423 aSTG.pUnkForRelease = nullptr;
1424 HGLOBAL hGlobalMemory = nullptr;
1425 hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
1426 if (hGlobalMemory) {
1427 DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
1428 // The PreferredDropEffect clipboard format is only registered if a
1429 // drag/drop of an image happens from Mozilla to the desktop. We want its
1430 // value to be DROPEFFECT_MOVE in that case so that the file is moved from
1431 // the temporary location, not copied. This value should, ideally, be set on
1432 // the data object via SetData() but our IDataObject implementation doesn't
1433 // implement SetData. It adds data to the data object lazily only when the
1434 // drop target asks for it.
1435 *pdw = (DWORD)DROPEFFECT_MOVE;
1436 GlobalUnlock(hGlobalMemory);
1437 } else {
1438 res = E_OUTOFMEMORY;
1440 aSTG.hGlobal = hGlobalMemory;
1441 return res;
1444 //-----------------------------------------------------
1445 HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
1446 STGMEDIUM& aSTG) {
1447 void* data = nullptr;
1449 // if someone asks for text/plain, look up text/unicode instead in the
1450 // transferable.
1451 const char* flavorStr;
1452 const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor);
1453 if (aDataFlavor.EqualsLiteral("text/plain"))
1454 flavorStr = kUnicodeMime;
1455 else
1456 flavorStr = flat.get();
1458 // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
1459 nsCOMPtr<nsISupports> genericDataWrapper;
1460 nsresult rv = mTransferable->GetTransferData(
1461 flavorStr, getter_AddRefs(genericDataWrapper));
1462 if (NS_FAILED(rv) || !genericDataWrapper) {
1463 return E_FAIL;
1466 uint32_t len;
1467 nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(flavorStr),
1468 genericDataWrapper, &data, &len);
1469 if (!data) return E_FAIL;
1471 HGLOBAL hGlobalMemory = nullptr;
1473 aSTG.tymed = TYMED_HGLOBAL;
1474 aSTG.pUnkForRelease = nullptr;
1476 // We play games under the hood and advertise flavors that we know we
1477 // can support, only they require a bit of conversion or munging of the data.
1478 // Do that here.
1480 // The transferable gives us data that is null-terminated, but this isn't
1481 // reflected in the |len| parameter. Windoze apps expect this null to be there
1482 // so bump our data buffer by the appropriate size to account for the null
1483 // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
1484 DWORD allocLen = (DWORD)len;
1485 if (aFE.cfFormat == CF_TEXT) {
1486 // Someone is asking for text/plain; convert the unicode (assuming it's
1487 // present) to text with the correct platform encoding.
1488 size_t bufferSize = sizeof(char) * (len + 2);
1489 char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
1490 char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
1491 int32_t plainTextLen =
1492 WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
1493 plainTextData, bufferSize, NULL, NULL);
1494 // replace the unicode data with our plaintext data. Recall that
1495 // |plainTextLen| doesn't include the null in the length.
1496 free(data);
1497 if (plainTextLen) {
1498 data = plainTextData;
1499 allocLen = plainTextLen;
1500 } else {
1501 free(plainTextData);
1502 NS_WARNING("Oh no, couldn't convert unicode to plain text");
1503 return S_OK;
1505 } else if (aFE.cfFormat == nsClipboard::GetHtmlClipboardFormat()) {
1506 // Someone is asking for win32's HTML flavor. Convert our html fragment
1507 // from unicode to UTF-8 then put it into a format specified by msft.
1508 NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
1509 char* utf8HTML = nullptr;
1510 nsresult rv =
1511 BuildPlatformHTML(converter.get(), &utf8HTML); // null terminates
1513 free(data);
1514 if (NS_SUCCEEDED(rv) && utf8HTML) {
1515 // replace the unicode data with our HTML data. Don't forget the null.
1516 data = utf8HTML;
1517 allocLen = strlen(utf8HTML) + sizeof(char);
1518 } else {
1519 NS_WARNING("Oh no, couldn't convert to HTML");
1520 return S_OK;
1522 } else if (aFE.cfFormat != nsClipboard::GetCustomClipboardFormat()) {
1523 // we assume that any data that isn't caught above is unicode. This may
1524 // be an erroneous assumption, but is true so far.
1525 allocLen += sizeof(char16_t);
1528 hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
1530 // Copy text to Global Memory Area
1531 if (hGlobalMemory) {
1532 char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
1533 char* source = reinterpret_cast<char*>(data);
1534 memcpy(dest, source, allocLen); // copies the null as well
1535 GlobalUnlock(hGlobalMemory);
1537 aSTG.hGlobal = hGlobalMemory;
1539 // Now, delete the memory that was created by CreateDataFromPrimitive (or our
1540 // text/plain data)
1541 free(data);
1543 return S_OK;
1546 //-----------------------------------------------------
1547 HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1548 uint32_t dfInx = 0;
1549 ULONG count;
1550 FORMATETC fe;
1551 m_enumFE->Reset();
1552 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
1553 dfInx < mDataFlavors.Length()) {
1554 if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
1555 return DropImage(aFE, aSTG);
1556 if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
1557 return DropFile(aFE, aSTG);
1558 if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
1559 return DropTempFile(aFE, aSTG);
1560 dfInx++;
1562 return E_FAIL;
1565 HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1566 nsresult rv;
1567 nsCOMPtr<nsISupports> genericDataWrapper;
1569 if (NS_FAILED(mTransferable->GetTransferData(
1570 kFileMime, getter_AddRefs(genericDataWrapper)))) {
1571 return E_FAIL;
1573 nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
1574 if (!file) return E_FAIL;
1576 aSTG.tymed = TYMED_HGLOBAL;
1577 aSTG.pUnkForRelease = nullptr;
1579 nsAutoString path;
1580 rv = file->GetPath(path);
1581 if (NS_FAILED(rv)) return E_FAIL;
1583 uint32_t allocLen = path.Length() + 2;
1584 HGLOBAL hGlobalMemory = nullptr;
1585 char16_t* dest;
1587 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1588 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1589 if (!hGlobalMemory) return E_FAIL;
1591 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1593 // First, populate the drop file structure
1594 pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string
1595 pDropFile->fNC = 0;
1596 pDropFile->pt.x = 0;
1597 pDropFile->pt.y = 0;
1598 pDropFile->fWide = TRUE;
1600 // Copy the filename right after the DROPFILES structure
1601 dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1602 memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
1604 // Two null characters are needed at the end of the file name.
1605 // Lookup the CF_HDROP shell clipboard format for more info.
1606 // Add the second null character right after the first one.
1607 dest[allocLen - 1] = L'\0';
1609 GlobalUnlock(hGlobalMemory);
1611 aSTG.hGlobal = hGlobalMemory;
1613 return S_OK;
1616 HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
1617 nsresult rv;
1618 if (!mCachedTempFile) {
1619 nsCOMPtr<nsISupports> genericDataWrapper;
1621 if (NS_FAILED(mTransferable->GetTransferData(
1622 kNativeImageMime, getter_AddRefs(genericDataWrapper)))) {
1623 return E_FAIL;
1625 nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
1626 if (!image) return E_FAIL;
1628 nsCOMPtr<imgITools> imgTools =
1629 do_CreateInstance("@mozilla.org/image/tools;1");
1630 nsCOMPtr<nsIInputStream> inputStream;
1631 rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
1632 u"bpp=32;version=3"_ns,
1633 getter_AddRefs(inputStream));
1634 if (NS_FAILED(rv) || !inputStream) {
1635 return E_FAIL;
1638 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1639 if (!encoder) {
1640 return E_FAIL;
1643 uint32_t size = 0;
1644 rv = encoder->GetImageBufferUsed(&size);
1645 if (NS_FAILED(rv)) {
1646 return E_FAIL;
1649 char* src = nullptr;
1650 rv = encoder->GetImageBuffer(&src);
1651 if (NS_FAILED(rv) || !src) {
1652 return E_FAIL;
1655 // Save the bitmap to a temporary location.
1656 nsCOMPtr<nsIFile> dropFile;
1657 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1658 if (!dropFile) {
1659 return E_FAIL;
1662 // Filename must be random so as not to confuse apps like
1663 // Photoshop which handle multiple drags into a single window.
1664 char buf[13];
1665 nsCString filename;
1666 NS_MakeRandomString(buf, 8);
1667 memcpy(buf + 8, ".bmp", 5);
1668 filename.Append(nsDependentCString(buf, 12));
1669 dropFile->AppendNative(filename);
1670 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1671 if (NS_FAILED(rv)) {
1672 return E_FAIL;
1675 // Cache the temp file so we can delete it later and so
1676 // it doesn't get recreated over and over on multiple calls
1677 // which does occur from windows shell.
1678 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1680 // Write the data to disk.
1681 nsCOMPtr<nsIOutputStream> outStream;
1682 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1683 if (NS_FAILED(rv)) {
1684 return E_FAIL;
1687 uint32_t written = 0;
1688 rv = outStream->Write(src, size, &written);
1689 if (NS_FAILED(rv) || written != size) {
1690 return E_FAIL;
1693 outStream->Close();
1696 // Pass the file name back to the drop target so that it can access the file.
1697 nsAutoString path;
1698 rv = mCachedTempFile->GetPath(path);
1699 if (NS_FAILED(rv)) return E_FAIL;
1701 // Two null characters are needed to terminate the file name list.
1702 HGLOBAL hGlobalMemory = nullptr;
1704 uint32_t allocLen = path.Length() + 2;
1706 aSTG.tymed = TYMED_HGLOBAL;
1707 aSTG.pUnkForRelease = nullptr;
1709 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1710 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1711 if (!hGlobalMemory) return E_FAIL;
1713 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1715 // First, populate the drop file structure.
1716 pDropFile->pFiles =
1717 sizeof(DROPFILES); // Offset to start of file name char array.
1718 pDropFile->fNC = 0;
1719 pDropFile->pt.x = 0;
1720 pDropFile->pt.y = 0;
1721 pDropFile->fWide = TRUE;
1723 // Copy the filename right after the DROPFILES structure.
1724 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1725 memcpy(dest, path.get(),
1726 (allocLen - 1) *
1727 sizeof(char16_t)); // Copies the null character in path as well.
1729 // Two null characters are needed at the end of the file name.
1730 // Lookup the CF_HDROP shell clipboard format for more info.
1731 // Add the second null character right after the first one.
1732 dest[allocLen - 1] = L'\0';
1734 GlobalUnlock(hGlobalMemory);
1736 aSTG.hGlobal = hGlobalMemory;
1738 return S_OK;
1741 HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1742 nsresult rv;
1743 if (!mCachedTempFile) {
1744 // Tempfile will need a temporary location.
1745 nsCOMPtr<nsIFile> dropFile;
1746 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1747 if (!dropFile) return E_FAIL;
1749 // Filename must be random
1750 nsCString filename;
1751 nsAutoString wideFileName;
1752 nsCOMPtr<nsIURI> sourceURI;
1753 HRESULT res;
1754 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
1755 if (FAILED(res)) return res;
1756 NS_CopyUnicodeToNative(wideFileName, filename);
1758 dropFile->AppendNative(filename);
1759 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1760 if (NS_FAILED(rv)) return E_FAIL;
1762 // Cache the temp file so we can delete it later and so
1763 // it doesn't get recreated over and over on multiple calls
1764 // which does occur from windows shell.
1765 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1767 // Write the data to disk.
1768 nsCOMPtr<nsIOutputStream> outStream;
1769 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1770 if (NS_FAILED(rv)) return E_FAIL;
1772 IStream* pStream = nullptr;
1773 nsDataObj::CreateStream(&pStream);
1774 NS_ENSURE_TRUE(pStream, E_FAIL);
1776 char buffer[512];
1777 ULONG readCount = 0;
1778 uint32_t writeCount = 0;
1779 while (1) {
1780 HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
1781 if (FAILED(hres)) return E_FAIL;
1782 if (readCount == 0) break;
1783 rv = outStream->Write(buffer, readCount, &writeCount);
1784 if (NS_FAILED(rv)) return E_FAIL;
1786 outStream->Close();
1787 pStream->Release();
1790 // Pass the file name back to the drop target so that it can access the file.
1791 nsAutoString path;
1792 rv = mCachedTempFile->GetPath(path);
1793 if (NS_FAILED(rv)) return E_FAIL;
1795 uint32_t allocLen = path.Length() + 2;
1797 // Two null characters are needed to terminate the file name list.
1798 HGLOBAL hGlobalMemory = nullptr;
1800 aSTG.tymed = TYMED_HGLOBAL;
1801 aSTG.pUnkForRelease = nullptr;
1803 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1804 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1805 if (!hGlobalMemory) return E_FAIL;
1807 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1809 // First, populate the drop file structure.
1810 pDropFile->pFiles =
1811 sizeof(DROPFILES); // Offset to start of file name char array.
1812 pDropFile->fNC = 0;
1813 pDropFile->pt.x = 0;
1814 pDropFile->pt.y = 0;
1815 pDropFile->fWide = TRUE;
1817 // Copy the filename right after the DROPFILES structure.
1818 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1819 memcpy(dest, path.get(),
1820 (allocLen - 1) *
1821 sizeof(char16_t)); // Copies the null character in path as well.
1823 // Two null characters are needed at the end of the file name.
1824 // Lookup the CF_HDROP shell clipboard format for more info.
1825 // Add the second null character right after the first one.
1826 dest[allocLen - 1] = L'\0';
1828 GlobalUnlock(hGlobalMemory);
1830 aSTG.hGlobal = hGlobalMemory;
1832 return S_OK;
1835 //-----------------------------------------------------
1836 // Registers the DataFlavor/FE pair.
1837 //-----------------------------------------------------
1838 void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
1839 // These two lists are the mapping to and from data flavors and FEs.
1840 // Later, OLE will tell us it needs a certain type of FORMATETC (text,
1841 // unicode, etc) unicode, etc), so we will look up the data flavor that
1842 // corresponds to the FE and then ask the transferable for that type of data.
1843 mDataFlavors.AppendElement(aDataFlavor);
1844 m_enumFE->AddFormatEtc(aFE);
1847 //-----------------------------------------------------
1848 // Sets the transferable object
1849 //-----------------------------------------------------
1850 void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
1851 NS_IF_RELEASE(mTransferable);
1853 mTransferable = aTransferable;
1854 if (nullptr == mTransferable) {
1855 return;
1858 NS_ADDREF(mTransferable);
1860 return;
1864 // ExtractURL
1866 // Roots around in the transferable for the appropriate flavor that indicates
1867 // a url and pulls out the url portion of the data. Used mostly for creating
1868 // internet shortcuts on the desktop. The url flavor is of the format:
1870 // <url> <linefeed> <page title>
1872 nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
1873 NS_ASSERTION(mTransferable, "We don't have a good transferable");
1874 nsresult rv = NS_ERROR_FAILURE;
1876 nsCOMPtr<nsISupports> genericURL;
1877 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1878 kURLMime, getter_AddRefs(genericURL)))) {
1879 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1880 if (urlObject) {
1881 nsAutoString url;
1882 urlObject->GetData(url);
1883 outURL = url;
1885 // find the first linefeed in the data, that's where the url ends. trunc
1886 // the result string at that point.
1887 int32_t lineIndex = outURL.FindChar('\n');
1888 NS_ASSERTION(lineIndex > 0,
1889 "Format for url flavor is <url> <linefeed> <page title>");
1890 if (lineIndex > 0) {
1891 outURL.Truncate(lineIndex);
1892 rv = NS_OK;
1895 } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
1896 kURLDataMime, getter_AddRefs(genericURL))) ||
1897 NS_SUCCEEDED(mTransferable->GetTransferData(
1898 kURLPrivateMime, getter_AddRefs(genericURL)))) {
1899 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1900 if (urlObject) {
1901 nsAutoString url;
1902 urlObject->GetData(url);
1903 outURL = url;
1905 rv = NS_OK;
1908 } // if found flavor
1910 return rv;
1912 } // ExtractShortcutURL
1915 // ExtractShortcutTitle
1917 // Roots around in the transferable for the appropriate flavor that indicates
1918 // a url and pulls out the title portion of the data. Used mostly for creating
1919 // internet shortcuts on the desktop. The url flavor is of the format:
1921 // <url> <linefeed> <page title>
1923 nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
1924 NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
1925 nsresult rv = NS_ERROR_FAILURE;
1927 nsCOMPtr<nsISupports> genericURL;
1928 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1929 kURLMime, getter_AddRefs(genericURL)))) {
1930 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1931 if (urlObject) {
1932 nsAutoString url;
1933 urlObject->GetData(url);
1935 // find the first linefeed in the data, that's where the url ends. we want
1936 // everything after that linefeed. FindChar() returns -1 if we can't find
1937 int32_t lineIndex = url.FindChar('\n');
1938 NS_ASSERTION(lineIndex != -1,
1939 "Format for url flavor is <url> <linefeed> <page title>");
1940 if (lineIndex != -1) {
1941 url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
1942 rv = NS_OK;
1945 } // if found flavor
1947 return rv;
1949 } // ExtractShortcutTitle
1952 // BuildPlatformHTML
1954 // Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
1955 // header information on it. This will null-terminate |outPlatformHTML|. See
1956 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
1957 // for details.
1959 // We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
1960 // or <BODY> tags). We'll wrap the fragment with them to make other apps
1961 // happy.
1963 nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
1964 char** outPlatformHTML) {
1965 *outPlatformHTML = nullptr;
1966 nsDependentCString inHTMLString(inOurHTML);
1968 // Do we already have mSourceURL from a drag?
1969 if (mSourceURL.IsEmpty()) {
1970 nsAutoString url;
1971 ExtractShortcutURL(url);
1973 AppendUTF16toUTF8(url, mSourceURL);
1976 constexpr auto kStartHTMLPrefix = "Version:0.9\r\nStartHTML:"_ns;
1977 constexpr auto kEndHTMLPrefix = "\r\nEndHTML:"_ns;
1978 constexpr auto kStartFragPrefix = "\r\nStartFragment:"_ns;
1979 constexpr auto kEndFragPrefix = "\r\nEndFragment:"_ns;
1980 constexpr auto kStartSourceURLPrefix = "\r\nSourceURL:"_ns;
1981 constexpr auto kEndFragTrailer = "\r\n"_ns;
1983 // The CF_HTML's size is embedded in the fragment, in such a way that the
1984 // number of digits in the size is part of the size itself. While it _is_
1985 // technically possible to compute the necessary size of the size-field
1986 // precisely -- by trial and error, if nothing else -- it's simpler just to
1987 // pick a rough but generous estimate and zero-pad it. (Zero-padding is
1988 // explicitly permitted by the format definition.)
1990 // Originally, in 2001, the "rough but generous estimate" was 8 digits. While
1991 // a maximum size of (10**9 - 1) bytes probably would have covered all
1992 // possible use-cases at the time, it's somewhat more likely to overflow
1993 // nowadays. Nonetheless, for the sake of backwards compatibility with any
1994 // misbehaving consumers of our existing CF_HTML output, we retain exactly
1995 // that padding for (most) fragments where it suffices. (No such misbehaving
1996 // consumers are actually known, so this is arguably paranoia.)
1998 // It is now 2022. A padding size of 16 will cover up to about 8.8 petabytes,
1999 // which should be enough for at least the next few years or so.
2000 const size_t numberLength = inHTMLString.Length() < 9999'0000 ? 8 : 16;
2002 const size_t sourceURLLength = mSourceURL.Length();
2004 const size_t fixedHeaderLen =
2005 kStartHTMLPrefix.Length() + kEndHTMLPrefix.Length() +
2006 kStartFragPrefix.Length() + kEndFragPrefix.Length() +
2007 kEndFragTrailer.Length() + (4 * numberLength);
2009 const size_t totalHeaderLen =
2010 fixedHeaderLen + (sourceURLLength > 0
2011 ? kStartSourceURLPrefix.Length() + sourceURLLength
2012 : 0);
2014 constexpr auto kHeaderString = "<html><body>\r\n<!--StartFragment-->"_ns;
2015 constexpr auto kTrailingString =
2016 "<!--EndFragment-->\r\n"
2017 "</body>\r\n"
2018 "</html>"_ns;
2020 // calculate the offsets
2021 size_t startHTMLOffset = totalHeaderLen;
2022 size_t startFragOffset = startHTMLOffset + kHeaderString.Length();
2024 size_t endFragOffset = startFragOffset + inHTMLString.Length();
2025 size_t endHTMLOffset = endFragOffset + kTrailingString.Length();
2027 // now build the final version
2028 nsCString clipboardString;
2029 clipboardString.SetCapacity(endHTMLOffset);
2031 const int numberLengthInt = static_cast<int>(numberLength);
2032 clipboardString.Append(kStartHTMLPrefix);
2033 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startHTMLOffset);
2035 clipboardString.Append(kEndHTMLPrefix);
2036 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endHTMLOffset);
2038 clipboardString.Append(kStartFragPrefix);
2039 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startFragOffset);
2041 clipboardString.Append(kEndFragPrefix);
2042 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endFragOffset);
2044 if (sourceURLLength > 0) {
2045 clipboardString.Append(kStartSourceURLPrefix);
2046 clipboardString.Append(mSourceURL);
2049 clipboardString.Append(kEndFragTrailer);
2051 // Assert that the positional values were correct as we pass by their
2052 // corresponding positions.
2053 MOZ_ASSERT(clipboardString.Length() == startHTMLOffset);
2054 clipboardString.Append(kHeaderString);
2055 MOZ_ASSERT(clipboardString.Length() == startFragOffset);
2056 clipboardString.Append(inHTMLString);
2057 MOZ_ASSERT(clipboardString.Length() == endFragOffset);
2058 clipboardString.Append(kTrailingString);
2059 MOZ_ASSERT(clipboardString.Length() == endHTMLOffset);
2061 *outPlatformHTML = ToNewCString(clipboardString, mozilla::fallible);
2062 if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
2064 return NS_OK;
2067 HRESULT
2068 nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
2069 bool aIsUnicode) {
2070 HRESULT res = S_OK;
2071 if (IsFlavourPresent(kURLMime)) {
2072 if (aIsUnicode)
2073 res = ExtractUniformResourceLocatorW(aFE, aSTG);
2074 else
2075 res = ExtractUniformResourceLocatorA(aFE, aSTG);
2076 } else
2077 NS_WARNING("Not yet implemented\n");
2078 return res;
2081 HRESULT
2082 nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2083 HRESULT result = S_OK;
2085 nsAutoString url;
2086 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2088 NS_LossyConvertUTF16toASCII asciiUrl(url);
2089 const int totalLen = asciiUrl.Length() + 1;
2090 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2091 if (!hGlobalMemory) return E_OUTOFMEMORY;
2093 char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
2094 if (!contents) {
2095 GlobalFree(hGlobalMemory);
2096 return E_OUTOFMEMORY;
2099 strcpy(contents, asciiUrl.get());
2100 GlobalUnlock(hGlobalMemory);
2101 aSTG.hGlobal = hGlobalMemory;
2102 aSTG.tymed = TYMED_HGLOBAL;
2104 return result;
2107 HRESULT
2108 nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2109 HRESULT result = S_OK;
2111 nsAutoString url;
2112 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2114 const int totalLen = (url.Length() + 1) * sizeof(char16_t);
2115 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2116 if (!hGlobalMemory) return E_OUTOFMEMORY;
2118 wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
2119 if (!contents) {
2120 GlobalFree(hGlobalMemory);
2121 return E_OUTOFMEMORY;
2124 wcscpy(contents, url.get());
2125 GlobalUnlock(hGlobalMemory);
2126 aSTG.hGlobal = hGlobalMemory;
2127 aSTG.tymed = TYMED_HGLOBAL;
2129 return result;
2132 // Gets the filename from the kFilePromiseURLMime flavour
2133 HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
2134 nsAString& aFilename) {
2135 *aSourceURI = nullptr;
2137 NS_ENSURE_TRUE(mTransferable, E_FAIL);
2139 // get the URI from the kFilePromiseURLMime flavor
2140 nsCOMPtr<nsISupports> urlPrimitive;
2141 nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
2142 getter_AddRefs(urlPrimitive));
2143 NS_ENSURE_SUCCESS(rv, E_FAIL);
2144 nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
2145 NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
2147 nsAutoString srcUri;
2148 srcUrlPrimitive->GetData(srcUri);
2149 if (srcUri.IsEmpty()) return E_FAIL;
2150 nsCOMPtr<nsIURI> sourceURI;
2151 NS_NewURI(getter_AddRefs(sourceURI), srcUri);
2153 nsAutoString srcFileName;
2154 nsCOMPtr<nsISupports> fileNamePrimitive;
2155 Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
2156 getter_AddRefs(fileNamePrimitive));
2157 nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
2158 do_QueryInterface(fileNamePrimitive);
2159 if (srcFileNamePrimitive) {
2160 srcFileNamePrimitive->GetData(srcFileName);
2161 } else {
2162 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
2163 if (!sourceURL) return E_FAIL;
2165 nsAutoCString urlFileName;
2166 sourceURL->GetFileName(urlFileName);
2167 NS_UnescapeURL(urlFileName);
2168 CopyUTF8toUTF16(urlFileName, srcFileName);
2171 // make the name safe for the filesystem
2172 ValidateFilename(srcFileName);
2173 if (srcFileName.IsEmpty()) return E_FAIL;
2175 sourceURI.swap(*aSourceURI);
2176 aFilename = srcFileName;
2177 return S_OK;
2180 HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2181 HGLOBAL fileGroupDescHandle =
2182 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2183 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2185 LPFILEGROUPDESCRIPTORA fileGroupDescA =
2186 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
2187 if (!fileGroupDescA) {
2188 ::GlobalFree(fileGroupDescHandle);
2189 return E_OUTOFMEMORY;
2192 nsAutoString wideFileName;
2193 HRESULT res;
2194 nsCOMPtr<nsIURI> sourceURI;
2195 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2196 if (FAILED(res)) {
2197 ::GlobalFree(fileGroupDescHandle);
2198 return res;
2201 nsAutoCString nativeFileName;
2202 NS_CopyUnicodeToNative(wideFileName, nativeFileName);
2204 strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(),
2205 NS_MAX_FILEDESCRIPTOR - 1);
2206 fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
2208 // one file in the file block
2209 fileGroupDescA->cItems = 1;
2210 fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
2212 GlobalUnlock(fileGroupDescHandle);
2213 aSTG.hGlobal = fileGroupDescHandle;
2214 aSTG.tymed = TYMED_HGLOBAL;
2216 return S_OK;
2219 HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2220 HGLOBAL fileGroupDescHandle =
2221 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2222 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2224 LPFILEGROUPDESCRIPTORW fileGroupDescW =
2225 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
2226 if (!fileGroupDescW) {
2227 ::GlobalFree(fileGroupDescHandle);
2228 return E_OUTOFMEMORY;
2231 nsAutoString wideFileName;
2232 HRESULT res;
2233 nsCOMPtr<nsIURI> sourceURI;
2234 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2235 if (FAILED(res)) {
2236 ::GlobalFree(fileGroupDescHandle);
2237 return res;
2240 wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(),
2241 NS_MAX_FILEDESCRIPTOR - 1);
2242 fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
2243 // one file in the file block
2244 fileGroupDescW->cItems = 1;
2245 fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
2247 GlobalUnlock(fileGroupDescHandle);
2248 aSTG.hGlobal = fileGroupDescHandle;
2249 aSTG.tymed = TYMED_HGLOBAL;
2251 return S_OK;
2254 HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
2255 IStream* pStream = nullptr;
2257 nsDataObj::CreateStream(&pStream);
2258 NS_ENSURE_TRUE(pStream, E_FAIL);
2260 aSTG.tymed = TYMED_ISTREAM;
2261 aSTG.pstm = pStream;
2262 aSTG.pUnkForRelease = nullptr;
2264 return S_OK;