Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / windows / nsDataObj.cpp
blob88a2a2ad09352c29696292eb06e0deb775c3fb91
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 "nsIHttpChannel.h"
19 #include "nsISupportsPrimitives.h"
20 #include "nsITransferable.h"
21 #include "IEnumFE.h"
22 #include "nsPrimitiveHelpers.h"
23 #include "nsString.h"
24 #include "nsCRT.h"
25 #include "nsPrintfCString.h"
26 #include "nsIStringBundle.h"
27 #include "nsEscape.h"
28 #include "nsIURL.h"
29 #include "nsNetUtil.h"
30 #include "mozilla/Components.h"
31 #include "mozilla/SpinEventLoopUntil.h"
32 #include "mozilla/Unused.h"
33 #include "nsProxyRelease.h"
34 #include "nsIObserverService.h"
35 #include "nsIOutputStream.h"
36 #include "nscore.h"
37 #include "nsDirectoryServiceDefs.h"
38 #include "nsITimer.h"
39 #include "nsThreadUtils.h"
40 #include "mozilla/Preferences.h"
41 #include "nsContentUtils.h"
42 #include "nsIPrincipal.h"
43 #include "nsNativeCharsetUtils.h"
44 #include "nsMimeTypes.h"
45 #include "nsIMIMEService.h"
46 #include "imgIEncoder.h"
47 #include "imgITools.h"
48 #include "WinUtils.h"
49 #include "nsLocalFile.h"
51 #include "mozilla/LazyIdleThread.h"
52 #include <algorithm>
54 using namespace mozilla;
55 using namespace mozilla::glue;
56 using namespace mozilla::widget;
58 #define BFH_LENGTH 14
59 #define DEFAULT_THREAD_TIMEOUT_MS 30000
61 //-----------------------------------------------------------------------------
62 // CStreamBase implementation
63 nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {}
65 //-----------------------------------------------------------------------------
66 nsDataObj::CStreamBase::~CStreamBase() {}
68 NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
70 //-----------------------------------------------------------------------------
71 // CStream implementation
72 nsDataObj::CStream::CStream() : mChannelRead(false) {}
74 //-----------------------------------------------------------------------------
75 nsDataObj::CStream::~CStream() {}
77 //-----------------------------------------------------------------------------
78 // helper - initializes the stream
79 nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
80 nsContentPolicyType aContentPolicyType,
81 nsIPrincipal* aRequestingPrincipal,
82 nsICookieJarSettings* aCookieJarSettings,
83 nsIReferrerInfo* aReferrerInfo) {
84 // we can not create a channel without a requestingPrincipal
85 if (!aRequestingPrincipal) {
86 return NS_ERROR_FAILURE;
88 nsresult rv;
89 rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
90 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
91 aContentPolicyType, aCookieJarSettings,
92 nullptr, // PerformanceStorage
93 nullptr, // loadGroup
94 nullptr, // aCallbacks
95 nsIRequest::LOAD_FROM_CACHE);
96 NS_ENSURE_SUCCESS(rv, rv);
98 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
99 rv = httpChannel->SetReferrerInfo(aReferrerInfo);
100 Unused << NS_WARN_IF(NS_FAILED(rv));
103 rv = mChannel->AsyncOpen(this);
104 NS_ENSURE_SUCCESS(rv, rv);
105 return NS_OK;
108 //-----------------------------------------------------------------------------
109 // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
110 // IUnknown and nsIStreamListener.
111 STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
112 void** ppvResult) {
113 *ppvResult = nullptr;
114 if (IID_IUnknown == refiid || refiid == IID_IStream)
117 *ppvResult = this;
120 if (nullptr != *ppvResult) {
121 ((LPUNKNOWN)*ppvResult)->AddRef();
122 return S_OK;
125 return E_NOINTERFACE;
128 // nsIStreamListener implementation
129 NS_IMETHODIMP
130 nsDataObj::CStream::OnDataAvailable(
131 nsIRequest* aRequest, nsIInputStream* aInputStream,
132 uint64_t aOffset, // offset within the stream
133 uint32_t aCount) // bytes available on this call
135 // If we've been asked to read zero bytes, call `Read` once, just to ensure
136 // any side-effects take place, and return immediately.
137 if (aCount == 0) {
138 char buffer[1] = {0};
139 uint32_t bytesReadByCall = 0;
140 nsresult rv = aInputStream->Read(buffer, 0, &bytesReadByCall);
141 MOZ_ASSERT(bytesReadByCall == 0);
142 return rv;
145 // Extend the write buffer for the incoming data.
146 size_t oldLength = mChannelData.Length();
147 char* buffer =
148 reinterpret_cast<char*>(mChannelData.AppendElements(aCount, fallible));
149 if (!buffer) {
150 return NS_ERROR_OUT_OF_MEMORY;
152 MOZ_ASSERT(mChannelData.Length() == (aOffset + aCount),
153 "stream length mismatch w/write buffer");
155 // Read() may not return aCount on a single call, so loop until we've
156 // accumulated all the data OnDataAvailable has promised.
157 uint32_t bytesRead = 0;
158 while (bytesRead < aCount) {
159 uint32_t bytesReadByCall = 0;
160 nsresult rv = aInputStream->Read(buffer + bytesRead, aCount - bytesRead,
161 &bytesReadByCall);
162 bytesRead += bytesReadByCall;
164 if (bytesReadByCall == 0) {
165 // A `bytesReadByCall` of zero indicates EOF without failure... but we
166 // were promised `aCount` elements and haven't gotten them. Return a
167 // generic failure.
168 rv = NS_ERROR_FAILURE;
171 if (NS_FAILED(rv)) {
172 // Drop any trailing uninitialized elements before erroring out.
173 mChannelData.RemoveElementsAt(oldLength + bytesRead, aCount - bytesRead);
174 return rv;
177 return NS_OK;
180 NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest) {
181 mChannelResult = NS_OK;
182 return NS_OK;
185 NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
186 nsresult aStatusCode) {
187 mChannelRead = true;
188 mChannelResult = aStatusCode;
189 return NS_OK;
192 // Pumps thread messages while waiting for the async listener operation to
193 // complete. Failing this call will fail the stream incall from Windows
194 // and cancel the operation.
195 nsresult nsDataObj::CStream::WaitForCompletion() {
196 // We are guaranteed OnStopRequest will get called, so this should be ok.
197 SpinEventLoopUntil("widget:nsDataObj::CStream::WaitForCompletion"_ns,
198 [&]() { return mChannelRead; });
200 if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
202 return mChannelResult;
205 //-----------------------------------------------------------------------------
206 // IStream
207 STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) {
208 return E_NOTIMPL;
211 //-----------------------------------------------------------------------------
212 STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; }
214 //-----------------------------------------------------------------------------
215 STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream,
216 ULARGE_INTEGER nBytesToCopy,
217 ULARGE_INTEGER* nBytesRead,
218 ULARGE_INTEGER* nBytesWritten) {
219 return E_NOTIMPL;
222 //-----------------------------------------------------------------------------
223 STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart,
224 ULARGE_INTEGER nBytes,
225 DWORD dwFlags) {
226 return E_NOTIMPL;
229 //-----------------------------------------------------------------------------
230 STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
231 ULONG* nBytesRead) {
232 // Wait for the write into our buffer to complete via the stream listener.
233 // We can't respond to this by saying "call us back later".
234 if (NS_FAILED(WaitForCompletion())) return E_FAIL;
236 // Bytes left for Windows to read out of our buffer
237 ULONG bytesLeft = mChannelData.Length() - mStreamRead;
238 // Let Windows know what we will hand back, usually this is the entire buffer
239 *nBytesRead = std::min(bytesLeft, nBytesToRead);
240 // Copy the buffer data over
241 memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
242 // Update our bytes read tracking
243 mStreamRead += *nBytesRead;
244 return S_OK;
247 //-----------------------------------------------------------------------------
248 STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; }
250 //-----------------------------------------------------------------------------
251 STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
252 ULARGE_INTEGER* nNewPos) {
253 if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
255 if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
256 (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
257 nNewPos->LowPart = 0;
258 nNewPos->HighPart = 0;
259 return S_OK;
262 return E_NOTIMPL;
265 //-----------------------------------------------------------------------------
266 STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) {
267 return E_NOTIMPL;
270 //-----------------------------------------------------------------------------
271 STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
272 if (statstg == nullptr) return STG_E_INVALIDPOINTER;
274 if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
276 memset((void*)statstg, 0, sizeof(STATSTG));
278 if (dwFlags != STATFLAG_NONAME) {
279 nsCOMPtr<nsIURI> sourceURI;
280 if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
281 return E_FAIL;
284 nsAutoCString strFileName;
285 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
286 sourceURL->GetFileName(strFileName);
288 if (strFileName.IsEmpty()) return E_FAIL;
290 NS_UnescapeURL(strFileName);
291 NS_ConvertUTF8toUTF16 wideFileName(strFileName);
293 uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
294 void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller
295 if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
297 ZeroMemory(retBuf, nMaxNameLength);
298 memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
299 statstg->pwcsName = (LPOLESTR)retBuf;
302 SYSTEMTIME st;
304 statstg->type = STGTY_STREAM;
306 GetSystemTime(&st);
307 SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
308 statstg->ctime = statstg->atime = statstg->mtime;
310 statstg->cbSize.QuadPart = mChannelData.Length();
311 statstg->grfMode = STGM_READ;
312 statstg->grfLocksSupported = LOCK_ONLYONCE;
313 statstg->clsid = CLSID_NULL;
315 return S_OK;
318 //-----------------------------------------------------------------------------
319 STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart,
320 ULARGE_INTEGER nBytes,
321 DWORD dwFlags) {
322 return E_NOTIMPL;
325 //-----------------------------------------------------------------------------
326 STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer,
327 ULONG nBytesToRead,
328 ULONG* nBytesRead) {
329 return E_NOTIMPL;
332 //-----------------------------------------------------------------------------
333 HRESULT nsDataObj::CreateStream(IStream** outStream) {
334 NS_ENSURE_TRUE(outStream, E_INVALIDARG);
336 nsresult rv = NS_ERROR_FAILURE;
337 nsAutoString wideFileName;
338 nsCOMPtr<nsIURI> sourceURI;
339 HRESULT res;
341 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
342 if (FAILED(res)) return res;
344 nsDataObj::CStream* pStream = new nsDataObj::CStream();
345 NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
347 pStream->AddRef();
349 // query the requestingPrincipal from the transferable and add it to the new
350 // channel
351 nsCOMPtr<nsIPrincipal> requestingPrincipal =
352 mTransferable->GetRequestingPrincipal();
353 MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
355 // Note that the cookieJarSettings could be null if the data object is for the
356 // image copy. We will fix this in Bug 1690532.
357 nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
358 mTransferable->GetCookieJarSettings();
360 // The referrer is optional.
361 nsCOMPtr<nsIReferrerInfo> referrerInfo = mTransferable->GetReferrerInfo();
363 nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType();
364 rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal,
365 cookieJarSettings, referrerInfo);
366 if (NS_FAILED(rv)) {
367 pStream->Release();
368 return E_FAIL;
370 *outStream = pStream;
372 return S_OK;
375 //-----------------------------------------------------------------------------
376 // AutoCloseEvent implementation
377 nsDataObj::AutoCloseEvent::AutoCloseEvent()
378 : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}
380 bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; }
382 void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); }
384 DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const {
385 return ::WaitForSingleObject(mEvent, aMillisec);
388 //-----------------------------------------------------------------------------
389 // AutoSetEvent implementation
390 nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent)
391 : mEvent(aEvent) {}
393 nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); }
395 void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); }
397 bool nsDataObj::AutoSetEvent::IsWaiting() const {
398 return mEvent->Wait(0) == WAIT_TIMEOUT;
401 //-----------------------------------------------------------------------------
402 // CMemStream implementation
403 Win32SRWLock nsDataObj::CMemStream::mLock;
405 //-----------------------------------------------------------------------------
406 nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength,
407 already_AddRefed<AutoCloseEvent> aEvent)
408 : mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) {
409 ::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler));
412 //-----------------------------------------------------------------------------
413 nsDataObj::CMemStream::~CMemStream() {}
415 //-----------------------------------------------------------------------------
416 // IUnknown
417 STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid,
418 void** ppvResult) {
419 *ppvResult = nullptr;
420 if (refiid == IID_IUnknown || refiid == IID_IStream ||
421 refiid == IID_IAgileObject) {
422 *ppvResult = this;
423 } else if (refiid == IID_IMarshal && mMarshaler) {
424 return mMarshaler->QueryInterface(refiid, ppvResult);
427 if (nullptr != *ppvResult) {
428 ((LPUNKNOWN)*ppvResult)->AddRef();
429 return S_OK;
432 return E_NOINTERFACE;
435 void nsDataObj::CMemStream::WaitForCompletion() {
436 if (!mEvent) {
437 // We are not waiting for obtaining the icon cache.
438 return;
440 if (!NS_IsMainThread()) {
441 mEvent->Wait(INFINITE);
442 } else {
443 // We should not block the main thread.
444 mEvent->Signal();
446 // mEvent will always be in the signaled state here.
449 //-----------------------------------------------------------------------------
450 // IStream
451 STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead,
452 ULONG* nBytesRead) {
453 // Wait until the event is signaled.
454 WaitForCompletion();
456 AutoExclusiveLock lock(mLock);
457 char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get()));
458 if (!contents) {
459 return E_OUTOFMEMORY;
462 // Bytes left for Windows to read out of our buffer
463 ULONG bytesLeft = mTotalLength - mStreamRead;
464 // Let Windows know what we will hand back, usually this is the entire buffer
465 *nBytesRead = std::min(bytesLeft, nBytesToRead);
466 // Copy the buffer data over
467 memcpy(pvBuffer, contents + mStreamRead, *nBytesRead);
468 // Update our bytes read tracking
469 mStreamRead += *nBytesRead;
471 GlobalUnlock(mGlobalMem.get());
472 return S_OK;
475 //-----------------------------------------------------------------------------
476 STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) {
477 if (statstg == nullptr) return STG_E_INVALIDPOINTER;
479 memset((void*)statstg, 0, sizeof(STATSTG));
481 if (dwFlags != STATFLAG_NONAME) {
482 constexpr size_t kMaxNameLength = sizeof(wchar_t);
483 void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller
484 if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
486 ZeroMemory(retBuf, kMaxNameLength);
487 statstg->pwcsName = (LPOLESTR)retBuf;
490 SYSTEMTIME st;
492 statstg->type = STGTY_STREAM;
494 GetSystemTime(&st);
495 SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
496 statstg->ctime = statstg->atime = statstg->mtime;
498 statstg->cbSize.QuadPart = mTotalLength;
499 statstg->grfMode = STGM_READ;
500 statstg->grfLocksSupported = LOCK_ONLYONCE;
501 statstg->clsid = CLSID_NULL;
503 return S_OK;
507 * Class nsDataObj
510 //-----------------------------------------------------
511 // construction
512 //-----------------------------------------------------
513 nsDataObj::nsDataObj(nsIURI* uri)
514 : m_cRef(0),
515 mTransferable(nullptr),
516 mIsAsyncMode(FALSE),
517 mIsInOperation(FALSE) {
518 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj",
519 LazyIdleThread::ManualShutdown);
520 m_enumFE = new CEnumFormatEtc();
521 m_enumFE->AddRef();
523 if (uri) {
524 // A URI was obtained, so pass this through to the DataObject
525 // so it can create a SourceURL for CF_HTML flavour
526 uri->GetSpec(mSourceURL);
529 //-----------------------------------------------------
530 // destruction
531 //-----------------------------------------------------
532 nsDataObj::~nsDataObj() {
533 NS_IF_RELEASE(mTransferable);
535 mDataFlavors.Clear();
537 m_enumFE->Release();
539 // Free arbitrary system formats
540 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
541 CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
542 ReleaseStgMedium(&mDataEntryList[idx]->stgm);
543 CoTaskMemFree(mDataEntryList[idx]);
547 //-----------------------------------------------------
548 // IUnknown interface methods - see inknown.h for documentation
549 //-----------------------------------------------------
550 STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
551 *ppv = nullptr;
553 if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
554 *ppv = this;
555 AddRef();
556 return S_OK;
557 } else if (IID_IDataObjectAsyncCapability == riid) {
558 *ppv = static_cast<IDataObjectAsyncCapability*>(this);
559 AddRef();
560 return S_OK;
563 return E_NOINTERFACE;
566 //-----------------------------------------------------
567 STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
568 ++m_cRef;
569 NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
571 // When the first reference is taken, hold our own internal reference.
572 if (m_cRef == 1) {
573 mKeepAlive = this;
576 return m_cRef;
579 namespace {
580 class RemoveTempFileHelper final : public nsIObserver, public nsINamed {
581 public:
582 explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
583 MOZ_ASSERT(mTempFile);
586 // The attach method is seperate from the constructor as we may be addref-ing
587 // ourself, and we want to be sure someone has a strong reference to us.
588 void Attach() {
589 // We need to listen to both the xpcom shutdown message and our timer, and
590 // fire when the first of either of these two messages is received.
591 nsresult rv;
592 rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
593 nsITimer::TYPE_ONE_SHOT);
594 if (NS_WARN_IF(NS_FAILED(rv))) {
595 return;
598 nsCOMPtr<nsIObserverService> observerService =
599 do_GetService("@mozilla.org/observer-service;1");
600 if (NS_WARN_IF(!observerService)) {
601 mTimer->Cancel();
602 mTimer = nullptr;
603 return;
605 observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
608 NS_DECL_ISUPPORTS
609 NS_DECL_NSIOBSERVER
610 NS_DECL_NSINAMED
612 private:
613 ~RemoveTempFileHelper() {
614 if (mTempFile) {
615 mTempFile->Remove(false);
619 nsCOMPtr<nsIFile> mTempFile;
620 nsCOMPtr<nsITimer> mTimer;
623 NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver, nsINamed);
625 NS_IMETHODIMP
626 RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
627 const char16_t* aData) {
628 // Let's be careful and make sure that we don't die immediately
629 RefPtr<RemoveTempFileHelper> grip = this;
631 // Make sure that we aren't called again by destroying references to ourself.
632 nsCOMPtr<nsIObserverService> observerService =
633 do_GetService("@mozilla.org/observer-service;1");
634 if (observerService) {
635 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
638 if (mTimer) {
639 mTimer->Cancel();
640 mTimer = nullptr;
643 // Remove the tempfile
644 if (mTempFile) {
645 mTempFile->Remove(false);
646 mTempFile = nullptr;
648 return NS_OK;
651 NS_IMETHODIMP
652 RemoveTempFileHelper::GetName(nsACString& aName) {
653 aName.AssignLiteral("RemoveTempFileHelper");
654 return NS_OK;
656 } // namespace
658 //-----------------------------------------------------
659 STDMETHODIMP_(ULONG) nsDataObj::Release() {
660 --m_cRef;
662 NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
664 // If we hold the last reference, submit release of it to the main thread.
665 if (m_cRef == 1 && mKeepAlive) {
666 NS_ReleaseOnMainThread("nsDataObj release", mKeepAlive.forget(), true);
669 if (0 != m_cRef) return m_cRef;
671 // We have released our last ref on this object and need to delete the
672 // temp file. External app acting as drop target may still need to open the
673 // temp file. Addref a timer so it can delay deleting file and destroying
674 // this object.
675 if (mCachedTempFile) {
676 RefPtr<RemoveTempFileHelper> helper =
677 new RemoveTempFileHelper(mCachedTempFile);
678 mCachedTempFile = nullptr;
679 helper->Attach();
682 // In case the destructor ever AddRef/Releases, ensure we don't delete twice
683 // or take mKeepAlive as another reference.
684 m_cRef = 1;
686 delete this;
688 return 0;
691 //-----------------------------------------------------
692 BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
693 const FORMATETC& target) const {
694 if ((source.cfFormat == target.cfFormat) &&
695 (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
696 return TRUE;
697 } else {
698 return FALSE;
702 //-----------------------------------------------------
703 // IDataObject methods
704 //-----------------------------------------------------
705 STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
706 if (!mTransferable) return DV_E_FORMATETC;
708 // Hold an extra reference in case we end up spinning the event loop.
709 RefPtr<nsDataObj> keepAliveDuringGetData(this);
711 uint32_t dfInx = 0;
713 static CLIPFORMAT fileDescriptorFlavorA =
714 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
715 static CLIPFORMAT fileDescriptorFlavorW =
716 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
717 static CLIPFORMAT uniformResourceLocatorA =
718 ::RegisterClipboardFormat(CFSTR_INETURLA);
719 static CLIPFORMAT uniformResourceLocatorW =
720 ::RegisterClipboardFormat(CFSTR_INETURLW);
721 static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
722 static CLIPFORMAT PreferredDropEffect =
723 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
725 // Arbitrary system formats are used for image feedback during drag
726 // and drop. We are responsible for storing these internally during
727 // drag operations.
728 LPDATAENTRY pde;
729 if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
730 return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
731 : E_UNEXPECTED;
734 // Firefox internal formats
735 ULONG count;
736 FORMATETC fe;
737 m_enumFE->Reset();
738 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
739 dfInx < mDataFlavors.Length()) {
740 nsCString& df = mDataFlavors.ElementAt(dfInx);
741 if (FormatsMatch(fe, *aFormat)) {
742 pSTM->pUnkForRelease =
743 nullptr; // caller is responsible for deleting this data
744 CLIPFORMAT format = aFormat->cfFormat;
745 switch (format) {
746 // Someone is asking for plain or unicode text
747 case CF_TEXT:
748 case CF_UNICODETEXT:
749 return GetText(df, *aFormat, *pSTM);
751 // Some 3rd party apps that receive drag and drop files from the browser
752 // window require support for this.
753 case CF_HDROP:
754 return GetFile(*aFormat, *pSTM);
756 // Someone is asking for an image
757 case CF_DIBV5:
758 case CF_DIB:
759 return GetDib(df, *aFormat, *pSTM);
761 default:
762 if (format == fileDescriptorFlavorA)
763 return GetFileDescriptor(*aFormat, *pSTM, false);
764 if (format == fileDescriptorFlavorW)
765 return GetFileDescriptor(*aFormat, *pSTM, true);
766 if (format == uniformResourceLocatorA)
767 return GetUniformResourceLocator(*aFormat, *pSTM, false);
768 if (format == uniformResourceLocatorW)
769 return GetUniformResourceLocator(*aFormat, *pSTM, true);
770 if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
771 if (format == PreferredDropEffect)
772 return GetPreferredDropEffect(*aFormat, *pSTM);
773 // MOZ_LOG(gWindowsLog, LogLevel::Info,
774 // ("***** nsDataObj::GetData - Unknown format %u\n", format));
775 return GetText(df, *aFormat, *pSTM);
776 } // switch
777 } // if
778 dfInx++;
779 } // while
781 return DATA_E_FORMATETC;
784 //-----------------------------------------------------
785 STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
786 return E_FAIL;
789 //-----------------------------------------------------
790 // Other objects querying to see if we support a
791 // particular format
792 //-----------------------------------------------------
793 STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
794 // Arbitrary system formats are used for image feedback during drag
795 // and drop. We are responsible for storing these internally during
796 // drag operations.
797 LPDATAENTRY pde;
798 if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
800 // Firefox internal formats
801 ULONG count;
802 FORMATETC fe;
803 m_enumFE->Reset();
804 while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
805 if (fe.cfFormat == pFE->cfFormat) {
806 return S_OK;
809 return E_FAIL;
812 //-----------------------------------------------------
813 STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
814 LPFORMATETC pFEOut) {
815 return E_NOTIMPL;
818 //-----------------------------------------------------
819 STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
820 BOOL shouldRel) {
821 // Arbitrary system formats are used for image feedback during drag
822 // and drop. We are responsible for storing these internally during
823 // drag operations.
824 LPDATAENTRY pde;
825 if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
826 // Release the old data the lookup handed us for this format. This
827 // may have been set in CopyMediumData when we originally stored the
828 // data.
829 if (pde->stgm.tymed) {
830 ReleaseStgMedium(&pde->stgm);
831 memset(&pde->stgm, 0, sizeof(STGMEDIUM));
834 bool result = true;
835 if (shouldRel) {
836 // If shouldRel is TRUE, the data object called owns the storage medium
837 // after the call returns. Store the incoming data in our data array for
838 // release when we are destroyed. This is the common case with arbitrary
839 // data from explorer.
840 pde->stgm = *aMedium;
841 } else {
842 // Copy the incoming data into our data array. (AFAICT, this never gets
843 // called with arbitrary formats for drag images.)
844 result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
846 pde->fe.tymed = pde->stgm.tymed;
848 return result ? S_OK : DV_E_TYMED;
851 if (shouldRel) ReleaseStgMedium(aMedium);
853 return S_OK;
856 bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
857 LPDATAENTRY* aDataEntry,
858 BOOL aAddorUpdate) {
859 *aDataEntry = nullptr;
861 if (aFormat->ptd != nullptr) return false;
863 // See if it's already in our list. If so return the data entry.
864 for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
865 if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
866 mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
867 mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
868 if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
869 // If the caller requests we update, or if the
870 // medium type matches, return the entry.
871 *aDataEntry = mDataEntryList[idx];
872 return true;
873 } else {
874 // Medium does not match, not found.
875 return false;
880 if (!aAddorUpdate) return false;
882 // Add another entry to mDataEntryList
883 LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
884 if (!dataEntry) return false;
886 dataEntry->fe = *aFormat;
887 *aDataEntry = dataEntry;
888 memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
890 // Add this to our IEnumFORMATETC impl. so we can return it when
891 // it's requested.
892 m_enumFE->AddFormatEtc(aFormat);
894 // Store a copy internally in the arbitrary formats array.
895 mDataEntryList.AppendElement(dataEntry);
897 return true;
900 bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
901 LPFORMATETC aFormat, BOOL aSetData) {
902 STGMEDIUM stgmOut = *aMediumSrc;
904 switch (stgmOut.tymed) {
905 case TYMED_ISTREAM:
906 stgmOut.pstm->AddRef();
907 break;
908 case TYMED_ISTORAGE:
909 stgmOut.pstg->AddRef();
910 break;
911 case TYMED_HGLOBAL:
912 if (!aMediumSrc->pUnkForRelease) {
913 if (aSetData) {
914 if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
915 stgmOut.hGlobal =
916 OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
917 if (!stgmOut.hGlobal) return false;
918 } else {
919 // We are returning this data from LookupArbitraryFormat, indicate to
920 // the shell we hold it and will free it.
921 stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
924 break;
925 default:
926 return false;
929 if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
931 *aMediumDst = stgmOut;
933 return true;
936 //-----------------------------------------------------
937 STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
938 switch (dwDir) {
939 case DATADIR_GET:
940 m_enumFE->Clone(ppEnum);
941 break;
942 case DATADIR_SET:
943 // fall through
944 default:
945 *ppEnum = nullptr;
946 } // switch
948 if (nullptr == *ppEnum) return E_FAIL;
950 (*ppEnum)->Reset();
951 // Clone already AddRefed the result so don't addref it again.
952 return NOERROR;
955 //-----------------------------------------------------
956 STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
957 LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
958 return OLE_E_ADVISENOTSUPPORTED;
961 //-----------------------------------------------------
962 STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
963 return OLE_E_ADVISENOTSUPPORTED;
966 //-----------------------------------------------------
967 STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
968 return OLE_E_ADVISENOTSUPPORTED;
971 // IDataObjectAsyncCapability methods
972 STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
973 DWORD dwEffects) {
974 mIsInOperation = FALSE;
975 return S_OK;
978 STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
979 *pfIsOpAsync = mIsAsyncMode;
981 return S_OK;
984 STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
985 *pfInAsyncOp = mIsInOperation;
987 return S_OK;
990 STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
991 mIsAsyncMode = fDoOpAsync;
992 return S_OK;
995 STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
996 mIsInOperation = TRUE;
997 return S_OK;
1001 // GetDIB
1003 // Someone is asking for a bitmap. The data in the transferable will be a
1004 // straight imgIContainer, so just QI it.
1006 HRESULT
1007 nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
1008 STGMEDIUM& aSTG) {
1009 nsCOMPtr<nsISupports> genericDataWrapper;
1010 if (NS_FAILED(
1011 mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
1012 getter_AddRefs(genericDataWrapper)))) {
1013 return E_FAIL;
1016 nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
1017 if (!image) {
1018 return E_FAIL;
1021 nsCOMPtr<imgITools> imgTools =
1022 do_CreateInstance("@mozilla.org/image/tools;1");
1024 nsAutoString options(u"bpp=32;"_ns);
1025 if (aFormat.cfFormat == CF_DIBV5) {
1026 options.AppendLiteral("version=5");
1027 } else {
1028 options.AppendLiteral("version=3");
1031 nsCOMPtr<nsIInputStream> inputStream;
1032 nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
1033 options, getter_AddRefs(inputStream));
1034 if (NS_FAILED(rv) || !inputStream) {
1035 return E_FAIL;
1038 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1039 if (!encoder) {
1040 return E_FAIL;
1043 uint32_t size = 0;
1044 rv = encoder->GetImageBufferUsed(&size);
1045 if (NS_FAILED(rv) || size <= BFH_LENGTH) {
1046 return E_FAIL;
1049 char* src = nullptr;
1050 rv = encoder->GetImageBuffer(&src);
1051 if (NS_FAILED(rv) || !src) {
1052 return E_FAIL;
1055 // We don't want the file header.
1056 src += BFH_LENGTH;
1057 size -= BFH_LENGTH;
1059 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
1060 if (!glob) {
1061 return E_FAIL;
1064 char* dst = (char*)::GlobalLock(glob);
1065 ::CopyMemory(dst, src, size);
1066 ::GlobalUnlock(glob);
1068 aSTG.hGlobal = glob;
1069 aSTG.tymed = TYMED_HGLOBAL;
1070 return S_OK;
1074 // GetFileDescriptor
1077 HRESULT
1078 nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
1079 bool aIsUnicode) {
1080 HRESULT res = S_OK;
1082 // How we handle this depends on if we're dealing with an internet
1083 // shortcut, since those are done under the covers.
1084 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
1085 if (aIsUnicode)
1086 return GetFileDescriptor_IStreamW(aFE, aSTG);
1087 else
1088 return GetFileDescriptor_IStreamA(aFE, aSTG);
1089 } else if (IsFlavourPresent(kURLMime)) {
1090 if (aIsUnicode)
1091 res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
1092 else
1093 res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
1094 } else
1095 NS_WARNING("Not yet implemented\n");
1097 return res;
1098 } // GetFileDescriptor
1101 HRESULT
1102 nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
1103 HRESULT res = S_OK;
1105 // How we handle this depends on if we're dealing with an internet
1106 // shortcut, since those are done under the covers.
1107 if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
1108 return GetFileContents_IStream(aFE, aSTG);
1109 else if (IsFlavourPresent(kURLMime))
1110 return GetFileContentsInternetShortcut(aFE, aSTG);
1111 else
1112 NS_WARNING("Not yet implemented\n");
1114 return res;
1116 } // GetFileContents
1118 // Ensure that the supplied name doesn't have invalid characters.
1119 static void ValidateFilename(nsString& aFilename, bool isShortcut) {
1120 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
1121 if (NS_WARN_IF(!mimeService)) {
1122 aFilename.Truncate();
1123 return;
1126 uint32_t flags = nsIMIMEService::VALIDATE_SANITIZE_ONLY;
1127 if (isShortcut) {
1128 flags |= nsIMIMEService::VALIDATE_ALLOW_INVALID_FILENAMES;
1131 nsAutoString outFilename;
1132 mimeService->ValidateFileNameForSaving(aFilename, EmptyCString(), flags,
1133 outFilename);
1134 aFilename = outFilename;
1138 // Given a unicode string, convert it to a valid local charset filename
1139 // and append the .url extension to be used for a shortcut file.
1140 // This ensures that we do not cut MBCS characters in the middle.
1142 // It would seem that this is more functionality suited to being in nsIFile.
1144 static bool CreateURLFilenameFromTextA(nsAutoString& aText, char* aFilename) {
1145 if (aText.IsEmpty()) {
1146 return false;
1148 aText.AppendLiteral(".url");
1149 ValidateFilename(aText, true);
1150 if (aText.IsEmpty()) {
1151 return false;
1154 // ValidateFilename should already be checking the filename length, but do
1155 // an extra check to verify for the local code page that the converted text
1156 // doesn't go over MAX_PATH and just return false if it does.
1157 char defaultChar = '_';
1158 int currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
1159 aText.get(), -1, aFilename, MAX_PATH,
1160 &defaultChar, nullptr);
1161 return currLen != 0;
1164 // Wide character version of CreateURLFilenameFromTextA
1165 static bool CreateURLFilenameFromTextW(nsAutoString& aText,
1166 wchar_t* aFilename) {
1167 if (aText.IsEmpty()) {
1168 return false;
1170 aText.AppendLiteral(".url");
1171 ValidateFilename(aText, true);
1172 if (aText.IsEmpty() || aText.Length() >= MAX_PATH) {
1173 return false;
1176 wcscpy(&aFilename[0], aText.get());
1177 return true;
1180 #define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
1182 static bool GetLocalizedString(const char* aName, nsAString& aString) {
1183 nsCOMPtr<nsIStringBundleService> stringService =
1184 mozilla::components::StringBundle::Service();
1185 if (!stringService) return false;
1187 nsCOMPtr<nsIStringBundle> stringBundle;
1188 nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
1189 getter_AddRefs(stringBundle));
1190 if (NS_FAILED(rv)) return false;
1192 rv = stringBundle->GetStringFromName(aName, aString);
1193 return NS_SUCCEEDED(rv);
1197 // GetFileDescriptorInternetShortcut
1199 // Create the special format for an internet shortcut and build up the data
1200 // structures the shell is expecting.
1202 HRESULT
1203 nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
1204 STGMEDIUM& aSTG) {
1205 // get the title of the shortcut
1206 nsAutoString title;
1207 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1209 HGLOBAL fileGroupDescHandle =
1210 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
1211 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1213 LPFILEGROUPDESCRIPTORA fileGroupDescA =
1214 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
1215 ::GlobalLock(fileGroupDescHandle));
1216 if (!fileGroupDescA) {
1217 ::GlobalFree(fileGroupDescHandle);
1218 return E_OUTOFMEMORY;
1221 // get a valid filename in the following order: 1) from the page title,
1222 // 2) localized string for an untitled page, 3) just use "Untitled.url"
1223 if (!CreateURLFilenameFromTextA(title, fileGroupDescA->fgd[0].cFileName)) {
1224 nsAutoString untitled;
1225 if (!GetLocalizedString("noPageTitle", untitled) ||
1226 !CreateURLFilenameFromTextA(untitled,
1227 fileGroupDescA->fgd[0].cFileName)) {
1228 strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.url");
1232 // one file in the file block
1233 fileGroupDescA->cItems = 1;
1234 fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
1236 ::GlobalUnlock(fileGroupDescHandle);
1237 aSTG.hGlobal = fileGroupDescHandle;
1238 aSTG.tymed = TYMED_HGLOBAL;
1240 return S_OK;
1241 } // GetFileDescriptorInternetShortcutA
1243 HRESULT
1244 nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
1245 STGMEDIUM& aSTG) {
1246 // get the title of the shortcut
1247 nsAutoString title;
1248 if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
1250 HGLOBAL fileGroupDescHandle =
1251 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
1252 if (!fileGroupDescHandle) return E_OUTOFMEMORY;
1254 LPFILEGROUPDESCRIPTORW fileGroupDescW =
1255 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
1256 ::GlobalLock(fileGroupDescHandle));
1257 if (!fileGroupDescW) {
1258 ::GlobalFree(fileGroupDescHandle);
1259 return E_OUTOFMEMORY;
1262 // get a valid filename in the following order: 1) from the page title,
1263 // 2) localized string for an untitled page, 3) just use "Untitled.url"
1264 if (!CreateURLFilenameFromTextW(title, fileGroupDescW->fgd[0].cFileName)) {
1265 nsAutoString untitled;
1266 if (!GetLocalizedString("noPageTitle", untitled) ||
1267 !CreateURLFilenameFromTextW(untitled,
1268 fileGroupDescW->fgd[0].cFileName)) {
1269 wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.url");
1273 // one file in the file block
1274 fileGroupDescW->cItems = 1;
1275 fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
1277 ::GlobalUnlock(fileGroupDescHandle);
1278 aSTG.hGlobal = fileGroupDescHandle;
1279 aSTG.tymed = TYMED_HGLOBAL;
1281 return S_OK;
1282 } // GetFileDescriptorInternetShortcutW
1285 // GetFileContentsInternetShortcut
1287 // Create the special format for an internet shortcut and build up the data
1288 // structures the shell is expecting.
1290 HRESULT
1291 nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
1292 static const char* kShellIconPref = "browser.shell.shortcutFavicons";
1293 nsAutoString url;
1294 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
1296 nsCOMPtr<nsIURI> aUri;
1297 nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
1298 if (NS_FAILED(rv)) {
1299 return E_FAIL;
1302 nsAutoCString asciiUrl;
1303 rv = aUri->GetAsciiSpec(asciiUrl);
1304 if (NS_FAILED(rv)) {
1305 return E_FAIL;
1308 RefPtr<AutoCloseEvent> event;
1310 const char* shortcutFormatStr;
1311 int totalLen;
1312 nsCString asciiPath;
1313 if (!Preferences::GetBool(kShellIconPref, true)) {
1314 shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
1315 const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
1316 totalLen = formatLen + asciiUrl.Length(); // don't include null character
1317 } else {
1318 nsCOMPtr<nsIFile> icoFile;
1320 nsAutoString aUriHash;
1322 event = new AutoCloseEvent();
1323 if (!event->IsInited()) {
1324 return E_FAIL;
1327 RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event));
1328 mozilla::widget::FaviconHelper::ObtainCachedIconFile(
1329 aUri, aUriHash, mIOThread, true,
1330 NS_NewRunnableFunction(
1331 "FaviconHelper::RefreshDesktop", [e = std::move(e)] {
1332 if (e->IsWaiting()) {
1333 // Unblock IStream:::Read.
1334 e->Signal();
1335 } else {
1336 // We could not wait until the favicon was available. We have
1337 // to refresh to refect the favicon.
1338 SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
1339 SPI_SETNONCLIENTMETRICS, 0);
1341 }));
1343 rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
1344 NS_ENSURE_SUCCESS(rv, E_FAIL);
1345 nsString path;
1346 rv = icoFile->GetPath(path);
1347 NS_ENSURE_SUCCESS(rv, E_FAIL);
1349 if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
1350 LossyCopyUTF16toASCII(path, asciiPath);
1351 shortcutFormatStr =
1352 "[InternetShortcut]\r\nURL=%s\r\n"
1353 "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
1354 "IconIndex=0\r\n";
1355 } else {
1356 int len =
1357 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1358 path.Length(), nullptr, 0, nullptr, nullptr);
1359 NS_ENSURE_TRUE(len > 0, E_FAIL);
1360 asciiPath.SetLength(len);
1361 WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
1362 path.Length(), asciiPath.BeginWriting(), len, nullptr,
1363 nullptr);
1364 shortcutFormatStr =
1365 "[InternetShortcut]\r\nURL=%s\r\n"
1366 "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
1367 "[InternetShortcut.W]\r\nIconFile=%s\r\n";
1369 const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
1370 totalLen = formatLen + asciiUrl.Length() +
1371 asciiPath.Length(); // we don't want a null character on the end
1374 // create a global memory area and build up the file contents w/in it
1375 nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen)));
1376 if (!globalMem) return E_OUTOFMEMORY;
1378 char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get()));
1379 if (!contents) {
1380 return E_OUTOFMEMORY;
1383 // NOTE: we intentionally use the Microsoft version of snprintf here because
1384 // it does NOT null
1385 // terminate strings which reach the maximum size of the buffer. Since we know
1386 // that the formatted length here is totalLen, this call to _snprintf will
1387 // format the string into the buffer without appending the null character.
1389 if (!Preferences::GetBool(kShellIconPref, true)) {
1390 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
1391 } else {
1392 _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
1393 asciiPath.get());
1396 ::GlobalUnlock(globalMem.get());
1398 if (aFE.tymed & TYMED_ISTREAM) {
1399 if (!mIsInOperation) {
1400 // The drop target didn't initiate an async operation.
1401 // We can't block CMemStream::Read.
1402 event = nullptr;
1404 RefPtr<IStream> stream =
1405 new CMemStream(globalMem.disown(), totalLen, event.forget());
1406 stream.forget(&aSTG.pstm);
1407 aSTG.tymed = TYMED_ISTREAM;
1408 } else {
1409 if (event && event->IsInited()) {
1410 event->Signal(); // We can't block reading the global memory
1412 aSTG.hGlobal = globalMem.disown();
1413 aSTG.tymed = TYMED_HGLOBAL;
1416 return S_OK;
1417 } // GetFileContentsInternetShortcut
1419 // check if specified flavour is present in the transferable
1420 bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
1421 bool retval = false;
1422 NS_ENSURE_TRUE(mTransferable, false);
1424 // get the list of flavors available in the transferable
1425 nsTArray<nsCString> flavors;
1426 nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
1427 NS_ENSURE_SUCCESS(rv, false);
1429 // try to find requested flavour
1430 for (uint32_t i = 0; i < flavors.Length(); ++i) {
1431 if (flavors[i].Equals(inFlavour)) {
1432 retval = true; // found it!
1433 break;
1435 } // for each flavor
1437 return retval;
1440 HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
1441 HRESULT res = S_OK;
1442 aSTG.tymed = TYMED_HGLOBAL;
1443 aSTG.pUnkForRelease = nullptr;
1444 HGLOBAL hGlobalMemory = nullptr;
1445 hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
1446 if (hGlobalMemory) {
1447 DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
1448 // The PreferredDropEffect clipboard format is only registered if a
1449 // drag/drop of an image happens from Mozilla to the desktop. We want its
1450 // value to be DROPEFFECT_MOVE in that case so that the file is moved from
1451 // the temporary location, not copied. This value should, ideally, be set on
1452 // the data object via SetData() but our IDataObject implementation doesn't
1453 // implement SetData. It adds data to the data object lazily only when the
1454 // drop target asks for it.
1455 *pdw = (DWORD)DROPEFFECT_MOVE;
1456 GlobalUnlock(hGlobalMemory);
1457 } else {
1458 res = E_OUTOFMEMORY;
1460 aSTG.hGlobal = hGlobalMemory;
1461 return res;
1464 //-----------------------------------------------------
1465 HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
1466 STGMEDIUM& aSTG) {
1467 void* data = nullptr;
1469 const nsPromiseFlatCString& flavorStr = PromiseFlatCString(aDataFlavor);
1471 // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
1472 nsCOMPtr<nsISupports> genericDataWrapper;
1473 nsresult rv = mTransferable->GetTransferData(
1474 flavorStr.get(), getter_AddRefs(genericDataWrapper));
1475 if (NS_FAILED(rv) || !genericDataWrapper) {
1476 return E_FAIL;
1479 uint32_t len;
1480 nsPrimitiveHelpers::CreateDataFromPrimitive(
1481 nsDependentCString(flavorStr.get()), genericDataWrapper, &data, &len);
1482 if (!data) return E_FAIL;
1484 HGLOBAL hGlobalMemory = nullptr;
1486 aSTG.tymed = TYMED_HGLOBAL;
1487 aSTG.pUnkForRelease = nullptr;
1489 // We play games under the hood and advertise flavors that we know we
1490 // can support, only they require a bit of conversion or munging of the data.
1491 // Do that here.
1493 // The transferable gives us data that is null-terminated, but this isn't
1494 // reflected in the |len| parameter. Windoze apps expect this null to be there
1495 // so bump our data buffer by the appropriate size to account for the null
1496 // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
1497 DWORD allocLen = (DWORD)len;
1498 if (aFE.cfFormat == CF_TEXT) {
1499 // Someone is asking for text/plain; convert the unicode (assuming it's
1500 // present) to text with the correct platform encoding.
1501 size_t bufferSize = sizeof(char) * (len + 2);
1502 char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
1503 char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
1504 int32_t plainTextLen =
1505 WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
1506 plainTextData, bufferSize, NULL, NULL);
1507 // replace the unicode data with our plaintext data. Recall that
1508 // |plainTextLen| doesn't include the null in the length.
1509 free(data);
1510 if (plainTextLen) {
1511 data = plainTextData;
1512 allocLen = plainTextLen;
1513 } else {
1514 free(plainTextData);
1515 NS_WARNING("Oh no, couldn't convert unicode to plain text");
1516 return S_OK;
1518 } else if (aFE.cfFormat == nsClipboard::GetHtmlClipboardFormat()) {
1519 // Someone is asking for win32's HTML flavor. Convert our html fragment
1520 // from unicode to UTF-8 then put it into a format specified by msft.
1521 NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
1522 char* utf8HTML = nullptr;
1523 nsresult rv =
1524 BuildPlatformHTML(converter.get(), &utf8HTML); // null terminates
1526 free(data);
1527 if (NS_SUCCEEDED(rv) && utf8HTML) {
1528 // replace the unicode data with our HTML data. Don't forget the null.
1529 data = utf8HTML;
1530 allocLen = strlen(utf8HTML) + sizeof(char);
1531 } else {
1532 NS_WARNING("Oh no, couldn't convert to HTML");
1533 return S_OK;
1535 } else if (aFE.cfFormat != nsClipboard::GetCustomClipboardFormat()) {
1536 // we assume that any data that isn't caught above is unicode. This may
1537 // be an erroneous assumption, but is true so far.
1538 allocLen += sizeof(char16_t);
1541 hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
1543 // Copy text to Global Memory Area
1544 if (hGlobalMemory) {
1545 char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
1546 char* source = reinterpret_cast<char*>(data);
1547 memcpy(dest, source, allocLen); // copies the null as well
1548 GlobalUnlock(hGlobalMemory);
1550 aSTG.hGlobal = hGlobalMemory;
1552 // Now, delete the memory that was created by CreateDataFromPrimitive (or our
1553 // text/plain data)
1554 free(data);
1556 return S_OK;
1559 //-----------------------------------------------------
1560 HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1561 uint32_t dfInx = 0;
1562 ULONG count;
1563 FORMATETC fe;
1564 m_enumFE->Reset();
1565 while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
1566 dfInx < mDataFlavors.Length()) {
1567 if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
1568 return DropImage(aFE, aSTG);
1569 if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
1570 return DropFile(aFE, aSTG);
1571 if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
1572 return DropTempFile(aFE, aSTG);
1573 dfInx++;
1575 return E_FAIL;
1578 HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1579 nsresult rv;
1580 nsCOMPtr<nsISupports> genericDataWrapper;
1582 if (NS_FAILED(mTransferable->GetTransferData(
1583 kFileMime, getter_AddRefs(genericDataWrapper)))) {
1584 return E_FAIL;
1586 nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
1587 if (!file) return E_FAIL;
1589 aSTG.tymed = TYMED_HGLOBAL;
1590 aSTG.pUnkForRelease = nullptr;
1592 nsAutoString path;
1593 rv = file->GetPath(path);
1594 if (NS_FAILED(rv)) return E_FAIL;
1596 uint32_t allocLen = path.Length() + 2;
1597 HGLOBAL hGlobalMemory = nullptr;
1598 char16_t* dest;
1600 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1601 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1602 if (!hGlobalMemory) return E_FAIL;
1604 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1606 // First, populate the drop file structure
1607 pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string
1608 pDropFile->fNC = 0;
1609 pDropFile->pt.x = 0;
1610 pDropFile->pt.y = 0;
1611 pDropFile->fWide = TRUE;
1613 // Copy the filename right after the DROPFILES structure
1614 dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1615 memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
1617 // Two null characters are needed at the end of the file name.
1618 // Lookup the CF_HDROP shell clipboard format for more info.
1619 // Add the second null character right after the first one.
1620 dest[allocLen - 1] = L'\0';
1622 GlobalUnlock(hGlobalMemory);
1624 aSTG.hGlobal = hGlobalMemory;
1626 return S_OK;
1629 HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
1630 nsresult rv;
1631 if (!mCachedTempFile) {
1632 nsCOMPtr<nsISupports> genericDataWrapper;
1634 if (NS_FAILED(mTransferable->GetTransferData(
1635 kNativeImageMime, getter_AddRefs(genericDataWrapper)))) {
1636 return E_FAIL;
1638 nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
1639 if (!image) return E_FAIL;
1641 nsCOMPtr<imgITools> imgTools =
1642 do_CreateInstance("@mozilla.org/image/tools;1");
1643 nsCOMPtr<nsIInputStream> inputStream;
1644 rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
1645 u"bpp=32;version=3"_ns,
1646 getter_AddRefs(inputStream));
1647 if (NS_FAILED(rv) || !inputStream) {
1648 return E_FAIL;
1651 nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
1652 if (!encoder) {
1653 return E_FAIL;
1656 uint32_t size = 0;
1657 rv = encoder->GetImageBufferUsed(&size);
1658 if (NS_FAILED(rv)) {
1659 return E_FAIL;
1662 char* src = nullptr;
1663 rv = encoder->GetImageBuffer(&src);
1664 if (NS_FAILED(rv) || !src) {
1665 return E_FAIL;
1668 // Save the bitmap to a temporary location.
1669 nsCOMPtr<nsIFile> dropFile;
1670 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1671 if (!dropFile) {
1672 return E_FAIL;
1675 // Filename must be random so as not to confuse apps like
1676 // Photoshop which handle multiple drags into a single window.
1677 char buf[13];
1678 nsCString filename;
1679 NS_MakeRandomString(buf, 8);
1680 memcpy(buf + 8, ".bmp", 5);
1681 filename.Append(nsDependentCString(buf, 12));
1682 dropFile->AppendNative(filename);
1683 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1684 if (NS_FAILED(rv)) {
1685 return E_FAIL;
1688 // Cache the temp file so we can delete it later and so
1689 // it doesn't get recreated over and over on multiple calls
1690 // which does occur from windows shell.
1691 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1693 // Write the data to disk.
1694 nsCOMPtr<nsIOutputStream> outStream;
1695 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1696 if (NS_FAILED(rv)) {
1697 return E_FAIL;
1700 uint32_t written = 0;
1701 rv = outStream->Write(src, size, &written);
1702 if (NS_FAILED(rv) || written != size) {
1703 return E_FAIL;
1706 outStream->Close();
1709 // Pass the file name back to the drop target so that it can access the file.
1710 nsAutoString path;
1711 rv = mCachedTempFile->GetPath(path);
1712 if (NS_FAILED(rv)) return E_FAIL;
1714 // Two null characters are needed to terminate the file name list.
1715 HGLOBAL hGlobalMemory = nullptr;
1717 uint32_t allocLen = path.Length() + 2;
1719 aSTG.tymed = TYMED_HGLOBAL;
1720 aSTG.pUnkForRelease = nullptr;
1722 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1723 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1724 if (!hGlobalMemory) return E_FAIL;
1726 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1728 // First, populate the drop file structure.
1729 pDropFile->pFiles =
1730 sizeof(DROPFILES); // Offset to start of file name char array.
1731 pDropFile->fNC = 0;
1732 pDropFile->pt.x = 0;
1733 pDropFile->pt.y = 0;
1734 pDropFile->fWide = TRUE;
1736 // Copy the filename right after the DROPFILES structure.
1737 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1738 memcpy(dest, path.get(),
1739 (allocLen - 1) *
1740 sizeof(char16_t)); // Copies the null character in path as well.
1742 // Two null characters are needed at the end of the file name.
1743 // Lookup the CF_HDROP shell clipboard format for more info.
1744 // Add the second null character right after the first one.
1745 dest[allocLen - 1] = L'\0';
1747 GlobalUnlock(hGlobalMemory);
1749 aSTG.hGlobal = hGlobalMemory;
1751 return S_OK;
1754 HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
1755 nsresult rv;
1756 if (!mCachedTempFile) {
1757 // Tempfile will need a temporary location.
1758 nsCOMPtr<nsIFile> dropFile;
1759 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
1760 if (!dropFile) return E_FAIL;
1762 // Filename must be random
1763 nsCString filename;
1764 nsAutoString wideFileName;
1765 nsCOMPtr<nsIURI> sourceURI;
1766 HRESULT res;
1767 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
1768 if (FAILED(res)) return res;
1769 NS_CopyUnicodeToNative(wideFileName, filename);
1771 dropFile->AppendNative(filename);
1772 rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
1773 if (NS_FAILED(rv)) return E_FAIL;
1775 // Cache the temp file so we can delete it later and so
1776 // it doesn't get recreated over and over on multiple calls
1777 // which does occur from windows shell.
1778 dropFile->Clone(getter_AddRefs(mCachedTempFile));
1780 // Write the data to disk.
1781 nsCOMPtr<nsIOutputStream> outStream;
1782 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
1783 if (NS_FAILED(rv)) return E_FAIL;
1785 IStream* pStream = nullptr;
1786 nsDataObj::CreateStream(&pStream);
1787 NS_ENSURE_TRUE(pStream, E_FAIL);
1789 char buffer[512];
1790 ULONG readCount = 0;
1791 uint32_t writeCount = 0;
1792 while (1) {
1793 HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
1794 if (FAILED(hres)) return E_FAIL;
1795 if (readCount == 0) break;
1796 rv = outStream->Write(buffer, readCount, &writeCount);
1797 if (NS_FAILED(rv)) return E_FAIL;
1799 outStream->Close();
1800 pStream->Release();
1803 // Pass the file name back to the drop target so that it can access the file.
1804 nsAutoString path;
1805 rv = mCachedTempFile->GetPath(path);
1806 if (NS_FAILED(rv)) return E_FAIL;
1808 uint32_t allocLen = path.Length() + 2;
1810 // Two null characters are needed to terminate the file name list.
1811 HGLOBAL hGlobalMemory = nullptr;
1813 aSTG.tymed = TYMED_HGLOBAL;
1814 aSTG.pUnkForRelease = nullptr;
1816 hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
1817 sizeof(DROPFILES) + allocLen * sizeof(char16_t));
1818 if (!hGlobalMemory) return E_FAIL;
1820 DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
1822 // First, populate the drop file structure.
1823 pDropFile->pFiles =
1824 sizeof(DROPFILES); // Offset to start of file name char array.
1825 pDropFile->fNC = 0;
1826 pDropFile->pt.x = 0;
1827 pDropFile->pt.y = 0;
1828 pDropFile->fWide = TRUE;
1830 // Copy the filename right after the DROPFILES structure.
1831 char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
1832 memcpy(dest, path.get(),
1833 (allocLen - 1) *
1834 sizeof(char16_t)); // Copies the null character in path as well.
1836 // Two null characters are needed at the end of the file name.
1837 // Lookup the CF_HDROP shell clipboard format for more info.
1838 // Add the second null character right after the first one.
1839 dest[allocLen - 1] = L'\0';
1841 GlobalUnlock(hGlobalMemory);
1843 aSTG.hGlobal = hGlobalMemory;
1845 return S_OK;
1848 //-----------------------------------------------------
1849 // Registers the DataFlavor/FE pair.
1850 //-----------------------------------------------------
1851 void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
1852 // These two lists are the mapping to and from data flavors and FEs.
1853 // Later, OLE will tell us it needs a certain type of FORMATETC (text,
1854 // unicode, etc) unicode, etc), so we will look up the data flavor that
1855 // corresponds to the FE and then ask the transferable for that type of data.
1856 mDataFlavors.AppendElement(aDataFlavor);
1857 m_enumFE->AddFormatEtc(aFE);
1860 //-----------------------------------------------------
1861 // Sets the transferable object
1862 //-----------------------------------------------------
1863 void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
1864 NS_IF_RELEASE(mTransferable);
1866 mTransferable = aTransferable;
1867 if (nullptr == mTransferable) {
1868 return;
1871 NS_ADDREF(mTransferable);
1873 return;
1877 // ExtractURL
1879 // Roots around in the transferable for the appropriate flavor that indicates
1880 // a url and pulls out the url portion of the data. Used mostly for creating
1881 // internet shortcuts on the desktop. The url flavor is of the format:
1883 // <url> <linefeed> <page title>
1885 nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
1886 NS_ASSERTION(mTransferable, "We don't have a good transferable");
1887 nsresult rv = NS_ERROR_FAILURE;
1889 nsCOMPtr<nsISupports> genericURL;
1890 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1891 kURLMime, getter_AddRefs(genericURL)))) {
1892 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1893 if (urlObject) {
1894 nsAutoString url;
1895 urlObject->GetData(url);
1896 outURL = url;
1898 // find the first linefeed in the data, that's where the url ends. trunc
1899 // the result string at that point.
1900 int32_t lineIndex = outURL.FindChar('\n');
1901 NS_ASSERTION(lineIndex > 0,
1902 "Format for url flavor is <url> <linefeed> <page title>");
1903 if (lineIndex > 0) {
1904 outURL.Truncate(lineIndex);
1905 rv = NS_OK;
1908 } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
1909 kURLDataMime, getter_AddRefs(genericURL))) ||
1910 NS_SUCCEEDED(mTransferable->GetTransferData(
1911 kURLPrivateMime, getter_AddRefs(genericURL)))) {
1912 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1913 if (urlObject) {
1914 nsAutoString url;
1915 urlObject->GetData(url);
1916 outURL = url;
1918 rv = NS_OK;
1921 } // if found flavor
1923 return rv;
1925 } // ExtractShortcutURL
1928 // ExtractShortcutTitle
1930 // Roots around in the transferable for the appropriate flavor that indicates
1931 // a url and pulls out the title portion of the data. Used mostly for creating
1932 // internet shortcuts on the desktop. The url flavor is of the format:
1934 // <url> <linefeed> <page title>
1936 nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
1937 NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
1938 nsresult rv = NS_ERROR_FAILURE;
1940 nsCOMPtr<nsISupports> genericURL;
1941 if (NS_SUCCEEDED(mTransferable->GetTransferData(
1942 kURLMime, getter_AddRefs(genericURL)))) {
1943 nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
1944 if (urlObject) {
1945 nsAutoString url;
1946 urlObject->GetData(url);
1948 // find the first linefeed in the data, that's where the url ends. we want
1949 // everything after that linefeed. FindChar() returns -1 if we can't find
1950 int32_t lineIndex = url.FindChar('\n');
1951 NS_ASSERTION(lineIndex != -1,
1952 "Format for url flavor is <url> <linefeed> <page title>");
1953 if (lineIndex != -1) {
1954 url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
1955 rv = NS_OK;
1958 } // if found flavor
1960 return rv;
1962 } // ExtractShortcutTitle
1965 // BuildPlatformHTML
1967 // Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
1968 // header information on it. This will null-terminate |outPlatformHTML|. See
1969 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
1970 // for details.
1972 // We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
1973 // or <BODY> tags). We'll wrap the fragment with them to make other apps
1974 // happy.
1976 nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
1977 char** outPlatformHTML) {
1978 *outPlatformHTML = nullptr;
1979 nsDependentCString inHTMLString(inOurHTML);
1981 // Do we already have mSourceURL from a drag?
1982 if (mSourceURL.IsEmpty()) {
1983 nsAutoString url;
1984 ExtractShortcutURL(url);
1986 AppendUTF16toUTF8(url, mSourceURL);
1989 constexpr auto kStartHTMLPrefix = "Version:0.9\r\nStartHTML:"_ns;
1990 constexpr auto kEndHTMLPrefix = "\r\nEndHTML:"_ns;
1991 constexpr auto kStartFragPrefix = "\r\nStartFragment:"_ns;
1992 constexpr auto kEndFragPrefix = "\r\nEndFragment:"_ns;
1993 constexpr auto kStartSourceURLPrefix = "\r\nSourceURL:"_ns;
1994 constexpr auto kEndFragTrailer = "\r\n"_ns;
1996 // The CF_HTML's size is embedded in the fragment, in such a way that the
1997 // number of digits in the size is part of the size itself. While it _is_
1998 // technically possible to compute the necessary size of the size-field
1999 // precisely -- by trial and error, if nothing else -- it's simpler just to
2000 // pick a rough but generous estimate and zero-pad it. (Zero-padding is
2001 // explicitly permitted by the format definition.)
2003 // Originally, in 2001, the "rough but generous estimate" was 8 digits. While
2004 // a maximum size of (10**9 - 1) bytes probably would have covered all
2005 // possible use-cases at the time, it's somewhat more likely to overflow
2006 // nowadays. Nonetheless, for the sake of backwards compatibility with any
2007 // misbehaving consumers of our existing CF_HTML output, we retain exactly
2008 // that padding for (most) fragments where it suffices. (No such misbehaving
2009 // consumers are actually known, so this is arguably paranoia.)
2011 // It is now 2022. A padding size of 16 will cover up to about 8.8 petabytes,
2012 // which should be enough for at least the next few years or so.
2013 const size_t numberLength = inHTMLString.Length() < 9999'0000 ? 8 : 16;
2015 const size_t sourceURLLength = mSourceURL.Length();
2017 const size_t fixedHeaderLen =
2018 kStartHTMLPrefix.Length() + kEndHTMLPrefix.Length() +
2019 kStartFragPrefix.Length() + kEndFragPrefix.Length() +
2020 kEndFragTrailer.Length() + (4 * numberLength);
2022 const size_t totalHeaderLen =
2023 fixedHeaderLen + (sourceURLLength > 0
2024 ? kStartSourceURLPrefix.Length() + sourceURLLength
2025 : 0);
2027 constexpr auto kHeaderString = "<html><body>\r\n<!--StartFragment-->"_ns;
2028 constexpr auto kTrailingString =
2029 "<!--EndFragment-->\r\n"
2030 "</body>\r\n"
2031 "</html>"_ns;
2033 // calculate the offsets
2034 size_t startHTMLOffset = totalHeaderLen;
2035 size_t startFragOffset = startHTMLOffset + kHeaderString.Length();
2037 size_t endFragOffset = startFragOffset + inHTMLString.Length();
2038 size_t endHTMLOffset = endFragOffset + kTrailingString.Length();
2040 // now build the final version
2041 nsCString clipboardString;
2042 clipboardString.SetCapacity(endHTMLOffset);
2044 const int numberLengthInt = static_cast<int>(numberLength);
2045 clipboardString.Append(kStartHTMLPrefix);
2046 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startHTMLOffset);
2048 clipboardString.Append(kEndHTMLPrefix);
2049 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endHTMLOffset);
2051 clipboardString.Append(kStartFragPrefix);
2052 clipboardString.AppendPrintf("%0*zu", numberLengthInt, startFragOffset);
2054 clipboardString.Append(kEndFragPrefix);
2055 clipboardString.AppendPrintf("%0*zu", numberLengthInt, endFragOffset);
2057 if (sourceURLLength > 0) {
2058 clipboardString.Append(kStartSourceURLPrefix);
2059 clipboardString.Append(mSourceURL);
2062 clipboardString.Append(kEndFragTrailer);
2064 // Assert that the positional values were correct as we pass by their
2065 // corresponding positions.
2066 MOZ_ASSERT(clipboardString.Length() == startHTMLOffset);
2067 clipboardString.Append(kHeaderString);
2068 MOZ_ASSERT(clipboardString.Length() == startFragOffset);
2069 clipboardString.Append(inHTMLString);
2070 MOZ_ASSERT(clipboardString.Length() == endFragOffset);
2071 clipboardString.Append(kTrailingString);
2072 MOZ_ASSERT(clipboardString.Length() == endHTMLOffset);
2074 *outPlatformHTML = ToNewCString(clipboardString, mozilla::fallible);
2075 if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
2077 return NS_OK;
2080 HRESULT
2081 nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
2082 bool aIsUnicode) {
2083 HRESULT res = S_OK;
2084 if (IsFlavourPresent(kURLMime)) {
2085 if (aIsUnicode)
2086 res = ExtractUniformResourceLocatorW(aFE, aSTG);
2087 else
2088 res = ExtractUniformResourceLocatorA(aFE, aSTG);
2089 } else
2090 NS_WARNING("Not yet implemented\n");
2091 return res;
2094 HRESULT
2095 nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2096 HRESULT result = S_OK;
2098 nsAutoString url;
2099 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2101 NS_LossyConvertUTF16toASCII asciiUrl(url);
2102 const int totalLen = asciiUrl.Length() + 1;
2103 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2104 if (!hGlobalMemory) return E_OUTOFMEMORY;
2106 char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
2107 if (!contents) {
2108 GlobalFree(hGlobalMemory);
2109 return E_OUTOFMEMORY;
2112 strcpy(contents, asciiUrl.get());
2113 GlobalUnlock(hGlobalMemory);
2114 aSTG.hGlobal = hGlobalMemory;
2115 aSTG.tymed = TYMED_HGLOBAL;
2117 return result;
2120 HRESULT
2121 nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2122 HRESULT result = S_OK;
2124 nsAutoString url;
2125 if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
2127 const int totalLen = (url.Length() + 1) * sizeof(char16_t);
2128 HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
2129 if (!hGlobalMemory) return E_OUTOFMEMORY;
2131 wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
2132 if (!contents) {
2133 GlobalFree(hGlobalMemory);
2134 return E_OUTOFMEMORY;
2137 wcscpy(contents, url.get());
2138 GlobalUnlock(hGlobalMemory);
2139 aSTG.hGlobal = hGlobalMemory;
2140 aSTG.tymed = TYMED_HGLOBAL;
2142 return result;
2145 // Gets the filename from the kFilePromiseURLMime flavour
2146 HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
2147 nsAString& aFilename) {
2148 *aSourceURI = nullptr;
2150 NS_ENSURE_TRUE(mTransferable, E_FAIL);
2152 // get the URI from the kFilePromiseURLMime flavor
2153 nsCOMPtr<nsISupports> urlPrimitive;
2154 nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
2155 getter_AddRefs(urlPrimitive));
2156 NS_ENSURE_SUCCESS(rv, E_FAIL);
2157 nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
2158 NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
2160 nsAutoString srcUri;
2161 srcUrlPrimitive->GetData(srcUri);
2162 if (srcUri.IsEmpty()) return E_FAIL;
2163 nsCOMPtr<nsIURI> sourceURI;
2164 NS_NewURI(getter_AddRefs(sourceURI), srcUri);
2166 nsAutoString srcFileName;
2167 nsCOMPtr<nsISupports> fileNamePrimitive;
2168 Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
2169 getter_AddRefs(fileNamePrimitive));
2170 nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
2171 do_QueryInterface(fileNamePrimitive);
2172 if (srcFileNamePrimitive) {
2173 srcFileNamePrimitive->GetData(srcFileName);
2174 } else {
2175 nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
2176 if (!sourceURL) return E_FAIL;
2178 nsAutoCString urlFileName;
2179 sourceURL->GetFileName(urlFileName);
2180 NS_UnescapeURL(urlFileName);
2181 CopyUTF8toUTF16(urlFileName, srcFileName);
2184 // make the name safe for the filesystem
2185 ValidateFilename(srcFileName, false);
2186 if (srcFileName.IsEmpty()) return E_FAIL;
2188 sourceURI.swap(*aSourceURI);
2189 aFilename = srcFileName;
2190 return S_OK;
2193 HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
2194 HGLOBAL fileGroupDescHandle =
2195 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2196 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2198 LPFILEGROUPDESCRIPTORA fileGroupDescA =
2199 reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
2200 if (!fileGroupDescA) {
2201 ::GlobalFree(fileGroupDescHandle);
2202 return E_OUTOFMEMORY;
2205 nsAutoString wideFileName;
2206 HRESULT res;
2207 nsCOMPtr<nsIURI> sourceURI;
2208 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2209 if (FAILED(res)) {
2210 ::GlobalFree(fileGroupDescHandle);
2211 return res;
2214 nsAutoCString nativeFileName;
2215 NS_CopyUnicodeToNative(wideFileName, nativeFileName);
2217 strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), MAX_PATH - 1);
2218 fileGroupDescA->fgd[0].cFileName[MAX_PATH - 1] = '\0';
2220 // one file in the file block
2221 fileGroupDescA->cItems = 1;
2222 fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
2224 GlobalUnlock(fileGroupDescHandle);
2225 aSTG.hGlobal = fileGroupDescHandle;
2226 aSTG.tymed = TYMED_HGLOBAL;
2228 return S_OK;
2231 HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
2232 HGLOBAL fileGroupDescHandle =
2233 ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
2234 NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
2236 LPFILEGROUPDESCRIPTORW fileGroupDescW =
2237 reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
2238 if (!fileGroupDescW) {
2239 ::GlobalFree(fileGroupDescHandle);
2240 return E_OUTOFMEMORY;
2243 nsAutoString wideFileName;
2244 HRESULT res;
2245 nsCOMPtr<nsIURI> sourceURI;
2246 res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
2247 if (FAILED(res)) {
2248 ::GlobalFree(fileGroupDescHandle);
2249 return res;
2252 wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(), MAX_PATH - 1);
2253 fileGroupDescW->fgd[0].cFileName[MAX_PATH - 1] = '\0';
2254 // one file in the file block
2255 fileGroupDescW->cItems = 1;
2256 fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
2258 GlobalUnlock(fileGroupDescHandle);
2259 aSTG.hGlobal = fileGroupDescHandle;
2260 aSTG.tymed = TYMED_HGLOBAL;
2262 return S_OK;
2265 HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
2266 IStream* pStream = nullptr;
2268 nsDataObj::CreateStream(&pStream);
2269 NS_ENSURE_TRUE(pStream, E_FAIL);
2271 aSTG.tymed = TYMED_ISTREAM;
2272 aSTG.pstm = pStream;
2273 aSTG.pUnkForRelease = nullptr;
2275 return S_OK;