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"
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"
21 #include "nsPrimitiveHelpers.h"
24 #include "nsPrintfCString.h"
25 #include "nsIStringBundle.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"
36 #include "nsDirectoryServiceDefs.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"
48 #include "nsLocalFile.h"
50 #include "mozilla/LazyIdleThread.h"
53 using namespace mozilla
;
54 using namespace mozilla::glue
;
55 using namespace mozilla::widget
;
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
;
87 rv
= NS_NewChannel(getter_AddRefs(mChannel
), pSourceURI
, aRequestingPrincipal
,
88 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT
,
89 aContentPolicyType
, aCookieJarSettings
,
90 nullptr, // PerformanceStorage
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
);
101 //-----------------------------------------------------------------------------
102 // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
103 // IUnknown and nsIStreamListener.
104 STDMETHODIMP
nsDataObj::CStream::QueryInterface(REFIID refiid
,
106 *ppvResult
= nullptr;
107 if (IID_IUnknown
== refiid
|| refiid
== IID_IStream
)
113 if (nullptr != *ppvResult
) {
114 ((LPUNKNOWN
)*ppvResult
)->AddRef();
118 return E_NOINTERFACE
;
121 // nsIStreamListener implementation
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
);
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.
139 uint32_t odaBytesReadTotal
= 0;
141 uint32_t bytesReadByCall
= 0;
142 rv
= aInputStream
->Read((char*)(buffer
+ odaBytesReadTotal
), aCount
,
144 odaBytesReadTotal
+= bytesReadByCall
;
145 } while (aCount
< odaBytesReadTotal
&& NS_SUCCEEDED(rv
));
149 NS_IMETHODIMP
nsDataObj::CStream::OnStartRequest(nsIRequest
* aRequest
) {
150 mChannelResult
= NS_OK
;
154 NS_IMETHODIMP
nsDataObj::CStream::OnStopRequest(nsIRequest
* aRequest
,
155 nsresult aStatusCode
) {
157 mChannelResult
= aStatusCode
;
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 //-----------------------------------------------------------------------------
176 STDMETHODIMP
nsDataObj::CStreamBase::Clone(IStream
** ppStream
) {
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
) {
191 //-----------------------------------------------------------------------------
192 STDMETHODIMP
nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart
,
193 ULARGE_INTEGER nBytes
,
198 //-----------------------------------------------------------------------------
199 STDMETHODIMP
nsDataObj::CStream::Read(void* pvBuffer
, ULONG nBytesToRead
,
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
;
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;
234 //-----------------------------------------------------------------------------
235 STDMETHODIMP
nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize
) {
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
)))) {
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
;
273 statstg
->type
= STGTY_STREAM
;
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
;
287 //-----------------------------------------------------------------------------
288 STDMETHODIMP
nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart
,
289 ULARGE_INTEGER nBytes
,
294 //-----------------------------------------------------------------------------
295 STDMETHODIMP
nsDataObj::CStreamBase::Write(const void* pvBuffer
,
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
;
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
);
318 // query the requestingPrincipal from the transferable and add it to the new
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
,
336 *outStream
= pStream
;
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
)
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 //-----------------------------------------------------------------------------
383 STDMETHODIMP
nsDataObj::CMemStream::QueryInterface(REFIID refiid
,
385 *ppvResult
= nullptr;
386 if (refiid
== IID_IUnknown
|| refiid
== IID_IStream
||
387 refiid
== IID_IAgileObject
) {
389 } else if (refiid
== IID_IMarshal
&& mMarshaler
) {
390 return mMarshaler
->QueryInterface(refiid
, ppvResult
);
393 if (nullptr != *ppvResult
) {
394 ((LPUNKNOWN
)*ppvResult
)->AddRef();
398 return E_NOINTERFACE
;
401 void nsDataObj::CMemStream::WaitForCompletion() {
403 // We are not waiting for obtaining the icon cache.
406 if (!NS_IsMainThread()) {
407 mEvent
->Wait(INFINITE
);
409 // We should not block the main thread.
412 // mEvent will always be in the signaled state here.
415 //-----------------------------------------------------------------------------
417 STDMETHODIMP
nsDataObj::CMemStream::Read(void* pvBuffer
, ULONG nBytesToRead
,
419 // Wait until the event is signaled.
422 AutoExclusiveLock
lock(mLock
);
423 char* contents
= reinterpret_cast<char*>(GlobalLock(mGlobalMem
.get()));
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());
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
;
458 statstg
->type
= STGTY_STREAM
;
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
;
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
484 //-----------------------------------------------------
486 //-----------------------------------------------------
487 nsDataObj::nsDataObj(nsIURI
* uri
)
489 mTransferable(nullptr),
491 mIsInOperation(FALSE
) {
492 mIOThread
= new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS
, "nsDataObj"_ns
,
493 LazyIdleThread::ManualShutdown
);
494 m_enumFE
= new CEnumFormatEtc();
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 //-----------------------------------------------------
505 //-----------------------------------------------------
506 nsDataObj::~nsDataObj() {
507 NS_IF_RELEASE(mTransferable
);
509 mDataFlavors
.Clear();
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
) {
527 if ((IID_IUnknown
== riid
) || (IID_IDataObject
== riid
)) {
531 } else if (IID_IDataObjectAsyncCapability
== riid
) {
532 *ppv
= static_cast<IDataObjectAsyncCapability
*>(this);
537 return E_NOINTERFACE
;
540 //-----------------------------------------------------
541 STDMETHODIMP_(ULONG
) nsDataObj::AddRef() {
543 NS_LOG_ADDREF(this, m_cRef
, "nsDataObj", sizeof(*this));
545 // When the first reference is taken, hold our own internal reference.
554 class RemoveTempFileHelper final
: public nsIObserver
, public nsINamed
{
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.
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.
566 rv
= NS_NewTimerWithObserver(getter_AddRefs(mTimer
), this, 500,
567 nsITimer::TYPE_ONE_SHOT
);
568 if (NS_WARN_IF(NS_FAILED(rv
))) {
572 nsCOMPtr
<nsIObserverService
> observerService
=
573 do_GetService("@mozilla.org/observer-service;1");
574 if (NS_WARN_IF(!observerService
)) {
579 observerService
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
587 ~RemoveTempFileHelper() {
589 mTempFile
->Remove(false);
593 nsCOMPtr
<nsIFile
> mTempFile
;
594 nsCOMPtr
<nsITimer
> mTimer
;
597 NS_IMPL_ISUPPORTS(RemoveTempFileHelper
, nsIObserver
, nsINamed
);
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
);
617 // Remove the tempfile
619 mTempFile
->Remove(false);
626 RemoveTempFileHelper::GetName(nsACString
& aName
) {
627 aName
.AssignLiteral("RemoveTempFileHelper");
632 //-----------------------------------------------------
633 STDMETHODIMP_(ULONG
) nsDataObj::Release() {
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
649 if (mCachedTempFile
) {
650 RefPtr
<RemoveTempFileHelper
> helper
=
651 new RemoveTempFileHelper(mCachedTempFile
);
652 mCachedTempFile
= nullptr;
656 // In case the destructor ever AddRef/Releases, ensure we don't delete twice
657 // or take mKeepAlive as another reference.
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
)) {
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);
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
703 if (LookupArbitraryFormat(aFormat
, &pde
, FALSE
)) {
704 return CopyMediumData(pSTM
, &pde
->stgm
, aFormat
, FALSE
) ? S_OK
708 // Firefox internal formats
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
;
720 // Someone is asking for plain or unicode text
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.
728 return GetFile(*aFormat
, *pSTM
);
730 // Someone is asking for an image
733 return GetDib(df
, *aFormat
, *pSTM
);
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
);
755 return DATA_E_FORMATETC
;
758 //-----------------------------------------------------
759 STDMETHODIMP
nsDataObj::GetDataHere(LPFORMATETC pFE
, LPSTGMEDIUM pSTM
) {
763 //-----------------------------------------------------
764 // Other objects querying to see if we support a
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
772 if (LookupArbitraryFormat(pFE
, &pde
, FALSE
)) return S_OK
;
774 // Firefox internal formats
778 while (NOERROR
== m_enumFE
->Next(1, &fe
, &count
)) {
779 if (fe
.cfFormat
== pFE
->cfFormat
) {
786 //-----------------------------------------------------
787 STDMETHODIMP
nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn
,
788 LPFORMATETC pFEOut
) {
792 //-----------------------------------------------------
793 STDMETHODIMP
nsDataObj::SetData(LPFORMATETC aFormat
, LPSTGMEDIUM aMedium
,
795 // Arbitrary system formats are used for image feedback during drag
796 // and drop. We are responsible for storing these internally during
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
803 if (pde
->stgm
.tymed
) {
804 ReleaseStgMedium(&pde
->stgm
);
805 memset(&pde
->stgm
, 0, sizeof(STGMEDIUM
));
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
;
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
);
830 bool nsDataObj::LookupArbitraryFormat(FORMATETC
* aFormat
,
831 LPDATAENTRY
* aDataEntry
,
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
];
848 // Medium does not match, not found.
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
866 m_enumFE
->AddFormatEtc(aFormat
);
868 // Store a copy internally in the arbitrary formats array.
869 mDataEntryList
.AppendElement(dataEntry
);
874 bool nsDataObj::CopyMediumData(STGMEDIUM
* aMediumDst
, STGMEDIUM
* aMediumSrc
,
875 LPFORMATETC aFormat
, BOOL aSetData
) {
876 STGMEDIUM stgmOut
= *aMediumSrc
;
878 switch (stgmOut
.tymed
) {
880 stgmOut
.pstm
->AddRef();
883 stgmOut
.pstg
->AddRef();
886 if (!aMediumSrc
->pUnkForRelease
) {
888 if (aMediumSrc
->tymed
!= TYMED_HGLOBAL
) return false;
890 OleDuplicateData(aMediumSrc
->hGlobal
, aFormat
->cfFormat
, 0);
891 if (!stgmOut
.hGlobal
) return false;
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);
903 if (stgmOut
.pUnkForRelease
) stgmOut
.pUnkForRelease
->AddRef();
905 *aMediumDst
= stgmOut
;
910 //-----------------------------------------------------
911 STDMETHODIMP
nsDataObj::EnumFormatEtc(DWORD dwDir
, LPENUMFORMATETC
* ppEnum
) {
914 m_enumFE
->Clone(ppEnum
);
922 if (nullptr == *ppEnum
) return E_FAIL
;
925 // Clone already AddRefed the result so don't addref it again.
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
,
948 mIsInOperation
= FALSE
;
952 STDMETHODIMP
nsDataObj::GetAsyncMode(BOOL
* pfIsOpAsync
) {
953 *pfIsOpAsync
= mIsAsyncMode
;
958 STDMETHODIMP
nsDataObj::InOperation(BOOL
* pfInAsyncOp
) {
959 *pfInAsyncOp
= mIsInOperation
;
964 STDMETHODIMP
nsDataObj::SetAsyncMode(BOOL fDoOpAsync
) {
965 mIsAsyncMode
= fDoOpAsync
;
969 STDMETHODIMP
nsDataObj::StartOperation(IBindCtx
* pbcReserved
) {
970 mIsInOperation
= TRUE
;
977 // Someone is asking for a bitmap. The data in the transferable will be a
978 // straight imgIContainer, so just QI it.
981 nsDataObj::GetDib(const nsACString
& inFlavor
, FORMATETC
& aFormat
,
983 nsCOMPtr
<nsISupports
> genericDataWrapper
;
985 mTransferable
->GetTransferData(PromiseFlatCString(inFlavor
).get(),
986 getter_AddRefs(genericDataWrapper
)))) {
990 nsCOMPtr
<imgIContainer
> image
= do_QueryInterface(genericDataWrapper
);
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");
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
) {
1012 nsCOMPtr
<imgIEncoder
> encoder
= do_QueryInterface(inputStream
);
1018 rv
= encoder
->GetImageBufferUsed(&size
);
1019 if (NS_FAILED(rv
) || size
<= BFH_LENGTH
) {
1023 char* src
= nullptr;
1024 rv
= encoder
->GetImageBuffer(&src
);
1025 if (NS_FAILED(rv
) || !src
) {
1029 // We don't want the file header.
1033 HGLOBAL glob
= ::GlobalAlloc(GMEM_MOVEABLE
| GMEM_ZEROINIT
, size
);
1038 char* dst
= (char*)::GlobalLock(glob
);
1039 ::CopyMemory(dst
, src
, size
);
1040 ::GlobalUnlock(glob
);
1042 aSTG
.hGlobal
= glob
;
1043 aSTG
.tymed
= TYMED_HGLOBAL
;
1048 // GetFileDescriptor
1052 nsDataObj ::GetFileDescriptor(FORMATETC
& aFE
, STGMEDIUM
& aSTG
,
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
)) {
1060 return GetFileDescriptor_IStreamW(aFE
, aSTG
);
1062 return GetFileDescriptor_IStreamA(aFE
, aSTG
);
1063 } else if (IsFlavourPresent(kURLMime
)) {
1065 res
= GetFileDescriptorInternetShortcutW(aFE
, aSTG
);
1067 res
= GetFileDescriptorInternetShortcutA(aFE
, aSTG
);
1069 NS_WARNING("Not yet implemented\n");
1072 } // GetFileDescriptor
1076 nsDataObj ::GetFileContents(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
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
);
1086 NS_WARNING("Not yet implemented\n");
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();
1100 nsAutoString outFilename
;
1101 mimeService
->ValidateFileNameForSaving(aFilename
, EmptyCString(),
1102 nsIMIMEService::VALIDATE_SANITIZE_ONLY
,
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
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
= '_';
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
);
1138 // empty names aren't permitted
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
);
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.
1179 nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC
& aFE
,
1181 // get the title of the shortcut
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
;
1219 } // GetFileDescriptorInternetShortcutA
1222 nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC
& aFE
,
1224 // get the title of the shortcut
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
;
1262 } // GetFileDescriptorInternetShortcutW
1265 // GetFileContentsInternetShortcut
1267 // Create the special format for an internet shortcut and build up the data
1268 // structures the shell is expecting.
1271 nsDataObj ::GetFileContentsInternetShortcut(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
1272 static const char* kShellIconPref
= "browser.shell.shortcutFavicons";
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
)) {
1282 nsAutoCString asciiUrl
;
1283 rv
= aUri
->GetAsciiSpec(asciiUrl
);
1284 if (NS_FAILED(rv
)) {
1288 RefPtr
<AutoCloseEvent
> event
;
1290 const char* shortcutFormatStr
;
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
1298 nsCOMPtr
<nsIFile
> icoFile
;
1300 nsAutoString aUriHash
;
1302 event
= new AutoCloseEvent();
1303 if (!event
->IsInited()) {
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.
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);
1323 rv
= mozilla::widget::FaviconHelper::GetOutputIconPath(aUri
, icoFile
, true);
1324 NS_ENSURE_SUCCESS(rv
, E_FAIL
);
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
);
1332 "[InternetShortcut]\r\nURL=%s\r\n"
1333 "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
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,
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()));
1360 return E_OUTOFMEMORY
;
1363 // NOTE: we intentionally use the Microsoft version of snprintf here because
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());
1372 _snprintf(contents
, totalLen
, shortcutFormatStr
, asciiUrl
.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.
1384 RefPtr
<IStream
> stream
=
1385 new CMemStream(globalMem
.disown(), totalLen
, event
.forget());
1386 stream
.forget(&aSTG
.pstm
);
1387 aSTG
.tymed
= TYMED_ISTREAM
;
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
;
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!
1415 } // for each flavor
1420 HRESULT
nsDataObj::GetPreferredDropEffect(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
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
);
1438 res
= E_OUTOFMEMORY
;
1440 aSTG
.hGlobal
= hGlobalMemory
;
1444 //-----------------------------------------------------
1445 HRESULT
nsDataObj::GetText(const nsACString
& aDataFlavor
, FORMATETC
& aFE
,
1447 void* data
= nullptr;
1449 // if someone asks for text/plain, look up text/unicode instead in the
1451 const char* flavorStr
;
1452 const nsPromiseFlatCString
& flat
= PromiseFlatCString(aDataFlavor
);
1453 if (aDataFlavor
.EqualsLiteral("text/plain"))
1454 flavorStr
= kUnicodeMime
;
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
) {
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.
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.
1498 data
= plainTextData
;
1499 allocLen
= plainTextLen
;
1501 free(plainTextData
);
1502 NS_WARNING("Oh no, couldn't convert unicode to plain text");
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;
1511 BuildPlatformHTML(converter
.get(), &utf8HTML
); // null terminates
1514 if (NS_SUCCEEDED(rv
) && utf8HTML
) {
1515 // replace the unicode data with our HTML data. Don't forget the null.
1517 allocLen
= strlen(utf8HTML
) + sizeof(char);
1519 NS_WARNING("Oh no, couldn't convert to HTML");
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
1546 //-----------------------------------------------------
1547 HRESULT
nsDataObj::GetFile(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
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
);
1565 HRESULT
nsDataObj::DropFile(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
1567 nsCOMPtr
<nsISupports
> genericDataWrapper
;
1569 if (NS_FAILED(mTransferable
->GetTransferData(
1570 kFileMime
, getter_AddRefs(genericDataWrapper
)))) {
1573 nsCOMPtr
<nsIFile
> file(do_QueryInterface(genericDataWrapper
));
1574 if (!file
) return E_FAIL
;
1576 aSTG
.tymed
= TYMED_HGLOBAL
;
1577 aSTG
.pUnkForRelease
= nullptr;
1580 rv
= file
->GetPath(path
);
1581 if (NS_FAILED(rv
)) return E_FAIL
;
1583 uint32_t allocLen
= path
.Length() + 2;
1584 HGLOBAL hGlobalMemory
= nullptr;
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
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
;
1616 HRESULT
nsDataObj::DropImage(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
1618 if (!mCachedTempFile
) {
1619 nsCOMPtr
<nsISupports
> genericDataWrapper
;
1621 if (NS_FAILED(mTransferable
->GetTransferData(
1622 kNativeImageMime
, getter_AddRefs(genericDataWrapper
)))) {
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
) {
1638 nsCOMPtr
<imgIEncoder
> encoder
= do_QueryInterface(inputStream
);
1644 rv
= encoder
->GetImageBufferUsed(&size
);
1645 if (NS_FAILED(rv
)) {
1649 char* src
= nullptr;
1650 rv
= encoder
->GetImageBuffer(&src
);
1651 if (NS_FAILED(rv
) || !src
) {
1655 // Save the bitmap to a temporary location.
1656 nsCOMPtr
<nsIFile
> dropFile
;
1657 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dropFile
));
1662 // Filename must be random so as not to confuse apps like
1663 // Photoshop which handle multiple drags into a single window.
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
)) {
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
)) {
1687 uint32_t written
= 0;
1688 rv
= outStream
->Write(src
, size
, &written
);
1689 if (NS_FAILED(rv
) || written
!= size
) {
1696 // Pass the file name back to the drop target so that it can access the file.
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.
1717 sizeof(DROPFILES
); // Offset to start of file name char array.
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(),
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
;
1741 HRESULT
nsDataObj::DropTempFile(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
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
1751 nsAutoString wideFileName
;
1752 nsCOMPtr
<nsIURI
> sourceURI
;
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
);
1777 ULONG readCount
= 0;
1778 uint32_t writeCount
= 0;
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
;
1790 // Pass the file name back to the drop target so that it can access the file.
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.
1811 sizeof(DROPFILES
); // Offset to start of file name char array.
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(),
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
;
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
) {
1858 NS_ADDREF(mTransferable
);
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
));
1882 urlObject
->GetData(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
);
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
));
1902 urlObject
->GetData(url
);
1908 } // if found flavor
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
));
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));
1945 } // if found flavor
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
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
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()) {
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
2014 constexpr auto kHeaderString
= "<html><body>\r\n<!--StartFragment-->"_ns
;
2015 constexpr auto kTrailingString
=
2016 "<!--EndFragment-->\r\n"
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
;
2068 nsDataObj ::GetUniformResourceLocator(FORMATETC
& aFE
, STGMEDIUM
& aSTG
,
2071 if (IsFlavourPresent(kURLMime
)) {
2073 res
= ExtractUniformResourceLocatorW(aFE
, aSTG
);
2075 res
= ExtractUniformResourceLocatorA(aFE
, aSTG
);
2077 NS_WARNING("Not yet implemented\n");
2082 nsDataObj::ExtractUniformResourceLocatorA(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
2083 HRESULT result
= S_OK
;
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
));
2095 GlobalFree(hGlobalMemory
);
2096 return E_OUTOFMEMORY
;
2099 strcpy(contents
, asciiUrl
.get());
2100 GlobalUnlock(hGlobalMemory
);
2101 aSTG
.hGlobal
= hGlobalMemory
;
2102 aSTG
.tymed
= TYMED_HGLOBAL
;
2108 nsDataObj::ExtractUniformResourceLocatorW(FORMATETC
& aFE
, STGMEDIUM
& aSTG
) {
2109 HRESULT result
= S_OK
;
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
));
2120 GlobalFree(hGlobalMemory
);
2121 return E_OUTOFMEMORY
;
2124 wcscpy(contents
, url
.get());
2125 GlobalUnlock(hGlobalMemory
);
2126 aSTG
.hGlobal
= hGlobalMemory
;
2127 aSTG
.tymed
= TYMED_HGLOBAL
;
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
);
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
;
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
;
2194 nsCOMPtr
<nsIURI
> sourceURI
;
2195 res
= GetDownloadDetails(getter_AddRefs(sourceURI
), wideFileName
);
2197 ::GlobalFree(fileGroupDescHandle
);
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
;
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
;
2233 nsCOMPtr
<nsIURI
> sourceURI
;
2234 res
= GetDownloadDetails(getter_AddRefs(sourceURI
), wideFileName
);
2236 ::GlobalFree(fileGroupDescHandle
);
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
;
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;