michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nsDataObj.h" michael@0: #include "nsClipboard.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "IEnumFE.h" michael@0: #include "nsPrimitiveHelpers.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsImageClipboard.h" michael@0: #include "nsCRT.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIURL.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsXPCOMStrings.h" michael@0: #include "nscore.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsITimer.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #include "WinUtils.h" michael@0: #include "mozilla/LazyIdleThread.h" michael@0: #include "mozilla/WindowsVersion.h" michael@0: #include michael@0: michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: #define DEFAULT_THREAD_TIMEOUT_MS 30000 michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // CStream implementation michael@0: nsDataObj::CStream::CStream() : michael@0: mChannelRead(false), michael@0: mStreamRead(0) michael@0: { michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsDataObj::CStream::~CStream() michael@0: { michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // helper - initializes the stream michael@0: nsresult nsDataObj::CStream::Init(nsIURI *pSourceURI) michael@0: { michael@0: nsresult rv; michael@0: rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, michael@0: nullptr, nullptr, nullptr, michael@0: nsIRequest::LOAD_FROM_CACHE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by michael@0: // IUnknown and nsIStreamListener. michael@0: STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid, void** ppvResult) michael@0: { michael@0: *ppvResult = nullptr; michael@0: if (IID_IUnknown == refiid || michael@0: refiid == IID_IStream) michael@0: michael@0: { michael@0: *ppvResult = this; michael@0: } michael@0: michael@0: if (nullptr != *ppvResult) michael@0: { michael@0: ((LPUNKNOWN)*ppvResult)->AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: // nsIStreamListener implementation michael@0: NS_IMETHODIMP michael@0: nsDataObj::CStream::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aInputStream, michael@0: uint64_t aOffset, // offset within the stream michael@0: uint32_t aCount) // bytes available on this call michael@0: { michael@0: // Extend the write buffer for the incoming data. michael@0: uint8_t* buffer = mChannelData.AppendElements(aCount); michael@0: if (buffer == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ASSERTION((mChannelData.Length() == (aOffset + aCount)), michael@0: "stream length mismatch w/write buffer"); michael@0: michael@0: // Read() may not return aCount on a single call, so loop until we've michael@0: // accumulated all the data OnDataAvailable has promised. michael@0: nsresult rv; michael@0: uint32_t odaBytesReadTotal = 0; michael@0: do { michael@0: uint32_t bytesReadByCall = 0; michael@0: rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), michael@0: aCount, &bytesReadByCall); michael@0: odaBytesReadTotal += bytesReadByCall; michael@0: } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: mChannelResult = NS_OK; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatusCode) michael@0: { michael@0: mChannelRead = true; michael@0: mChannelResult = aStatusCode; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Pumps thread messages while waiting for the async listener operation to michael@0: // complete. Failing this call will fail the stream incall from Windows michael@0: // and cancel the operation. michael@0: nsresult nsDataObj::CStream::WaitForCompletion() michael@0: { michael@0: // We are guaranteed OnStopRequest will get called, so this should be ok. michael@0: while (!mChannelRead) { michael@0: // Pump messages michael@0: NS_ProcessNextEvent(nullptr, true); michael@0: } michael@0: michael@0: if (!mChannelData.Length()) michael@0: mChannelResult = NS_ERROR_FAILURE; michael@0: michael@0: return mChannelResult; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // IStream michael@0: STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream, michael@0: ULARGE_INTEGER nBytesToCopy, michael@0: ULARGE_INTEGER* nBytesRead, michael@0: ULARGE_INTEGER* nBytesWritten) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::LockRegion(ULARGE_INTEGER nStart, michael@0: ULARGE_INTEGER nBytes, michael@0: DWORD dwFlags) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, michael@0: ULONG nBytesToRead, michael@0: ULONG* nBytesRead) michael@0: { michael@0: // Wait for the write into our buffer to complete via the stream listener. michael@0: // We can't respond to this by saying "call us back later". michael@0: if (NS_FAILED(WaitForCompletion())) michael@0: return E_FAIL; michael@0: michael@0: // Bytes left for Windows to read out of our buffer michael@0: ULONG bytesLeft = mChannelData.Length() - mStreamRead; michael@0: // Let Windows know what we will hand back, usually this is the entire buffer michael@0: *nBytesRead = std::min(bytesLeft, nBytesToRead); michael@0: // Copy the buffer data over michael@0: memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead); michael@0: // Update our bytes read tracking michael@0: mStreamRead += *nBytesRead; michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Revert(void) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Seek(LARGE_INTEGER nMove, michael@0: DWORD dwOrigin, michael@0: ULARGE_INTEGER* nNewPos) michael@0: { michael@0: if (nNewPos == nullptr) michael@0: return STG_E_INVALIDPOINTER; michael@0: michael@0: if (nMove.LowPart == 0 && nMove.HighPart == 0 && michael@0: (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) { michael@0: nNewPos->LowPart = 0; michael@0: nNewPos->HighPart = 0; michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::SetSize(ULARGE_INTEGER nNewSize) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) michael@0: { michael@0: if (statstg == nullptr) michael@0: return STG_E_INVALIDPOINTER; michael@0: michael@0: if (!mChannel || NS_FAILED(WaitForCompletion())) michael@0: return E_FAIL; michael@0: michael@0: memset((void*)statstg, 0, sizeof(STATSTG)); michael@0: michael@0: if (dwFlags != STATFLAG_NONAME) michael@0: { michael@0: nsCOMPtr sourceURI; michael@0: if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: nsAutoCString strFileName; michael@0: nsCOMPtr sourceURL = do_QueryInterface(sourceURI); michael@0: sourceURL->GetFileName(strFileName); michael@0: michael@0: if (strFileName.IsEmpty()) michael@0: return E_FAIL; michael@0: michael@0: NS_UnescapeURL(strFileName); michael@0: NS_ConvertUTF8toUTF16 wideFileName(strFileName); michael@0: michael@0: uint32_t nMaxNameLength = (wideFileName.Length()*2) + 2; michael@0: void * retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller michael@0: if (!retBuf) michael@0: return STG_E_INSUFFICIENTMEMORY; michael@0: michael@0: ZeroMemory(retBuf, nMaxNameLength); michael@0: memcpy(retBuf, wideFileName.get(), wideFileName.Length()*2); michael@0: statstg->pwcsName = (LPOLESTR)retBuf; michael@0: } michael@0: michael@0: SYSTEMTIME st; michael@0: michael@0: statstg->type = STGTY_STREAM; michael@0: michael@0: GetSystemTime(&st); michael@0: SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime); michael@0: statstg->ctime = statstg->atime = statstg->mtime; michael@0: michael@0: statstg->cbSize.LowPart = (DWORD)mChannelData.Length(); michael@0: statstg->grfMode = STGM_READ; michael@0: statstg->grfLocksSupported = LOCK_ONLYONCE; michael@0: statstg->clsid = CLSID_NULL; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart, michael@0: ULARGE_INTEGER nBytes, michael@0: DWORD dwFlags) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::CStream::Write(const void* pvBuffer, michael@0: ULONG nBytesToRead, michael@0: ULONG* nBytesRead) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: HRESULT nsDataObj::CreateStream(IStream **outStream) michael@0: { michael@0: NS_ENSURE_TRUE(outStream, E_INVALIDARG); michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: nsAutoString wideFileName; michael@0: nsCOMPtr sourceURI; michael@0: HRESULT res; michael@0: michael@0: res = GetDownloadDetails(getter_AddRefs(sourceURI), michael@0: wideFileName); michael@0: if(FAILED(res)) michael@0: return res; michael@0: michael@0: nsDataObj::CStream *pStream = new nsDataObj::CStream(); michael@0: NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY); michael@0: michael@0: pStream->AddRef(); michael@0: michael@0: rv = pStream->Init(sourceURI); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: pStream->Release(); michael@0: return E_FAIL; michael@0: } michael@0: *outStream = pStream; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: static GUID CLSID_nsDataObj = michael@0: { 0x1bba7640, 0xdf52, 0x11cf, { 0x82, 0x7b, 0, 0xa0, 0x24, 0x3a, 0xe5, 0x05 } }; michael@0: michael@0: /* michael@0: * deliberately not using MAX_PATH. This is because on platforms < XP michael@0: * a file created with a long filename may be mishandled by the shell michael@0: * resulting in it not being able to be deleted or moved. michael@0: * See bug 250392 for more details. michael@0: */ michael@0: #define NS_MAX_FILEDESCRIPTOR 128 + 1 michael@0: michael@0: /* michael@0: * Class nsDataObj michael@0: */ michael@0: michael@0: //----------------------------------------------------- michael@0: // construction michael@0: //----------------------------------------------------- michael@0: nsDataObj::nsDataObj(nsIURI * uri) michael@0: : m_cRef(0), mTransferable(nullptr), michael@0: mIsAsyncMode(FALSE), mIsInOperation(FALSE) michael@0: { michael@0: mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, michael@0: NS_LITERAL_CSTRING("nsDataObj"), michael@0: LazyIdleThread::ManualShutdown); michael@0: m_enumFE = new CEnumFormatEtc(); michael@0: m_enumFE->AddRef(); michael@0: michael@0: if (uri) { michael@0: // A URI was obtained, so pass this through to the DataObject michael@0: // so it can create a SourceURL for CF_HTML flavour michael@0: uri->GetSpec(mSourceURL); michael@0: } michael@0: } michael@0: //----------------------------------------------------- michael@0: // destruction michael@0: //----------------------------------------------------- michael@0: nsDataObj::~nsDataObj() michael@0: { michael@0: NS_IF_RELEASE(mTransferable); michael@0: michael@0: mDataFlavors.Clear(); michael@0: michael@0: m_enumFE->Release(); michael@0: michael@0: // Free arbitrary system formats michael@0: for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) { michael@0: CoTaskMemFree(mDataEntryList[idx]->fe.ptd); michael@0: ReleaseStgMedium(&mDataEntryList[idx]->stgm); michael@0: CoTaskMemFree(mDataEntryList[idx]); michael@0: } michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------- michael@0: // IUnknown interface methods - see inknown.h for documentation michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) michael@0: { michael@0: *ppv=nullptr; michael@0: michael@0: if ( (IID_IUnknown == riid) || (IID_IDataObject == riid) ) { michael@0: *ppv = this; michael@0: AddRef(); michael@0: return S_OK; michael@0: } else if (IID_IAsyncOperation == riid) { michael@0: *ppv = static_cast(this); michael@0: AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP_(ULONG) nsDataObj::AddRef() michael@0: { michael@0: ++m_cRef; michael@0: NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this)); michael@0: return m_cRef; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP_(ULONG) nsDataObj::Release() michael@0: { michael@0: --m_cRef; michael@0: michael@0: NS_LOG_RELEASE(this, m_cRef, "nsDataObj"); michael@0: if (0 != m_cRef) michael@0: return m_cRef; michael@0: michael@0: // We have released our last ref on this object and need to delete the michael@0: // temp file. External app acting as drop target may still need to open the michael@0: // temp file. Addref a timer so it can delay deleting file and destroying michael@0: // this object. Delete file anyway and destroy this obj if there's a problem. michael@0: if (mCachedTempFile) { michael@0: nsresult rv; michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mTimer->InitWithFuncCallback(nsDataObj::RemoveTempFile, this, michael@0: 500, nsITimer::TYPE_ONE_SHOT); michael@0: return AddRef(); michael@0: } michael@0: mCachedTempFile->Remove(false); michael@0: mCachedTempFile = nullptr; michael@0: } michael@0: michael@0: delete this; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: BOOL nsDataObj::FormatsMatch(const FORMATETC& source, const FORMATETC& target) const michael@0: { michael@0: if ((source.cfFormat == target.cfFormat) && michael@0: (source.dwAspect & target.dwAspect) && michael@0: (source.tymed & target.tymed)) { michael@0: return TRUE; michael@0: } else { michael@0: return FALSE; michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: // IDataObject methods michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) michael@0: { michael@0: if (!mTransferable) michael@0: return DV_E_FORMATETC; michael@0: michael@0: uint32_t dfInx = 0; michael@0: michael@0: static CLIPFORMAT fileDescriptorFlavorA = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORA ); michael@0: static CLIPFORMAT fileDescriptorFlavorW = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORW ); michael@0: static CLIPFORMAT uniformResourceLocatorA = ::RegisterClipboardFormat( CFSTR_INETURLA ); michael@0: static CLIPFORMAT uniformResourceLocatorW = ::RegisterClipboardFormat( CFSTR_INETURLW ); michael@0: static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat( CFSTR_FILECONTENTS ); michael@0: static CLIPFORMAT PreferredDropEffect = ::RegisterClipboardFormat( CFSTR_PREFERREDDROPEFFECT ); michael@0: michael@0: // Arbitrary system formats are used for image feedback during drag michael@0: // and drop. We are responsible for storing these internally during michael@0: // drag operations. michael@0: LPDATAENTRY pde; michael@0: if (LookupArbitraryFormat(aFormat, &pde, FALSE)) { michael@0: return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) michael@0: ? S_OK : E_UNEXPECTED; michael@0: } michael@0: michael@0: // Firefox internal formats michael@0: ULONG count; michael@0: FORMATETC fe; michael@0: m_enumFE->Reset(); michael@0: while (NOERROR == m_enumFE->Next(1, &fe, &count) michael@0: && dfInx < mDataFlavors.Length()) { michael@0: nsCString& df = mDataFlavors.ElementAt(dfInx); michael@0: if (FormatsMatch(fe, *aFormat)) { michael@0: pSTM->pUnkForRelease = nullptr; // caller is responsible for deleting this data michael@0: CLIPFORMAT format = aFormat->cfFormat; michael@0: switch(format) { michael@0: michael@0: // Someone is asking for plain or unicode text michael@0: case CF_TEXT: michael@0: case CF_UNICODETEXT: michael@0: return GetText(df, *aFormat, *pSTM); michael@0: michael@0: // Some 3rd party apps that receive drag and drop files from the browser michael@0: // window require support for this. michael@0: case CF_HDROP: michael@0: return GetFile(*aFormat, *pSTM); michael@0: michael@0: // Someone is asking for an image michael@0: case CF_DIBV5: michael@0: case CF_DIB: michael@0: return GetDib(df, *aFormat, *pSTM); michael@0: michael@0: default: michael@0: if ( format == fileDescriptorFlavorA ) michael@0: return GetFileDescriptor ( *aFormat, *pSTM, false ); michael@0: if ( format == fileDescriptorFlavorW ) michael@0: return GetFileDescriptor ( *aFormat, *pSTM, true); michael@0: if ( format == uniformResourceLocatorA ) michael@0: return GetUniformResourceLocator( *aFormat, *pSTM, false); michael@0: if ( format == uniformResourceLocatorW ) michael@0: return GetUniformResourceLocator( *aFormat, *pSTM, true); michael@0: if ( format == fileFlavor ) michael@0: return GetFileContents ( *aFormat, *pSTM ); michael@0: if ( format == PreferredDropEffect ) michael@0: return GetPreferredDropEffect( *aFormat, *pSTM ); michael@0: //PR_LOG(gWindowsLog, PR_LOG_ALWAYS, michael@0: // ("***** nsDataObj::GetData - Unknown format %u\n", format)); michael@0: return GetText(df, *aFormat, *pSTM); michael@0: } //switch michael@0: } // if michael@0: dfInx++; michael@0: } // while michael@0: michael@0: return DATA_E_FORMATETC; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------- michael@0: // Other objects querying to see if we support a michael@0: // particular format michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) michael@0: { michael@0: // Arbitrary system formats are used for image feedback during drag michael@0: // and drop. We are responsible for storing these internally during michael@0: // drag operations. michael@0: LPDATAENTRY pde; michael@0: if (LookupArbitraryFormat(pFE, &pde, FALSE)) michael@0: return S_OK; michael@0: michael@0: // Firefox internal formats michael@0: ULONG count; michael@0: FORMATETC fe; michael@0: m_enumFE->Reset(); michael@0: while (NOERROR == m_enumFE->Next(1, &fe, &count)) { michael@0: if (fe.cfFormat == pFE->cfFormat) { michael@0: return S_OK; michael@0: } michael@0: } michael@0: return E_FAIL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::GetCanonicalFormatEtc michael@0: (LPFORMATETC pFEIn, LPFORMATETC pFEOut) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium, BOOL shouldRel) michael@0: { michael@0: // Arbitrary system formats are used for image feedback during drag michael@0: // and drop. We are responsible for storing these internally during michael@0: // drag operations. michael@0: LPDATAENTRY pde; michael@0: if (LookupArbitraryFormat(aFormat, &pde, TRUE)) { michael@0: // Release the old data the lookup handed us for this format. This michael@0: // may have been set in CopyMediumData when we originally stored the michael@0: // data. michael@0: if (pde->stgm.tymed) { michael@0: ReleaseStgMedium(&pde->stgm); michael@0: memset(&pde->stgm, 0, sizeof(STGMEDIUM)); michael@0: } michael@0: michael@0: bool result = true; michael@0: if (shouldRel) { michael@0: // If shouldRel is TRUE, the data object called owns the storage medium michael@0: // after the call returns. Store the incoming data in our data array for michael@0: // release when we are destroyed. This is the common case with arbitrary michael@0: // data from explorer. michael@0: pde->stgm = *aMedium; michael@0: } else { michael@0: // Copy the incoming data into our data array. (AFAICT, this never gets michael@0: // called with arbitrary formats for drag images.) michael@0: result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE); michael@0: } michael@0: pde->fe.tymed = pde->stgm.tymed; michael@0: michael@0: return result ? S_OK : DV_E_TYMED; michael@0: } michael@0: michael@0: if (shouldRel) michael@0: ReleaseStgMedium(aMedium); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: bool michael@0: nsDataObj::LookupArbitraryFormat(FORMATETC *aFormat, LPDATAENTRY *aDataEntry, BOOL aAddorUpdate) michael@0: { michael@0: *aDataEntry = nullptr; michael@0: michael@0: if (aFormat->ptd != nullptr) michael@0: return false; michael@0: michael@0: // See if it's already in our list. If so return the data entry. michael@0: for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) { michael@0: if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat && michael@0: mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect && michael@0: mDataEntryList[idx]->fe.lindex == aFormat->lindex) { michael@0: if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) { michael@0: // If the caller requests we update, or if the michael@0: // medium type matches, return the entry. michael@0: *aDataEntry = mDataEntryList[idx]; michael@0: return true; michael@0: } else { michael@0: // Medium does not match, not found. michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!aAddorUpdate) michael@0: return false; michael@0: michael@0: // Add another entry to mDataEntryList michael@0: LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY)); michael@0: if (!dataEntry) michael@0: return false; michael@0: michael@0: dataEntry->fe = *aFormat; michael@0: *aDataEntry = dataEntry; michael@0: memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM)); michael@0: michael@0: // Add this to our IEnumFORMATETC impl. so we can return it when michael@0: // it's requested. michael@0: m_enumFE->AddFormatEtc(aFormat); michael@0: michael@0: // Store a copy internally in the arbitrary formats array. michael@0: mDataEntryList.AppendElement(dataEntry); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsDataObj::CopyMediumData(STGMEDIUM *aMediumDst, STGMEDIUM *aMediumSrc, LPFORMATETC aFormat, BOOL aSetData) michael@0: { michael@0: STGMEDIUM stgmOut = *aMediumSrc; michael@0: michael@0: switch (stgmOut.tymed) { michael@0: case TYMED_ISTREAM: michael@0: stgmOut.pstm->AddRef(); michael@0: break; michael@0: case TYMED_ISTORAGE: michael@0: stgmOut.pstg->AddRef(); michael@0: break; michael@0: case TYMED_HGLOBAL: michael@0: if (!aMediumSrc->pUnkForRelease) { michael@0: if (aSetData) { michael@0: if (aMediumSrc->tymed != TYMED_HGLOBAL) michael@0: return false; michael@0: stgmOut.hGlobal = OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0); michael@0: if (!stgmOut.hGlobal) michael@0: return false; michael@0: } else { michael@0: // We are returning this data from LookupArbitraryFormat, indicate to the michael@0: // shell we hold it and will free it. michael@0: stgmOut.pUnkForRelease = static_cast(this); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: if (stgmOut.pUnkForRelease) michael@0: stgmOut.pUnkForRelease->AddRef(); michael@0: michael@0: *aMediumDst = stgmOut; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC *ppEnum) michael@0: { michael@0: switch (dwDir) { michael@0: case DATADIR_GET: michael@0: m_enumFE->Clone(ppEnum); michael@0: break; michael@0: case DATADIR_SET: michael@0: // fall through michael@0: default: michael@0: *ppEnum = nullptr; michael@0: } // switch michael@0: michael@0: if (nullptr == *ppEnum) michael@0: return E_FAIL; michael@0: michael@0: (*ppEnum)->Reset(); michael@0: // Clone already AddRefed the result so don't addref it again. michael@0: return NOERROR; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags, michael@0: LPADVISESINK pIAdviseSink, DWORD* pdwConn) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA *ppEnum) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // IAsyncOperation methods michael@0: STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, michael@0: IBindCtx *pbcReserved, michael@0: DWORD dwEffects) michael@0: { michael@0: mIsInOperation = FALSE; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObj::GetAsyncMode(BOOL *pfIsOpAsync) michael@0: { michael@0: *pfIsOpAsync = mIsAsyncMode; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObj::InOperation(BOOL *pfInAsyncOp) michael@0: { michael@0: *pfInAsyncOp = mIsInOperation; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) michael@0: { michael@0: mIsAsyncMode = fDoOpAsync; michael@0: return S_OK; michael@0: } michael@0: michael@0: STDMETHODIMP nsDataObj::StartOperation(IBindCtx *pbcReserved) michael@0: { michael@0: mIsInOperation = TRUE; michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: // GetData and SetData helper functions michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::AddSetFormat(FORMATETC& aFE) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::AddGetFormat(FORMATETC& aFE) michael@0: { michael@0: return S_OK; michael@0: } michael@0: michael@0: // michael@0: // GetDIB michael@0: // michael@0: // Someone is asking for a bitmap. The data in the transferable will be a straight michael@0: // imgIContainer, so just QI it. michael@0: // michael@0: HRESULT michael@0: nsDataObj::GetDib(const nsACString& inFlavor, michael@0: FORMATETC &aFormat, michael@0: STGMEDIUM & aSTG) michael@0: { michael@0: ULONG result = E_FAIL; michael@0: uint32_t len = 0; michael@0: nsCOMPtr genericDataWrapper; michael@0: mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(), getter_AddRefs(genericDataWrapper), &len); michael@0: nsCOMPtr image ( do_QueryInterface(genericDataWrapper) ); michael@0: if ( !image ) { michael@0: // Check if the image was put in an nsISupportsInterfacePointer wrapper. michael@0: // This might not be necessary any more, but could be useful for backwards michael@0: // compatibility. michael@0: nsCOMPtr ptr(do_QueryInterface(genericDataWrapper)); michael@0: if ( ptr ) { michael@0: nsCOMPtr supports; michael@0: ptr->GetData(getter_AddRefs(supports)); michael@0: image = do_QueryInterface(supports); michael@0: } michael@0: } michael@0: michael@0: if ( image ) { michael@0: // use the |nsImageToClipboard| helper class to build up a bitmap. We now own michael@0: // the bits, and pass them back to the OS in |aSTG|. michael@0: nsImageToClipboard converter(image, aFormat.cfFormat == CF_DIBV5); michael@0: HANDLE bits = nullptr; michael@0: nsresult rv = converter.GetPicture ( &bits ); michael@0: if ( NS_SUCCEEDED(rv) && bits ) { michael@0: aSTG.hGlobal = bits; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: result = S_OK; michael@0: } michael@0: } // if we have an image michael@0: else michael@0: NS_WARNING ( "Definitely not an image on clipboard" ); michael@0: return result; michael@0: } michael@0: michael@0: michael@0: michael@0: // michael@0: // GetFileDescriptor michael@0: // michael@0: michael@0: HRESULT michael@0: nsDataObj :: GetFileDescriptor ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) michael@0: { michael@0: HRESULT res = S_OK; michael@0: michael@0: // How we handle this depends on if we're dealing with an internet michael@0: // shortcut, since those are done under the covers. michael@0: if (IsFlavourPresent(kFilePromiseMime) || michael@0: IsFlavourPresent(kFileMime)) michael@0: { michael@0: if (aIsUnicode) michael@0: return GetFileDescriptor_IStreamW(aFE, aSTG); michael@0: else michael@0: return GetFileDescriptor_IStreamA(aFE, aSTG); michael@0: } michael@0: else if (IsFlavourPresent(kURLMime)) michael@0: { michael@0: if ( aIsUnicode ) michael@0: res = GetFileDescriptorInternetShortcutW ( aFE, aSTG ); michael@0: else michael@0: res = GetFileDescriptorInternetShortcutA ( aFE, aSTG ); michael@0: } michael@0: else michael@0: NS_WARNING ( "Not yet implemented\n" ); michael@0: michael@0: return res; michael@0: } // GetFileDescriptor michael@0: michael@0: michael@0: // michael@0: HRESULT michael@0: nsDataObj :: GetFileContents ( FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: HRESULT res = S_OK; michael@0: michael@0: // How we handle this depends on if we're dealing with an internet michael@0: // shortcut, since those are done under the covers. michael@0: if (IsFlavourPresent(kFilePromiseMime) || michael@0: IsFlavourPresent(kFileMime)) michael@0: return GetFileContents_IStream(aFE, aSTG); michael@0: else if (IsFlavourPresent(kURLMime)) michael@0: return GetFileContentsInternetShortcut ( aFE, aSTG ); michael@0: else michael@0: NS_WARNING ( "Not yet implemented\n" ); michael@0: michael@0: return res; michael@0: michael@0: } // GetFileContents michael@0: michael@0: // michael@0: // Given a unicode string, we ensure that it contains only characters which are valid within michael@0: // the file system. Remove all forbidden characters from the name, and completely disallow michael@0: // any title that starts with a forbidden name and extension (e.g. "nul" is invalid, but michael@0: // "nul." and "nul.txt" are also invalid and will cause problems). michael@0: // michael@0: // It would seem that this is more functionality suited to being in nsIFile. michael@0: // michael@0: static void michael@0: MangleTextToValidFilename(nsString & aText) michael@0: { michael@0: static const char* forbiddenNames[] = { michael@0: "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", michael@0: "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", michael@0: "CON", "PRN", "AUX", "NUL", "CLOCK$" michael@0: }; michael@0: michael@0: aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS); michael@0: aText.CompressWhitespace(true, true); michael@0: uint32_t nameLen; michael@0: for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) { michael@0: nameLen = (uint32_t) strlen(forbiddenNames[n]); michael@0: if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) { michael@0: // invalid name is either the entire string, or a prefix with a period michael@0: if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) { michael@0: aText.Truncate(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // michael@0: // Given a unicode string, convert it down to a valid local charset filename michael@0: // with the supplied extension. This ensures that we do not cut MBCS characters michael@0: // in the middle. michael@0: // michael@0: // It would seem that this is more functionality suited to being in nsIFile. michael@0: // michael@0: static bool michael@0: CreateFilenameFromTextA(nsString & aText, const char * aExtension, michael@0: char * aFilename, uint32_t aFilenameLen) michael@0: { michael@0: // ensure that the supplied name doesn't have invalid characters. If michael@0: // a valid mangled filename couldn't be created then it will leave the michael@0: // text empty. michael@0: MangleTextToValidFilename(aText); michael@0: if (aText.IsEmpty()) michael@0: return false; michael@0: michael@0: // repeatably call WideCharToMultiByte as long as the title doesn't fit in the buffer michael@0: // available to us. Continually reduce the length of the source title until the MBCS michael@0: // version will fit in the buffer with room for the supplied extension. Doing it this michael@0: // way ensures that even in MBCS environments there will be a valid MBCS filename of michael@0: // the correct length. michael@0: int maxUsableFilenameLen = aFilenameLen - strlen(aExtension) - 1; // space for ext + null byte michael@0: int currLen, textLen = (int) std::min(aText.Length(), aFilenameLen); michael@0: char defaultChar = '_'; michael@0: do { michael@0: currLen = WideCharToMultiByte(CP_ACP, michael@0: WC_COMPOSITECHECK|WC_DEFAULTCHAR, michael@0: aText.get(), textLen--, aFilename, maxUsableFilenameLen, &defaultChar, nullptr); michael@0: } michael@0: while (currLen == 0 && textLen > 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER); michael@0: if (currLen > 0 && textLen > 0) { michael@0: strcpy(&aFilename[currLen], aExtension); michael@0: return true; michael@0: } michael@0: else { michael@0: // empty names aren't permitted michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: CreateFilenameFromTextW(nsString & aText, const wchar_t * aExtension, michael@0: wchar_t * aFilename, uint32_t aFilenameLen) michael@0: { michael@0: // ensure that the supplied name doesn't have invalid characters. If michael@0: // a valid mangled filename couldn't be created then it will leave the michael@0: // text empty. michael@0: MangleTextToValidFilename(aText); michael@0: if (aText.IsEmpty()) michael@0: return false; michael@0: michael@0: const int extensionLen = wcslen(aExtension); michael@0: if (aText.Length() + extensionLen + 1 > aFilenameLen) michael@0: aText.Truncate(aFilenameLen - extensionLen - 1); michael@0: wcscpy(&aFilename[0], aText.get()); michael@0: wcscpy(&aFilename[aText.Length()], aExtension); michael@0: return true; michael@0: } michael@0: michael@0: #define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties" michael@0: michael@0: static bool michael@0: GetLocalizedString(const char16_t * aName, nsXPIDLString & aString) michael@0: { michael@0: nsCOMPtr stringService = michael@0: mozilla::services::GetStringBundleService(); michael@0: if (!stringService) michael@0: return false; michael@0: michael@0: nsCOMPtr stringBundle; michael@0: nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES, michael@0: getter_AddRefs(stringBundle)); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: rv = stringBundle->GetStringFromName(aName, getter_Copies(aString)); michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: michael@0: // michael@0: // GetFileDescriptorInternetShortcut michael@0: // michael@0: // Create the special format for an internet shortcut and build up the data michael@0: // structures the shell is expecting. michael@0: // michael@0: HRESULT michael@0: nsDataObj :: GetFileDescriptorInternetShortcutA ( FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: // get the title of the shortcut michael@0: nsAutoString title; michael@0: if ( NS_FAILED(ExtractShortcutTitle(title)) ) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORA)); michael@0: if (!fileGroupDescHandle) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: LPFILEGROUPDESCRIPTORA fileGroupDescA = reinterpret_cast(::GlobalLock(fileGroupDescHandle)); michael@0: if (!fileGroupDescA) { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: // get a valid filename in the following order: 1) from the page title, michael@0: // 2) localized string for an untitled page, 3) just use "Untitled.URL" michael@0: if (!CreateFilenameFromTextA(title, ".URL", michael@0: fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { michael@0: nsXPIDLString untitled; michael@0: if (!GetLocalizedString(MOZ_UTF16("noPageTitle"), untitled) || michael@0: !CreateFilenameFromTextA(untitled, ".URL", michael@0: fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { michael@0: strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.URL"); michael@0: } michael@0: } michael@0: michael@0: // one file in the file block michael@0: fileGroupDescA->cItems = 1; michael@0: fileGroupDescA->fgd[0].dwFlags = FD_LINKUI; michael@0: michael@0: ::GlobalUnlock( fileGroupDescHandle ); michael@0: aSTG.hGlobal = fileGroupDescHandle; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return S_OK; michael@0: } // GetFileDescriptorInternetShortcutA michael@0: michael@0: HRESULT michael@0: nsDataObj :: GetFileDescriptorInternetShortcutW ( FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: // get the title of the shortcut michael@0: nsAutoString title; michael@0: if ( NS_FAILED(ExtractShortcutTitle(title)) ) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); michael@0: if (!fileGroupDescHandle) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: LPFILEGROUPDESCRIPTORW fileGroupDescW = reinterpret_cast(::GlobalLock(fileGroupDescHandle)); michael@0: if (!fileGroupDescW) { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: // get a valid filename in the following order: 1) from the page title, michael@0: // 2) localized string for an untitled page, 3) just use "Untitled.URL" michael@0: if (!CreateFilenameFromTextW(title, L".URL", michael@0: fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { michael@0: nsXPIDLString untitled; michael@0: if (!GetLocalizedString(MOZ_UTF16("noPageTitle"), untitled) || michael@0: !CreateFilenameFromTextW(untitled, L".URL", michael@0: fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { michael@0: wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.URL"); michael@0: } michael@0: } michael@0: michael@0: // one file in the file block michael@0: fileGroupDescW->cItems = 1; michael@0: fileGroupDescW->fgd[0].dwFlags = FD_LINKUI; michael@0: michael@0: ::GlobalUnlock( fileGroupDescHandle ); michael@0: aSTG.hGlobal = fileGroupDescHandle; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return S_OK; michael@0: } // GetFileDescriptorInternetShortcutW michael@0: michael@0: michael@0: // michael@0: // GetFileContentsInternetShortcut michael@0: // michael@0: // Create the special format for an internet shortcut and build up the data michael@0: // structures the shell is expecting. michael@0: // michael@0: HRESULT michael@0: nsDataObj :: GetFileContentsInternetShortcut ( FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: static const char * kShellIconPref = "browser.shell.shortcutFavicons"; michael@0: nsAutoString url; michael@0: if ( NS_FAILED(ExtractShortcutURL(url)) ) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: // will need to change if we ever support iDNS michael@0: nsAutoCString asciiUrl; michael@0: LossyCopyUTF16toASCII(url, asciiUrl); michael@0: michael@0: nsCOMPtr icoFile; michael@0: nsCOMPtr aUri; michael@0: NS_NewURI(getter_AddRefs(aUri), url); michael@0: michael@0: const char *shortcutFormatStr; michael@0: int totalLen; michael@0: nsCString path; michael@0: if (!Preferences::GetBool(kShellIconPref, true) || michael@0: !IsVistaOrLater()) { michael@0: shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n"; michael@0: const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s michael@0: totalLen = formatLen + asciiUrl.Length(); // don't include null character michael@0: } else { michael@0: nsCOMPtr icoFile; michael@0: nsCOMPtr aUri; michael@0: NS_NewURI(getter_AddRefs(aUri), url); michael@0: michael@0: nsAutoString aUriHash; michael@0: michael@0: mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash, mIOThread, true); michael@0: michael@0: nsresult rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true); michael@0: NS_ENSURE_SUCCESS(rv, E_FAIL); michael@0: rv = icoFile->GetNativePath(path); michael@0: NS_ENSURE_SUCCESS(rv, E_FAIL); michael@0: michael@0: shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n" michael@0: "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n" michael@0: "IconIndex=0\r\n"; michael@0: const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice michael@0: totalLen = formatLen + asciiUrl.Length() + michael@0: path.Length(); // we don't want a null character on the end michael@0: } michael@0: michael@0: // create a global memory area and build up the file contents w/in it michael@0: HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen); michael@0: if ( !hGlobalMemory ) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: char* contents = reinterpret_cast(::GlobalLock(hGlobalMemory)); michael@0: if ( !contents ) { michael@0: ::GlobalFree( hGlobalMemory ); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: //NOTE: we intentionally use the Microsoft version of snprintf here because it does NOT null michael@0: // terminate strings which reach the maximum size of the buffer. Since we know that the michael@0: // formatted length here is totalLen, this call to _snprintf will format the string into michael@0: // the buffer without appending the null character. michael@0: michael@0: if (!Preferences::GetBool(kShellIconPref, true)) { michael@0: _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get()); michael@0: } else { michael@0: _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(), path.get()); michael@0: } michael@0: michael@0: ::GlobalUnlock(hGlobalMemory); michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return S_OK; michael@0: } // GetFileContentsInternetShortcut michael@0: michael@0: // check if specified flavour is present in the transferable michael@0: bool nsDataObj :: IsFlavourPresent(const char *inFlavour) michael@0: { michael@0: bool retval = false; michael@0: NS_ENSURE_TRUE(mTransferable, false); michael@0: michael@0: // get the list of flavors available in the transferable michael@0: nsCOMPtr flavorList; michael@0: mTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); michael@0: NS_ENSURE_TRUE(flavorList, false); michael@0: michael@0: // try to find requested flavour michael@0: uint32_t cnt; michael@0: flavorList->Count(&cnt); michael@0: for (uint32_t i = 0; i < cnt; ++i) { michael@0: nsCOMPtr genericFlavor; michael@0: flavorList->GetElementAt (i, getter_AddRefs(genericFlavor)); michael@0: nsCOMPtr currentFlavor (do_QueryInterface(genericFlavor)); michael@0: if (currentFlavor) { michael@0: nsAutoCString flavorStr; michael@0: currentFlavor->GetData(flavorStr); michael@0: if (flavorStr.Equals(inFlavour)) { michael@0: retval = true; // found it! michael@0: break; michael@0: } michael@0: } michael@0: } // for each flavor michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: HRESULT nsDataObj::GetPreferredDropEffect ( FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: HRESULT res = S_OK; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: HGLOBAL hGlobalMemory = nullptr; michael@0: hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)); michael@0: if (hGlobalMemory) { michael@0: DWORD* pdw = (DWORD*) GlobalLock(hGlobalMemory); michael@0: // The PreferredDropEffect clipboard format is only registered if a drag/drop michael@0: // of an image happens from Mozilla to the desktop. We want its value michael@0: // to be DROPEFFECT_MOVE in that case so that the file is moved from the michael@0: // temporary location, not copied. michael@0: // This value should, ideally, be set on the data object via SetData() but michael@0: // our IDataObject implementation doesn't implement SetData. It adds data michael@0: // to the data object lazily only when the drop target asks for it. michael@0: *pdw = (DWORD) DROPEFFECT_MOVE; michael@0: GlobalUnlock(hGlobalMemory); michael@0: } michael@0: else { michael@0: res = E_OUTOFMEMORY; michael@0: } michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: return res; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::GetText(const nsACString & aDataFlavor, FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: void* data = nullptr; michael@0: uint32_t len; michael@0: michael@0: // if someone asks for text/plain, look up text/unicode instead in the transferable. michael@0: const char* flavorStr; michael@0: const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor); michael@0: if ( aDataFlavor.Equals("text/plain") ) michael@0: flavorStr = kUnicodeMime; michael@0: else michael@0: flavorStr = flat.get(); michael@0: michael@0: // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted michael@0: nsCOMPtr genericDataWrapper; michael@0: mTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &len); michael@0: if ( !len ) michael@0: return E_FAIL; michael@0: nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, len ); michael@0: if ( !data ) michael@0: return E_FAIL; michael@0: michael@0: HGLOBAL hGlobalMemory = nullptr; michael@0: michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: michael@0: // We play games under the hood and advertise flavors that we know we michael@0: // can support, only they require a bit of conversion or munging of the data. michael@0: // Do that here. michael@0: // michael@0: // The transferable gives us data that is null-terminated, but this isn't reflected in michael@0: // the |len| parameter. Windoze apps expect this null to be there so bump our data buffer michael@0: // by the appropriate size to account for the null (one char for CF_TEXT, one char16_t for michael@0: // CF_UNICODETEXT). michael@0: DWORD allocLen = (DWORD)len; michael@0: if ( aFE.cfFormat == CF_TEXT ) { michael@0: // Someone is asking for text/plain; convert the unicode (assuming it's present) michael@0: // to text with the correct platform encoding. michael@0: char* plainTextData = nullptr; michael@0: char16_t* castedUnicode = reinterpret_cast(data); michael@0: int32_t plainTextLen = 0; michael@0: nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText ( castedUnicode, len / 2, &plainTextData, &plainTextLen ); michael@0: michael@0: // replace the unicode data with our plaintext data. Recall that |plainTextLen| doesn't include michael@0: // the null in the length. michael@0: nsMemory::Free(data); michael@0: if ( plainTextData ) { michael@0: data = plainTextData; michael@0: allocLen = plainTextLen + sizeof(char); michael@0: } michael@0: else { michael@0: NS_WARNING ( "Oh no, couldn't convert unicode to plain text" ); michael@0: return S_OK; michael@0: } michael@0: } michael@0: else if ( aFE.cfFormat == nsClipboard::CF_HTML ) { michael@0: // Someone is asking for win32's HTML flavor. Convert our html fragment michael@0: // from unicode to UTF-8 then put it into a format specified by msft. michael@0: NS_ConvertUTF16toUTF8 converter ( reinterpret_cast(data) ); michael@0: char* utf8HTML = nullptr; michael@0: nsresult rv = BuildPlatformHTML ( converter.get(), &utf8HTML ); // null terminates michael@0: michael@0: nsMemory::Free(data); michael@0: if ( NS_SUCCEEDED(rv) && utf8HTML ) { michael@0: // replace the unicode data with our HTML data. Don't forget the null. michael@0: data = utf8HTML; michael@0: allocLen = strlen(utf8HTML) + sizeof(char); michael@0: } michael@0: else { michael@0: NS_WARNING ( "Oh no, couldn't convert to HTML" ); michael@0: return S_OK; michael@0: } michael@0: } michael@0: else { michael@0: // we assume that any data that isn't caught above is unicode. This may michael@0: // be an erroneous assumption, but is true so far. michael@0: allocLen += sizeof(char16_t); michael@0: } michael@0: michael@0: hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen); michael@0: michael@0: // Copy text to Global Memory Area michael@0: if ( hGlobalMemory ) { michael@0: char* dest = reinterpret_cast(GlobalLock(hGlobalMemory)); michael@0: char* source = reinterpret_cast(data); michael@0: memcpy ( dest, source, allocLen ); // copies the null as well michael@0: GlobalUnlock(hGlobalMemory); michael@0: } michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: michael@0: // Now, delete the memory that was created by CreateDataFromPrimitive (or our text/plain data) michael@0: nsMemory::Free(data); michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: uint32_t dfInx = 0; michael@0: ULONG count; michael@0: FORMATETC fe; michael@0: m_enumFE->Reset(); michael@0: while (NOERROR == m_enumFE->Next(1, &fe, &count) michael@0: && dfInx < mDataFlavors.Length()) { michael@0: if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime)) michael@0: return DropImage(aFE, aSTG); michael@0: if (mDataFlavors[dfInx].EqualsLiteral(kFileMime)) michael@0: return DropFile(aFE, aSTG); michael@0: if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime)) michael@0: return DropTempFile(aFE, aSTG); michael@0: dfInx++; michael@0: } michael@0: return E_FAIL; michael@0: } michael@0: michael@0: HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: nsresult rv; michael@0: uint32_t len = 0; michael@0: nsCOMPtr genericDataWrapper; michael@0: michael@0: mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper), michael@0: &len); michael@0: nsCOMPtr file ( do_QueryInterface(genericDataWrapper) ); michael@0: michael@0: if (!file) michael@0: { michael@0: nsCOMPtr ptr(do_QueryInterface(genericDataWrapper)); michael@0: if (ptr) { michael@0: nsCOMPtr supports; michael@0: ptr->GetData(getter_AddRefs(supports)); michael@0: file = do_QueryInterface(supports); michael@0: } michael@0: } michael@0: michael@0: if (!file) michael@0: return E_FAIL; michael@0: michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: michael@0: nsAutoString path; michael@0: rv = file->GetPath(path); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: michael@0: uint32_t allocLen = path.Length() + 2; michael@0: HGLOBAL hGlobalMemory = nullptr; michael@0: char16_t *dest; michael@0: michael@0: hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + michael@0: allocLen * sizeof(char16_t)); michael@0: if (!hGlobalMemory) michael@0: return E_FAIL; michael@0: michael@0: DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); michael@0: michael@0: // First, populate the drop file structure michael@0: pDropFile->pFiles = sizeof(DROPFILES); //Offset to start of file name string michael@0: pDropFile->fNC = 0; michael@0: pDropFile->pt.x = 0; michael@0: pDropFile->pt.y = 0; michael@0: pDropFile->fWide = TRUE; michael@0: michael@0: // Copy the filename right after the DROPFILES structure michael@0: dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); michael@0: memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); michael@0: michael@0: // Two null characters are needed at the end of the file name. michael@0: // Lookup the CF_HDROP shell clipboard format for more info. michael@0: // Add the second null character right after the first one. michael@0: dest[allocLen - 1] = L'\0'; michael@0: michael@0: GlobalUnlock(hGlobalMemory); michael@0: michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: nsresult rv; michael@0: if (!mCachedTempFile) { michael@0: uint32_t len = 0; michael@0: nsCOMPtr genericDataWrapper; michael@0: michael@0: mTransferable->GetTransferData(kNativeImageMime, getter_AddRefs(genericDataWrapper), &len); michael@0: nsCOMPtr image(do_QueryInterface(genericDataWrapper)); michael@0: michael@0: if (!image) { michael@0: // Check if the image was put in an nsISupportsInterfacePointer wrapper. michael@0: // This might not be necessary any more, but could be useful for backwards michael@0: // compatibility. michael@0: nsCOMPtr ptr(do_QueryInterface(genericDataWrapper)); michael@0: if (ptr) { michael@0: nsCOMPtr supports; michael@0: ptr->GetData(getter_AddRefs(supports)); michael@0: image = do_QueryInterface(supports); michael@0: } michael@0: } michael@0: michael@0: if (!image) michael@0: return E_FAIL; michael@0: michael@0: // Use the clipboard helper class to build up a memory bitmap. michael@0: nsImageToClipboard converter(image); michael@0: HANDLE bits = nullptr; michael@0: rv = converter.GetPicture(&bits); // Clipboard routines return a global handle we own. michael@0: michael@0: if (NS_FAILED(rv) || !bits) michael@0: return E_FAIL; michael@0: michael@0: // We now own these bits! michael@0: uint32_t bitmapSize = GlobalSize(bits); michael@0: if (!bitmapSize) { michael@0: GlobalFree(bits); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Save the bitmap to a temporary location. michael@0: nsCOMPtr dropFile; michael@0: rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); michael@0: if (!dropFile) { michael@0: GlobalFree(bits); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Filename must be random so as not to confuse apps like michael@0: // Photoshop which handle multiple drags into a single window. michael@0: char buf[13]; michael@0: nsCString filename; michael@0: NS_MakeRandomString(buf, 8); michael@0: memcpy(buf+8, ".bmp", 5); michael@0: filename.Append(nsDependentCString(buf, 12)); michael@0: dropFile->AppendNative(filename); michael@0: rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); michael@0: if (NS_FAILED(rv)) { michael@0: GlobalFree(bits); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Cache the temp file so we can delete it later and so michael@0: // it doesn't get recreated over and over on multiple calls michael@0: // which does occur from windows shell. michael@0: dropFile->Clone(getter_AddRefs(mCachedTempFile)); michael@0: michael@0: // Write the data to disk. michael@0: nsCOMPtr outStream; michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); michael@0: if (NS_FAILED(rv)) { michael@0: GlobalFree(bits); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: char * bm = (char *)GlobalLock(bits); michael@0: michael@0: BITMAPFILEHEADER fileHdr; michael@0: BITMAPINFOHEADER *bmpHdr = (BITMAPINFOHEADER*)bm; michael@0: michael@0: fileHdr.bfType = ((WORD) ('M' << 8) | 'B'); michael@0: fileHdr.bfSize = GlobalSize (bits) + sizeof(fileHdr); michael@0: fileHdr.bfReserved1 = 0; michael@0: fileHdr.bfReserved2 = 0; michael@0: fileHdr.bfOffBits = (DWORD) (sizeof(fileHdr) + bmpHdr->biSize); michael@0: michael@0: uint32_t writeCount = 0; michael@0: if (NS_FAILED(outStream->Write((const char *)&fileHdr, sizeof(fileHdr), &writeCount)) || michael@0: NS_FAILED(outStream->Write((const char *)bm, bitmapSize, &writeCount))) michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: outStream->Close(); michael@0: michael@0: GlobalUnlock(bits); michael@0: GlobalFree(bits); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: } michael@0: michael@0: // Pass the file name back to the drop target so that it can access the file. michael@0: nsAutoString path; michael@0: rv = mCachedTempFile->GetPath(path); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: michael@0: // Two null characters are needed to terminate the file name list. michael@0: HGLOBAL hGlobalMemory = nullptr; michael@0: michael@0: uint32_t allocLen = path.Length() + 2; michael@0: michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: michael@0: hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + allocLen * sizeof(char16_t)); michael@0: if (!hGlobalMemory) michael@0: return E_FAIL; michael@0: michael@0: DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); michael@0: michael@0: // First, populate the drop file structure. michael@0: pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name char array. michael@0: pDropFile->fNC = 0; michael@0: pDropFile->pt.x = 0; michael@0: pDropFile->pt.y = 0; michael@0: pDropFile->fWide = TRUE; michael@0: michael@0: // Copy the filename right after the DROPFILES structure. michael@0: char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); michael@0: memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); // Copies the null character in path as well. michael@0: michael@0: // Two null characters are needed at the end of the file name. michael@0: // Lookup the CF_HDROP shell clipboard format for more info. michael@0: // Add the second null character right after the first one. michael@0: dest[allocLen - 1] = L'\0'; michael@0: michael@0: GlobalUnlock(hGlobalMemory); michael@0: michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: nsresult rv; michael@0: if (!mCachedTempFile) { michael@0: // Tempfile will need a temporary location. michael@0: nsCOMPtr dropFile; michael@0: rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); michael@0: if (!dropFile) michael@0: return E_FAIL; michael@0: michael@0: // Filename must be random michael@0: nsCString filename; michael@0: nsAutoString wideFileName; michael@0: nsCOMPtr sourceURI; michael@0: HRESULT res; michael@0: res = GetDownloadDetails(getter_AddRefs(sourceURI), michael@0: wideFileName); michael@0: if (FAILED(res)) michael@0: return res; michael@0: NS_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, filename); michael@0: michael@0: dropFile->AppendNative(filename); michael@0: rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: michael@0: // Cache the temp file so we can delete it later and so michael@0: // it doesn't get recreated over and over on multiple calls michael@0: // which does occur from windows shell. michael@0: dropFile->Clone(getter_AddRefs(mCachedTempFile)); michael@0: michael@0: // Write the data to disk. michael@0: nsCOMPtr outStream; michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: michael@0: IStream *pStream = nullptr; michael@0: nsDataObj::CreateStream(&pStream); michael@0: NS_ENSURE_TRUE(pStream, E_FAIL); michael@0: michael@0: char buffer[512]; michael@0: ULONG readCount = 0; michael@0: uint32_t writeCount = 0; michael@0: while (1) { michael@0: HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount); michael@0: if (FAILED(hres)) michael@0: return E_FAIL; michael@0: if (readCount == 0) michael@0: break; michael@0: rv = outStream->Write(buffer, readCount, &writeCount); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: } michael@0: outStream->Close(); michael@0: pStream->Release(); michael@0: } michael@0: michael@0: // Pass the file name back to the drop target so that it can access the file. michael@0: nsAutoString path; michael@0: rv = mCachedTempFile->GetPath(path); michael@0: if (NS_FAILED(rv)) michael@0: return E_FAIL; michael@0: michael@0: uint32_t allocLen = path.Length() + 2; michael@0: michael@0: // Two null characters are needed to terminate the file name list. michael@0: HGLOBAL hGlobalMemory = nullptr; michael@0: michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: michael@0: hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + allocLen * sizeof(char16_t)); michael@0: if (!hGlobalMemory) michael@0: return E_FAIL; michael@0: michael@0: DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); michael@0: michael@0: // First, populate the drop file structure. michael@0: pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name char array. michael@0: pDropFile->fNC = 0; michael@0: pDropFile->pt.x = 0; michael@0: pDropFile->pt.y = 0; michael@0: pDropFile->fWide = TRUE; michael@0: michael@0: // Copy the filename right after the DROPFILES structure. michael@0: char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); michael@0: memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); // Copies the null character in path as well. michael@0: michael@0: // Two null characters are needed at the end of the file name. michael@0: // Lookup the CF_HDROP shell clipboard format for more info. michael@0: // Add the second null character right after the first one. michael@0: dest[allocLen - 1] = L'\0'; michael@0: michael@0: GlobalUnlock(hGlobalMemory); michael@0: michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::GetMetafilePict(FORMATETC&, STGMEDIUM&) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::SetBitmap(FORMATETC&, STGMEDIUM&) michael@0: { michael@0: return E_NOTIMPL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::SetDib(FORMATETC&, STGMEDIUM&) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::SetText (FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: HRESULT nsDataObj::SetMetafilePict(FORMATETC&, STGMEDIUM&) michael@0: { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: michael@0: michael@0: //----------------------------------------------------- michael@0: //----------------------------------------------------- michael@0: CLSID nsDataObj::GetClassID() const michael@0: { michael@0: return CLSID_nsDataObj; michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: // Registers the DataFlavor/FE pair. michael@0: //----------------------------------------------------- michael@0: void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) michael@0: { michael@0: // These two lists are the mapping to and from data flavors and FEs. michael@0: // Later, OLE will tell us it needs a certain type of FORMATETC (text, unicode, etc) michael@0: // unicode, etc), so we will look up the data flavor that corresponds to michael@0: // the FE and then ask the transferable for that type of data. michael@0: mDataFlavors.AppendElement(aDataFlavor); michael@0: m_enumFE->AddFormatEtc(aFE); michael@0: } michael@0: michael@0: //----------------------------------------------------- michael@0: // Sets the transferable object michael@0: //----------------------------------------------------- michael@0: void nsDataObj::SetTransferable(nsITransferable * aTransferable) michael@0: { michael@0: NS_IF_RELEASE(mTransferable); michael@0: michael@0: mTransferable = aTransferable; michael@0: if (nullptr == mTransferable) { michael@0: return; michael@0: } michael@0: michael@0: NS_ADDREF(mTransferable); michael@0: michael@0: return; michael@0: } michael@0: michael@0: michael@0: // michael@0: // ExtractURL michael@0: // michael@0: // Roots around in the transferable for the appropriate flavor that indicates michael@0: // a url and pulls out the url portion of the data. Used mostly for creating michael@0: // internet shortcuts on the desktop. The url flavor is of the format: michael@0: // michael@0: // michael@0: // michael@0: nsresult michael@0: nsDataObj :: ExtractShortcutURL ( nsString & outURL ) michael@0: { michael@0: NS_ASSERTION ( mTransferable, "We don't have a good transferable" ); michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: uint32_t len = 0; michael@0: nsCOMPtr genericURL; michael@0: if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) { michael@0: nsCOMPtr urlObject ( do_QueryInterface(genericURL) ); michael@0: if ( urlObject ) { michael@0: nsAutoString url; michael@0: urlObject->GetData ( url ); michael@0: outURL = url; michael@0: michael@0: // find the first linefeed in the data, that's where the url ends. trunc the michael@0: // result string at that point. michael@0: int32_t lineIndex = outURL.FindChar ( '\n' ); michael@0: NS_ASSERTION ( lineIndex > 0, "Format for url flavor is " ); michael@0: if ( lineIndex > 0 ) { michael@0: outURL.Truncate ( lineIndex ); michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: } else if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLDataMime, getter_AddRefs(genericURL), &len)) || michael@0: NS_SUCCEEDED(mTransferable->GetTransferData(kURLPrivateMime, getter_AddRefs(genericURL), &len)) ) { michael@0: nsCOMPtr urlObject ( do_QueryInterface(genericURL) ); michael@0: if ( urlObject ) { michael@0: nsAutoString url; michael@0: urlObject->GetData ( url ); michael@0: outURL = url; michael@0: michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: } // if found flavor michael@0: michael@0: return rv; michael@0: michael@0: } // ExtractShortcutURL michael@0: michael@0: michael@0: // michael@0: // ExtractShortcutTitle michael@0: // michael@0: // Roots around in the transferable for the appropriate flavor that indicates michael@0: // a url and pulls out the title portion of the data. Used mostly for creating michael@0: // internet shortcuts on the desktop. The url flavor is of the format: michael@0: // michael@0: // michael@0: // michael@0: nsresult michael@0: nsDataObj :: ExtractShortcutTitle ( nsString & outTitle ) michael@0: { michael@0: NS_ASSERTION ( mTransferable, "We'd don't have a good transferable" ); michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: uint32_t len = 0; michael@0: nsCOMPtr genericURL; michael@0: if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) { michael@0: nsCOMPtr urlObject ( do_QueryInterface(genericURL) ); michael@0: if ( urlObject ) { michael@0: nsAutoString url; michael@0: urlObject->GetData ( url ); michael@0: michael@0: // find the first linefeed in the data, that's where the url ends. we want michael@0: // everything after that linefeed. FindChar() returns -1 if we can't find michael@0: int32_t lineIndex = url.FindChar ( '\n' ); michael@0: NS_ASSERTION ( lineIndex != -1, "Format for url flavor is " ); michael@0: if ( lineIndex != -1 ) { michael@0: url.Mid ( outTitle, lineIndex + 1, (len/2) - (lineIndex + 1) ); michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: } // if found flavor michael@0: michael@0: return rv; michael@0: michael@0: } // ExtractShortcutTitle michael@0: michael@0: michael@0: // michael@0: // BuildPlatformHTML michael@0: // michael@0: // Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite michael@0: // header information on it. This will null terminate |outPlatformHTML|. See michael@0: // http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp michael@0: // for details. michael@0: // michael@0: // We assume that |inOurHTML| is already a fragment (ie, doesn't have michael@0: // or tags). We'll wrap the fragment with them to make other apps michael@0: // happy. michael@0: // michael@0: nsresult michael@0: nsDataObj :: BuildPlatformHTML ( const char* inOurHTML, char** outPlatformHTML ) michael@0: { michael@0: *outPlatformHTML = nullptr; michael@0: michael@0: nsDependentCString inHTMLString(inOurHTML); michael@0: const char* const numPlaceholder = "00000000"; michael@0: const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:"; michael@0: const char* const endHTMLPrefix = "\r\nEndHTML:"; michael@0: const char* const startFragPrefix = "\r\nStartFragment:"; michael@0: const char* const endFragPrefix = "\r\nEndFragment:"; michael@0: const char* const startSourceURLPrefix = "\r\nSourceURL:"; michael@0: const char* const endFragTrailer = "\r\n"; michael@0: michael@0: // Do we already have mSourceURL from a drag? michael@0: if (mSourceURL.IsEmpty()) { michael@0: nsAutoString url; michael@0: ExtractShortcutURL(url); michael@0: michael@0: AppendUTF16toUTF8(url, mSourceURL); michael@0: } michael@0: michael@0: const int32_t kSourceURLLength = mSourceURL.Length(); michael@0: const int32_t kNumberLength = strlen(numPlaceholder); michael@0: michael@0: const int32_t kTotalHeaderLen = strlen(startHTMLPrefix) + michael@0: strlen(endHTMLPrefix) + michael@0: strlen(startFragPrefix) + michael@0: strlen(endFragPrefix) + michael@0: strlen(endFragTrailer) + michael@0: (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) + michael@0: kSourceURLLength + michael@0: (4 * kNumberLength); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "\r\n"); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, ""); michael@0: michael@0: nsDependentCString trailingString( michael@0: "\r\n" michael@0: "\r\n" michael@0: ""); michael@0: michael@0: // calculate the offsets michael@0: int32_t startHTMLOffset = kTotalHeaderLen; michael@0: int32_t startFragOffset = startHTMLOffset michael@0: + htmlHeaderString.Length() michael@0: + fragmentHeaderString.Length(); michael@0: michael@0: int32_t endFragOffset = startFragOffset michael@0: + inHTMLString.Length(); michael@0: michael@0: int32_t endHTMLOffset = endFragOffset michael@0: + trailingString.Length(); michael@0: michael@0: // now build the final version michael@0: nsCString clipboardString; michael@0: clipboardString.SetCapacity(endHTMLOffset); michael@0: michael@0: clipboardString.Append(startHTMLPrefix); michael@0: clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset)); michael@0: michael@0: clipboardString.Append(endHTMLPrefix); michael@0: clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset)); michael@0: michael@0: clipboardString.Append(startFragPrefix); michael@0: clipboardString.Append(nsPrintfCString("%08u", startFragOffset)); michael@0: michael@0: clipboardString.Append(endFragPrefix); michael@0: clipboardString.Append(nsPrintfCString("%08u", endFragOffset)); michael@0: michael@0: if (kSourceURLLength > 0) { michael@0: clipboardString.Append(startSourceURLPrefix); michael@0: clipboardString.Append(mSourceURL); michael@0: } michael@0: michael@0: clipboardString.Append(endFragTrailer); michael@0: michael@0: clipboardString.Append(htmlHeaderString); michael@0: clipboardString.Append(fragmentHeaderString); michael@0: clipboardString.Append(inHTMLString); michael@0: clipboardString.Append(trailingString); michael@0: michael@0: *outPlatformHTML = ToNewCString(clipboardString); michael@0: if (!*outPlatformHTML) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: HRESULT michael@0: nsDataObj :: GetUniformResourceLocator( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) michael@0: { michael@0: HRESULT res = S_OK; michael@0: if (IsFlavourPresent(kURLMime)) { michael@0: if ( aIsUnicode ) michael@0: res = ExtractUniformResourceLocatorW( aFE, aSTG ); michael@0: else michael@0: res = ExtractUniformResourceLocatorA( aFE, aSTG ); michael@0: } michael@0: else michael@0: NS_WARNING ("Not yet implemented\n"); michael@0: return res; michael@0: } michael@0: michael@0: HRESULT michael@0: nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: HRESULT result = S_OK; michael@0: michael@0: nsAutoString url; michael@0: if (NS_FAILED(ExtractShortcutURL(url))) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: NS_LossyConvertUTF16toASCII asciiUrl(url); michael@0: const int totalLen = asciiUrl.Length() + 1; michael@0: HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE, totalLen); michael@0: if (!hGlobalMemory) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: char* contents = reinterpret_cast(GlobalLock(hGlobalMemory)); michael@0: if (!contents) { michael@0: GlobalFree(hGlobalMemory); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: strcpy(contents, asciiUrl.get()); michael@0: GlobalUnlock(hGlobalMemory); michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: HRESULT michael@0: nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG ) michael@0: { michael@0: HRESULT result = S_OK; michael@0: michael@0: nsAutoString url; michael@0: if (NS_FAILED(ExtractShortcutURL(url))) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: const int totalLen = (url.Length() + 1) * sizeof(char16_t); michael@0: HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE, totalLen); michael@0: if (!hGlobalMemory) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: wchar_t* contents = reinterpret_cast(GlobalLock(hGlobalMemory)); michael@0: if (!contents) { michael@0: GlobalFree(hGlobalMemory); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: wcscpy(contents, url.get()); michael@0: GlobalUnlock(hGlobalMemory); michael@0: aSTG.hGlobal = hGlobalMemory; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: // Gets the filename from the kFilePromiseURLMime flavour michael@0: HRESULT nsDataObj::GetDownloadDetails(nsIURI **aSourceURI, michael@0: nsAString &aFilename) michael@0: { michael@0: *aSourceURI = nullptr; michael@0: michael@0: NS_ENSURE_TRUE(mTransferable, E_FAIL); michael@0: michael@0: // get the URI from the kFilePromiseURLMime flavor michael@0: nsCOMPtr urlPrimitive; michael@0: uint32_t dataSize = 0; michael@0: mTransferable->GetTransferData(kFilePromiseURLMime, getter_AddRefs(urlPrimitive), &dataSize); michael@0: nsCOMPtr srcUrlPrimitive = do_QueryInterface(urlPrimitive); michael@0: NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL); michael@0: michael@0: nsAutoString srcUri; michael@0: srcUrlPrimitive->GetData(srcUri); michael@0: if (srcUri.IsEmpty()) michael@0: return E_FAIL; michael@0: nsCOMPtr sourceURI; michael@0: NS_NewURI(getter_AddRefs(sourceURI), srcUri); michael@0: michael@0: nsAutoString srcFileName; michael@0: nsCOMPtr fileNamePrimitive; michael@0: mTransferable->GetTransferData(kFilePromiseDestFilename, getter_AddRefs(fileNamePrimitive), &dataSize); michael@0: nsCOMPtr srcFileNamePrimitive = do_QueryInterface(fileNamePrimitive); michael@0: if (srcFileNamePrimitive) { michael@0: srcFileNamePrimitive->GetData(srcFileName); michael@0: } else { michael@0: nsCOMPtr sourceURL = do_QueryInterface(sourceURI); michael@0: if (!sourceURL) michael@0: return E_FAIL; michael@0: michael@0: nsAutoCString urlFileName; michael@0: sourceURL->GetFileName(urlFileName); michael@0: NS_UnescapeURL(urlFileName); michael@0: CopyUTF8toUTF16(urlFileName, srcFileName); michael@0: } michael@0: if (srcFileName.IsEmpty()) michael@0: return E_FAIL; michael@0: michael@0: // make the name safe for the filesystem michael@0: MangleTextToValidFilename(srcFileName); michael@0: michael@0: sourceURI.swap(*aSourceURI); michael@0: aFilename = srcFileName; michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); michael@0: NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY); michael@0: michael@0: LPFILEGROUPDESCRIPTORA fileGroupDescA = reinterpret_cast(GlobalLock(fileGroupDescHandle)); michael@0: if (!fileGroupDescA) { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: nsAutoString wideFileName; michael@0: HRESULT res; michael@0: nsCOMPtr sourceURI; michael@0: res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName); michael@0: if (FAILED(res)) michael@0: { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return res; michael@0: } michael@0: michael@0: nsAutoCString nativeFileName; michael@0: NS_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, nativeFileName); michael@0: michael@0: strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), NS_MAX_FILEDESCRIPTOR - 1); michael@0: fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0'; michael@0: michael@0: // one file in the file block michael@0: fileGroupDescA->cItems = 1; michael@0: fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI; michael@0: michael@0: GlobalUnlock( fileGroupDescHandle ); michael@0: aSTG.hGlobal = fileGroupDescHandle; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); michael@0: NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY); michael@0: michael@0: LPFILEGROUPDESCRIPTORW fileGroupDescW = reinterpret_cast(GlobalLock(fileGroupDescHandle)); michael@0: if (!fileGroupDescW) { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return E_OUTOFMEMORY; michael@0: } michael@0: michael@0: nsAutoString wideFileName; michael@0: HRESULT res; michael@0: nsCOMPtr sourceURI; michael@0: res = GetDownloadDetails(getter_AddRefs(sourceURI), michael@0: wideFileName); michael@0: if (FAILED(res)) michael@0: { michael@0: ::GlobalFree(fileGroupDescHandle); michael@0: return res; michael@0: } michael@0: michael@0: wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(), NS_MAX_FILEDESCRIPTOR - 1); michael@0: fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0'; michael@0: // one file in the file block michael@0: fileGroupDescW->cItems = 1; michael@0: fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI; michael@0: michael@0: GlobalUnlock(fileGroupDescHandle); michael@0: aSTG.hGlobal = fileGroupDescHandle; michael@0: aSTG.tymed = TYMED_HGLOBAL; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) michael@0: { michael@0: IStream *pStream = nullptr; michael@0: michael@0: nsDataObj::CreateStream(&pStream); michael@0: NS_ENSURE_TRUE(pStream, E_FAIL); michael@0: michael@0: aSTG.tymed = TYMED_ISTREAM; michael@0: aSTG.pstm = pStream; michael@0: aSTG.pUnkForRelease = nullptr; michael@0: michael@0: return S_OK; michael@0: } michael@0: michael@0: void nsDataObj::RemoveTempFile(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: nsDataObj *timedDataObj = static_cast(aClosure); michael@0: if (timedDataObj->mCachedTempFile) { michael@0: timedDataObj->mCachedTempFile->Remove(false); michael@0: timedDataObj->mCachedTempFile = nullptr; michael@0: } michael@0: timedDataObj->Release(); michael@0: }