Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "mozilla/ArrayUtils.h"
#include "mozilla/TextUtils.h"
#include <ole2.h>
#include <shlobj.h>
#include "nsComponentManagerUtils.h"
#include "nsDataObj.h"
#include "nsArrayUtils.h"
#include "nsClipboard.h"
#include "nsReadableUtils.h"
#include "nsICookieJarSettings.h"
#include "nsIHttpChannel.h"
#include "nsISupportsPrimitives.h"
#include "nsITransferable.h"
#include "IEnumFE.h"
#include "nsPrimitiveHelpers.h"
#include "nsString.h"
#include "nsCRT.h"
#include "nsPrintfCString.h"
#include "nsIStringBundle.h"
#include "nsEscape.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "mozilla/Components.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_clipboard.h"
#include "mozilla/Unused.h"
#include "nsProxyRelease.h"
#include "nsIObserverService.h"
#include "nsIOutputStream.h"
#include "nscore.h"
#include "nsDirectoryServiceDefs.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
#include "nsIPrincipal.h"
#include "nsNativeCharsetUtils.h"
#include "nsMimeTypes.h"
#include "nsIMIMEService.h"
#include "imgIEncoder.h"
#include "imgITools.h"
#include "WinOLELock.h"
#include "WinUtils.h"
#include "nsLocalFile.h"
#include "mozilla/LazyIdleThread.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::glue;
using namespace mozilla::widget;
#define BFH_LENGTH 14
#define DEFAULT_THREAD_TIMEOUT_MS 30000
//-----------------------------------------------------------------------------
// CStreamBase implementation
nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {}
//-----------------------------------------------------------------------------
nsDataObj::CStreamBase::~CStreamBase() {}
NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
//-----------------------------------------------------------------------------
// CStream implementation
nsDataObj::CStream::CStream() : mChannelRead(false) {}
//-----------------------------------------------------------------------------
nsDataObj::CStream::~CStream() {}
//-----------------------------------------------------------------------------
// helper - initializes the stream
nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
nsContentPolicyType aContentPolicyType,
nsIPrincipal* aRequestingPrincipal,
nsICookieJarSettings* aCookieJarSettings,
nsIReferrerInfo* aReferrerInfo) {
// we can not create a channel without a requestingPrincipal
if (!aRequestingPrincipal) {
return NS_ERROR_FAILURE;
}
nsresult rv;
rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
aContentPolicyType, aCookieJarSettings,
nullptr, // PerformanceStorage
nullptr, // loadGroup
nullptr, // aCallbacks
nsIRequest::LOAD_FROM_CACHE);
NS_ENSURE_SUCCESS(rv, rv);
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
rv = httpChannel->SetReferrerInfo(aReferrerInfo);
Unused << NS_WARN_IF(NS_FAILED(rv));
}
rv = mChannel->AsyncOpen(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
//-----------------------------------------------------------------------------
// IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
// IUnknown and nsIStreamListener.
STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
void** ppvResult) {
*ppvResult = nullptr;
if (IID_IUnknown == refiid || refiid == IID_IStream)
{
*ppvResult = this;
}
if (nullptr != *ppvResult) {
((LPUNKNOWN)*ppvResult)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
// nsIStreamListener implementation
NS_IMETHODIMP
nsDataObj::CStream::OnDataAvailable(
nsIRequest* aRequest, nsIInputStream* aInputStream,
uint64_t aOffset, // offset within the stream
uint32_t aCount) // bytes available on this call
{
// If we've been asked to read zero bytes, call `Read` once, just to ensure
// any side-effects take place, and return immediately.
if (aCount == 0) {
char buffer[1] = {0};
uint32_t bytesReadByCall = 0;
nsresult rv = aInputStream->Read(buffer, 0, &bytesReadByCall);
MOZ_ASSERT(bytesReadByCall == 0);
return rv;
}
// Extend the write buffer for the incoming data.
size_t oldLength = mChannelData.Length();
char* buffer =
reinterpret_cast<char*>(mChannelData.AppendElements(aCount, fallible));
if (!buffer) {
return NS_ERROR_OUT_OF_MEMORY;
}
MOZ_ASSERT(mChannelData.Length() == (aOffset + aCount),
"stream length mismatch w/write buffer");
// Read() may not return aCount on a single call, so loop until we've
// accumulated all the data OnDataAvailable has promised.
uint32_t bytesRead = 0;
while (bytesRead < aCount) {
uint32_t bytesReadByCall = 0;
nsresult rv = aInputStream->Read(buffer + bytesRead, aCount - bytesRead,
&bytesReadByCall);
bytesRead += bytesReadByCall;
if (bytesReadByCall == 0) {
// A `bytesReadByCall` of zero indicates EOF without failure... but we
// were promised `aCount` elements and haven't gotten them. Return a
// generic failure.
rv = NS_ERROR_FAILURE;
}
if (NS_FAILED(rv)) {
// Drop any trailing uninitialized elements before erroring out.
mChannelData.RemoveElementsAt(oldLength + bytesRead, aCount - bytesRead);
return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest) {
mChannelResult = NS_OK;
return NS_OK;
}
NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) {
mChannelRead = true;
mChannelResult = aStatusCode;
return NS_OK;
}
// Pumps thread messages while waiting for the async listener operation to
// complete. Failing this call will fail the stream incall from Windows
// and cancel the operation.
nsresult nsDataObj::CStream::WaitForCompletion() {
// We are guaranteed OnStopRequest will get called, so this should be ok.
SpinEventLoopUntil("widget:nsDataObj::CStream::WaitForCompletion"_ns,
[&]() { return mChannelRead; });
if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
return mChannelResult;
}
//-----------------------------------------------------------------------------
// IStream
STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; }
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream,
ULARGE_INTEGER nBytesToCopy,
ULARGE_INTEGER* nBytesRead,
ULARGE_INTEGER* nBytesWritten) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart,
ULARGE_INTEGER nBytes,
DWORD dwFlags) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
ULONG* nBytesRead) {
// Wait for the write into our buffer to complete via the stream listener.
// We can't respond to this by saying "call us back later".
if (NS_FAILED(WaitForCompletion())) return E_FAIL;
// Bytes left for Windows to read out of our buffer
ULONG bytesLeft = mChannelData.Length() - mStreamRead;
// Let Windows know what we will hand back, usually this is the entire buffer
*nBytesRead = std::min(bytesLeft, nBytesToRead);
// Copy the buffer data over
memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
// Update our bytes read tracking
mStreamRead += *nBytesRead;
return S_OK;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; }
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
ULARGE_INTEGER* nNewPos) {
if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
(dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
nNewPos->LowPart = 0;
nNewPos->HighPart = 0;
return S_OK;
}
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
if (statstg == nullptr) return STG_E_INVALIDPOINTER;
if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
memset((void*)statstg, 0, sizeof(STATSTG));
if (dwFlags != STATFLAG_NONAME) {
nsCOMPtr<nsIURI> sourceURI;
if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
return E_FAIL;
}
nsAutoCString strFileName;
nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
sourceURL->GetFileName(strFileName);
if (strFileName.IsEmpty()) return E_FAIL;
NS_UnescapeURL(strFileName);
NS_ConvertUTF8toUTF16 wideFileName(strFileName);
uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller
if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
ZeroMemory(retBuf, nMaxNameLength);
memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
statstg->pwcsName = (LPOLESTR)retBuf;
}
SYSTEMTIME st;
statstg->type = STGTY_STREAM;
GetSystemTime(&st);
SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
statstg->ctime = statstg->atime = statstg->mtime;
statstg->cbSize.QuadPart = mChannelData.Length();
statstg->grfMode = STGM_READ;
statstg->grfLocksSupported = LOCK_ONLYONCE;
statstg->clsid = CLSID_NULL;
return S_OK;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart,
ULARGE_INTEGER nBytes,
DWORD dwFlags) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer,
ULONG nBytesToRead,
ULONG* nBytesRead) {
return E_NOTIMPL;
}
//-----------------------------------------------------------------------------
HRESULT nsDataObj::CreateStream(IStream** outStream) {
NS_ENSURE_TRUE(outStream, E_INVALIDARG);
nsresult rv = NS_ERROR_FAILURE;
nsAutoString wideFileName;
nsCOMPtr<nsIURI> sourceURI;
HRESULT res;
res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
if (FAILED(res)) return res;
nsDataObj::CStream* pStream = new nsDataObj::CStream();
NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
pStream->AddRef();
// query the dataPrincipal from the transferable and add it to the new
// channel.
nsCOMPtr<nsIPrincipal> requestingPrincipal =
mTransferable->GetDataPrincipal();
MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
// Note that the cookieJarSettings could be null if the data object is for the
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
mTransferable->GetCookieJarSettings();
// The referrer is optional.
nsCOMPtr<nsIReferrerInfo> referrerInfo = mTransferable->GetReferrerInfo();
nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType();
rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal,
cookieJarSettings, referrerInfo);
if (NS_FAILED(rv)) {
pStream->Release();
return E_FAIL;
}
*outStream = pStream;
return S_OK;
}
//-----------------------------------------------------------------------------
// AutoCloseEvent implementation
nsDataObj::AutoCloseEvent::AutoCloseEvent()
: mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}
bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; }
void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); }
DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const {
return ::WaitForSingleObject(mEvent, aMillisec);
}
//-----------------------------------------------------------------------------
// AutoSetEvent implementation
nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent)
: mEvent(aEvent) {}
nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); }
void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); }
bool nsDataObj::AutoSetEvent::IsWaiting() const {
return mEvent->Wait(0) == WAIT_TIMEOUT;
}
//-----------------------------------------------------------------------------
// CMemStream implementation
Win32SRWLock nsDataObj::CMemStream::mLock;
//-----------------------------------------------------------------------------
nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength,
already_AddRefed<AutoCloseEvent> aEvent)
: mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) {
::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler));
}
//-----------------------------------------------------------------------------
nsDataObj::CMemStream::~CMemStream() {}
//-----------------------------------------------------------------------------
// IUnknown
STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid,
void** ppvResult) {
*ppvResult = nullptr;
if (refiid == IID_IUnknown || refiid == IID_IStream ||
refiid == IID_IAgileObject) {
*ppvResult = this;
} else if (refiid == IID_IMarshal && mMarshaler) {
return mMarshaler->QueryInterface(refiid, ppvResult);
}
if (nullptr != *ppvResult) {
((LPUNKNOWN)*ppvResult)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
void nsDataObj::CMemStream::WaitForCompletion() {
if (!mEvent) {
// We are not waiting for obtaining the icon cache.
return;
}
if (!NS_IsMainThread()) {
mEvent->Wait(INFINITE);
} else {
// We should not block the main thread.
mEvent->Signal();
}
// mEvent will always be in the signaled state here.
}
//-----------------------------------------------------------------------------
// IStream
STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead,
ULONG* nBytesRead) {
// Wait until the event is signaled.
WaitForCompletion();
AutoExclusiveLock lock(mLock);
char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get()));
if (!contents) {
return E_OUTOFMEMORY;
}
// Bytes left for Windows to read out of our buffer
ULONG bytesLeft = mTotalLength - mStreamRead;
// Let Windows know what we will hand back, usually this is the entire buffer
*nBytesRead = std::min(bytesLeft, nBytesToRead);
// Copy the buffer data over
memcpy(pvBuffer, contents + mStreamRead, *nBytesRead);
// Update our bytes read tracking
mStreamRead += *nBytesRead;
GlobalUnlock(mGlobalMem.get());
return S_OK;
}
//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) {
if (statstg == nullptr) return STG_E_INVALIDPOINTER;
memset((void*)statstg, 0, sizeof(STATSTG));
if (dwFlags != STATFLAG_NONAME) {
constexpr size_t kMaxNameLength = sizeof(wchar_t);
void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller
if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
ZeroMemory(retBuf, kMaxNameLength);
statstg->pwcsName = (LPOLESTR)retBuf;
}
SYSTEMTIME st;
statstg->type = STGTY_STREAM;
GetSystemTime(&st);
SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
statstg->ctime = statstg->atime = statstg->mtime;
statstg->cbSize.QuadPart = mTotalLength;
statstg->grfMode = STGM_READ;
statstg->grfLocksSupported = LOCK_ONLYONCE;
statstg->clsid = CLSID_NULL;
return S_OK;
}
/*
* Class nsDataObj
*/
//-----------------------------------------------------
// construction
//-----------------------------------------------------
nsDataObj::nsDataObj(nsIURI* uri)
: m_cRef(0),
mTransferable(nullptr),
mIsAsyncMode(FALSE),
mIsInOperation(FALSE) {
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj",
LazyIdleThread::ManualShutdown);
m_enumFE = new CEnumFormatEtc();
m_enumFE->AddRef();
if (uri) {
// A URI was obtained, so pass this through to the DataObject
// so it can create a SourceURL for CF_HTML flavour
uri->GetSpec(mSourceURL);
}
}
//-----------------------------------------------------
// destruction
//-----------------------------------------------------
nsDataObj::~nsDataObj() {
mTransferable = nullptr;
mDataFlavors.Clear();
m_enumFE->Release();
// Free arbitrary system formats
for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
ReleaseStgMedium(&mDataEntryList[idx]->stgm);
CoTaskMemFree(mDataEntryList[idx]);
}
}
//-----------------------------------------------------
// IUnknown interface methods - see inknown.h for documentation
//-----------------------------------------------------
STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
*ppv = nullptr;
if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
*ppv = this;
AddRef();
return S_OK;
} else if (IID_IDataObjectAsyncCapability == riid) {
*ppv = static_cast<IDataObjectAsyncCapability*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
//-----------------------------------------------------
STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
++m_cRef;
NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
// When the first reference is taken, hold our own internal reference.
if (m_cRef == 1) {
mKeepAlive = this;
}
return m_cRef;
}
namespace {
class RemoveTempFileHelper final : public nsIObserver, public nsINamed {
public:
explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
MOZ_ASSERT(mTempFile);
}
// The attach method is seperate from the constructor as we may be addref-ing
// ourself, and we want to be sure someone has a strong reference to us.
void Attach() {
// We need to listen to both the xpcom shutdown message and our timer, and
// fire when the first of either of these two messages is received.
nsresult rv;
rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
nsITimer::TYPE_ONE_SHOT);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1");
if (NS_WARN_IF(!observerService)) {
mTimer->Cancel();
mTimer = nullptr;
return;
}
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
}
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_NSINAMED
private:
~RemoveTempFileHelper() {
if (mTempFile) {
mTempFile->Remove(false);
}
}
nsCOMPtr<nsIFile> mTempFile;
nsCOMPtr<nsITimer> mTimer;
};
NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver, nsINamed);
NS_IMETHODIMP
RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
// Let's be careful and make sure that we don't die immediately
RefPtr<RemoveTempFileHelper> grip = this;
// Make sure that we aren't called again by destroying references to ourself.
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1");
if (observerService) {
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
// Remove the tempfile
if (mTempFile) {
mTempFile->Remove(false);
mTempFile = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP
RemoveTempFileHelper::GetName(nsACString& aName) {
aName.AssignLiteral("RemoveTempFileHelper");
return NS_OK;
}
} // namespace
//-----------------------------------------------------
STDMETHODIMP_(ULONG) nsDataObj::Release() {
--m_cRef;
NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
// If we hold the last reference, submit release of it to the main thread.
if (m_cRef == 1 && mKeepAlive) {
NS_ReleaseOnMainThread("nsDataObj release", mKeepAlive.forget(), true);
}
if (0 != m_cRef) return m_cRef;
// We have released our last ref on this object and need to delete the
// temp file. External app acting as drop target may still need to open the
// temp file. Addref a timer so it can delay deleting file and destroying
// this object.
if (mCachedTempFile) {
RefPtr<RemoveTempFileHelper> helper =
new RemoveTempFileHelper(mCachedTempFile);
mCachedTempFile = nullptr;
helper->Attach();
}
// In case the destructor ever AddRef/Releases, ensure we don't delete twice
// or take mKeepAlive as another reference.
m_cRef = 1;
delete this;
return 0;
}
//-----------------------------------------------------
BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
const FORMATETC& target) const {
if ((source.cfFormat == target.cfFormat) &&
(source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
return TRUE;
} else {
return FALSE;
}
}
//-----------------------------------------------------
// IDataObject methods
//-----------------------------------------------------
STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
if (!mTransferable) return DV_E_FORMATETC;
// Hold an extra reference in case we end up spinning the event loop.
RefPtr<nsDataObj> keepAliveDuringGetData(this);
uint32_t dfInx = 0;
static CLIPFORMAT fileDescriptorFlavorA =
::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
static CLIPFORMAT fileDescriptorFlavorW =
::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
static CLIPFORMAT uniformResourceLocatorA =
::RegisterClipboardFormat(CFSTR_INETURLA);
static CLIPFORMAT uniformResourceLocatorW =
::RegisterClipboardFormat(CFSTR_INETURLW);
static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
static CLIPFORMAT PreferredDropEffect =
::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
static CLIPFORMAT imagePNGFormat = ::RegisterClipboardFormat(TEXT("PNG"));
// Arbitrary system formats are used for image feedback during drag
// and drop. We are responsible for storing these internally during
// drag operations.
LPDATAENTRY pde;
if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
: E_UNEXPECTED;
}
// Firefox internal formats
ULONG count;
FORMATETC fe;
m_enumFE->Reset();
while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
dfInx < mDataFlavors.Length()) {
nsCString const& df = mDataFlavors.ElementAt(dfInx);
if (FormatsMatch(fe, *aFormat)) {
pSTM->pUnkForRelease =
nullptr; // caller is responsible for deleting this data
CLIPFORMAT const format = aFormat->cfFormat;
// compile-time-constant format indicators:
switch (format) {
// Someone is asking for plain or unicode text
case CF_TEXT:
case CF_UNICODETEXT:
return GetText(df, *aFormat, *pSTM);
// Some 3rd party apps that receive drag and drop files from the browser
// window require support for this.
case CF_HDROP:
return GetFile(*aFormat, *pSTM);
// Someone is asking for an image
case CF_DIBV5:
case CF_DIB:
return GetDib(df, *aFormat, *pSTM, DibType::Bmp);
default: /* fallthrough */;
} // switch
// non-compile-time-constant format indicators:
if (format == imagePNGFormat)
return GetDib(df, *aFormat, *pSTM, DibType::Png);
if (format == fileDescriptorFlavorA)
return GetFileDescriptor(*aFormat, *pSTM, false);
if (format == fileDescriptorFlavorW)
return GetFileDescriptor(*aFormat, *pSTM, true);
if (format == uniformResourceLocatorA)
return GetUniformResourceLocator(*aFormat, *pSTM, false);
if (format == uniformResourceLocatorW)
return GetUniformResourceLocator(*aFormat, *pSTM, true);
if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
if (format == PreferredDropEffect)
return GetPreferredDropEffect(*aFormat, *pSTM);
// MOZ_LOG(gWindowsLog, LogLevel::Info,
// ("***** nsDataObj::GetData - Unknown format %u\n", format));
return GetText(df, *aFormat, *pSTM);
} // if
dfInx++;
} // while
return DATA_E_FORMATETC;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
return E_FAIL;
}
//-----------------------------------------------------
// Other objects querying to see if we support a
// particular format
//-----------------------------------------------------
STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
// Arbitrary system formats are used for image feedback during drag
// and drop. We are responsible for storing these internally during
// drag operations.
LPDATAENTRY pde;
if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
// Firefox internal formats
ULONG count;
FORMATETC fe;
m_enumFE->Reset();
while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
if (fe.cfFormat == pFE->cfFormat) {
return S_OK;
}
}
return E_FAIL;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
LPFORMATETC pFEOut) {
return E_NOTIMPL;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
BOOL shouldRel) {
// Arbitrary system formats are used for image feedback during drag
// and drop. We are responsible for storing these internally during
// drag operations.
LPDATAENTRY pde;
if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
// Release the old data the lookup handed us for this format. This
// may have been set in CopyMediumData when we originally stored the
// data.
if (pde->stgm.tymed) {
ReleaseStgMedium(&pde->stgm);
memset(&pde->stgm, 0, sizeof(STGMEDIUM));
}
bool result = true;
if (shouldRel) {
// If shouldRel is TRUE, the data object called owns the storage medium
// after the call returns. Store the incoming data in our data array for
// release when we are destroyed. This is the common case with arbitrary
// data from explorer.
pde->stgm = *aMedium;
} else {
// Copy the incoming data into our data array. (AFAICT, this never gets
// called with arbitrary formats for drag images.)
result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
}
pde->fe.tymed = pde->stgm.tymed;
return result ? S_OK : DV_E_TYMED;
}
if (shouldRel) ReleaseStgMedium(aMedium);
return S_OK;
}
bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
LPDATAENTRY* aDataEntry,
BOOL aAddorUpdate) {
*aDataEntry = nullptr;
if (aFormat->ptd != nullptr) return false;
// See if it's already in our list. If so return the data entry.
for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
// If the caller requests we update, or if the
// medium type matches, return the entry.
*aDataEntry = mDataEntryList[idx];
return true;
} else {
// Medium does not match, not found.
return false;
}
}
}
if (!aAddorUpdate) return false;
// Add another entry to mDataEntryList
LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
if (!dataEntry) return false;
dataEntry->fe = *aFormat;
*aDataEntry = dataEntry;
memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
// Add this to our IEnumFORMATETC impl. so we can return it when
// it's requested.
m_enumFE->AddFormatEtc(aFormat);
// Store a copy internally in the arbitrary formats array.
mDataEntryList.AppendElement(dataEntry);
return true;
}
bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
LPFORMATETC aFormat, BOOL aSetData) {
STGMEDIUM stgmOut = *aMediumSrc;
switch (stgmOut.tymed) {
case TYMED_ISTREAM:
stgmOut.pstm->AddRef();
break;
case TYMED_ISTORAGE:
stgmOut.pstg->AddRef();
break;
case TYMED_HGLOBAL:
if (!aMediumSrc->pUnkForRelease) {
if (aSetData) {
if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
stgmOut.hGlobal =
OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
if (!stgmOut.hGlobal) return false;
} else {
// We are returning this data from LookupArbitraryFormat, indicate to
// the shell we hold it and will free it.
stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
}
}
break;
default:
return false;
}
if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
*aMediumDst = stgmOut;
return true;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
switch (dwDir) {
case DATADIR_GET:
m_enumFE->Clone(ppEnum);
break;
case DATADIR_SET:
// fall through
default:
*ppEnum = nullptr;
} // switch
if (nullptr == *ppEnum) return E_FAIL;
(*ppEnum)->Reset();
// Clone already AddRefed the result so don't addref it again.
return NOERROR;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
return OLE_E_ADVISENOTSUPPORTED;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
return OLE_E_ADVISENOTSUPPORTED;
}
//-----------------------------------------------------
STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
return OLE_E_ADVISENOTSUPPORTED;
}
// IDataObjectAsyncCapability methods
STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
DWORD dwEffects) {
mIsInOperation = FALSE;
return S_OK;
}
STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
*pfIsOpAsync = mIsAsyncMode;
return S_OK;
}
STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
*pfInAsyncOp = mIsInOperation;
return S_OK;
}
STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
mIsAsyncMode = fDoOpAsync;
return S_OK;
}
STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
mIsInOperation = TRUE;
return S_OK;
}
//
// GetDIB
//
// Someone is asking for a bitmap. The data in the transferable will be a
// straight imgIContainer, so just QI it.
//
HRESULT
nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
STGMEDIUM& aSTG, DibType aDibType) {
nsCOMPtr<nsISupports> genericDataWrapper;
if (NS_FAILED(
mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
getter_AddRefs(genericDataWrapper)))) {
return E_FAIL;
}
nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
if (!image) {
return E_FAIL;
}
nsCOMPtr<imgITools> imgTools =
do_CreateInstance("@mozilla.org/image/tools;1");
nsAutoString options(u""_ns);
if (aDibType == DibType::Bmp) {
if (aFormat.cfFormat == CF_DIBV5) {
options.AssignLiteral("version=5");
} else {
options.AssignLiteral("version=3");
}
}
const nsLiteralCString mimeType = aDibType == DibType::Bmp
? nsLiteralCString(IMAGE_BMP)
: nsLiteralCString(IMAGE_PNG);
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = imgTools->EncodeImage(image, mimeType, options,
getter_AddRefs(inputStream));
if (NS_FAILED(rv) || !inputStream) {
return E_FAIL;
}
nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
if (!encoder) {
return E_FAIL;
}
uint32_t size = 0;
rv = encoder->GetImageBufferUsed(&size);
if (NS_FAILED(rv) || size <= BFH_LENGTH) {
return E_FAIL;
}
char* src = nullptr;
rv = encoder->GetImageBuffer(&src);
if (NS_FAILED(rv) || !src) {
return E_FAIL;
}
if (aDibType == DibType::Bmp) {
// We don't want the BMP file-header for CF_DIB; it only exists in files.
src += BFH_LENGTH;
size -= BFH_LENGTH;
}
ScopedOLEMemory<char[]> glob(size);
if (!glob) {
return E_FAIL;
}
{
auto lock = glob.lock();
::CopyMemory(lock.begin(), src, size);
}
aSTG.tymed = TYMED_HGLOBAL;
aSTG.hGlobal = glob.forget();
return S_OK;
}
//
// GetFileDescriptor
//
HRESULT
nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
bool aIsUnicode) {
HRESULT res = S_OK;
// How we handle this depends on if we're dealing with an internet
// shortcut, since those are done under the covers.
if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
if (aIsUnicode)
return GetFileDescriptor_IStreamW(aFE, aSTG);
else
return GetFileDescriptor_IStreamA(aFE, aSTG);
} else if (IsFlavourPresent(kURLMime)) {
if (aIsUnicode)
res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
else
res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
} else
NS_WARNING("Not yet implemented\n");
return res;
} // GetFileDescriptor
//
HRESULT
nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
HRESULT res = S_OK;
// How we handle this depends on if we're dealing with an internet
// shortcut, since those are done under the covers.
if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
return GetFileContents_IStream(aFE, aSTG);
else if (IsFlavourPresent(kURLMime))
return GetFileContentsInternetShortcut(aFE, aSTG);
else
NS_WARNING("Not yet implemented\n");
return res;
} // GetFileContents
// Ensure that the supplied name doesn't have invalid characters.
static void ValidateFilename(nsString& aFilename, bool isShortcut) {
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
aFilename.Truncate();
return;
}
uint32_t flags = nsIMIMEService::VALIDATE_SANITIZE_ONLY;
if (isShortcut) {
flags |= nsIMIMEService::VALIDATE_ALLOW_INVALID_FILENAMES;
}
nsAutoString outFilename;
mimeService->ValidateFileNameForSaving(aFilename, EmptyCString(), flags,
outFilename);
aFilename = outFilename;
}
//
// Given a unicode string, convert it to a valid local charset filename
// and append the .url extension to be used for a shortcut file.
// This ensures that we do not cut MBCS characters in the middle.
//
// It would seem that this is more functionality suited to being in nsIFile.
//
static bool CreateURLFilenameFromTextA(nsAutoString& aText, char* aFilename) {
if (aText.IsEmpty()) {
return false;
}
aText.AppendLiteral(".url");
ValidateFilename(aText, true);
if (aText.IsEmpty()) {
return false;
}
// ValidateFilename should already be checking the filename length, but do
// an extra check to verify for the local code page that the converted text
// doesn't go over MAX_PATH and just return false if it does.
char defaultChar = '_';
int currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
aText.get(), -1, aFilename, MAX_PATH,
&defaultChar, nullptr);
return currLen != 0;
}
// Wide character version of CreateURLFilenameFromTextA
static bool CreateURLFilenameFromTextW(nsAutoString& aText,
wchar_t* aFilename) {
if (aText.IsEmpty()) {
return false;
}
aText.AppendLiteral(".url");
ValidateFilename(aText, true);
if (aText.IsEmpty() || aText.Length() >= MAX_PATH) {
return false;
}
wcscpy(&aFilename[0], aText.get());
return true;
}
static bool GetLocalizedString(const char* aName, nsAString& aString) {
nsCOMPtr<nsIStringBundleService> stringService =
mozilla::components::StringBundle::Service();
if (!stringService) return false;
nsCOMPtr<nsIStringBundle> stringBundle;
nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
getter_AddRefs(stringBundle));
if (NS_FAILED(rv)) return false;
rv = stringBundle->GetStringFromName(aName, aString);
return NS_SUCCEEDED(rv);
}
//
// GetFileDescriptorInternetShortcut
//
// Create the special format for an internet shortcut and build up the data
// structures the shell is expecting.
//
HRESULT
nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
STGMEDIUM& aSTG) {
// get the title of the shortcut
nsAutoString title;
if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
HGLOBAL fileGroupDescHandle =
::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
if (!fileGroupDescHandle) return E_OUTOFMEMORY;
LPFILEGROUPDESCRIPTORA fileGroupDescA =
reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
::GlobalLock(fileGroupDescHandle));
if (!fileGroupDescA) {
::GlobalFree(fileGroupDescHandle);
return E_OUTOFMEMORY;
}
// get a valid filename in the following order: 1) from the page title,
// 2) localized string for an untitled page, 3) just use "Untitled.url"
if (!CreateURLFilenameFromTextA(title, fileGroupDescA->fgd[0].cFileName)) {
nsAutoString untitled;
if (!GetLocalizedString("noPageTitle", untitled) ||
!CreateURLFilenameFromTextA(untitled,
fileGroupDescA->fgd[0].cFileName)) {
strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.url");
}
}
// one file in the file block
fileGroupDescA->cItems = 1;
fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
::GlobalUnlock(fileGroupDescHandle);
aSTG.hGlobal = fileGroupDescHandle;
aSTG.tymed = TYMED_HGLOBAL;
return S_OK;
} // GetFileDescriptorInternetShortcutA
HRESULT
nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
STGMEDIUM& aSTG) {
// get the title of the shortcut
nsAutoString title;
if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
HGLOBAL fileGroupDescHandle =
::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
if (!fileGroupDescHandle) return E_OUTOFMEMORY;
LPFILEGROUPDESCRIPTORW fileGroupDescW =
reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
::GlobalLock(fileGroupDescHandle));
if (!fileGroupDescW) {
::GlobalFree(fileGroupDescHandle);
return E_OUTOFMEMORY;
}
// get a valid filename in the following order: 1) from the page title,
// 2) localized string for an untitled page, 3) just use "Untitled.url"
if (!CreateURLFilenameFromTextW(title, fileGroupDescW->fgd[0].cFileName)) {
nsAutoString untitled;
if (!GetLocalizedString("noPageTitle", untitled) ||
!CreateURLFilenameFromTextW(untitled,
fileGroupDescW->fgd[0].cFileName)) {
wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.url");
}
}
// one file in the file block
fileGroupDescW->cItems = 1;
fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
::GlobalUnlock(fileGroupDescHandle);
aSTG.hGlobal = fileGroupDescHandle;
aSTG.tymed = TYMED_HGLOBAL;
return S_OK;
} // GetFileDescriptorInternetShortcutW
//
// GetFileContentsInternetShortcut
//
// Create the special format for an internet shortcut and build up the data
// structures the shell is expecting.
//
HRESULT
nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
static const char* kShellIconPref = "browser.shell.shortcutFavicons";
nsAutoString url;
if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
nsCOMPtr<nsIURI> aUri;
nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
if (NS_FAILED(rv)) {
return E_FAIL;
}
nsAutoCString asciiUrl;
rv = aUri->GetAsciiSpec(asciiUrl);
if (NS_FAILED(rv)) {
return E_FAIL;
}
RefPtr<AutoCloseEvent> event;
const char* shortcutFormatStr;
int totalLen;
nsCString asciiPath;
if (!Preferences::GetBool(kShellIconPref, true)) {
shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
totalLen = formatLen + asciiUrl.Length(); // don't include null character
} else {