/* -*- 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
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/ArrayUtils.h"
#include "mozilla/TextUtils.h"

#include <ole2.h>
#include <shlobj.h>

#include "nsDataObj.h"
#include "nsArrayUtils.h"
#include "nsClipboard.h"
#include "nsReadableUtils.h"
#include "nsITransferable.h"
#include "nsISupportsPrimitives.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/Services.h"
#include "mozilla/Unused.h"
#include "nsIOutputStream.h"
#include "nscore.h"
#include "nsDirectoryServiceDefs.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include "mozilla/Preferences.h"
#include "nsIContentPolicy.h"
#include "nsContentUtils.h"
#include "nsIPrincipal.h"
#include "nsNativeCharsetUtils.h"
#include "nsMimeTypes.h"
#include "imgITools.h"

#include "WinUtils.h"
#include "mozilla/LazyIdleThread.h"
#include <algorithm>

using namespace mozilla;
using namespace mozilla::widget;

#define BFH_LENGTH 14
#define DEFAULT_THREAD_TIMEOUT_MS 30000

NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)

//-----------------------------------------------------------------------------
// CStream implementation
nsDataObj::CStream::CStream() : mChannelRead(false), mStreamRead(0) {}

//-----------------------------------------------------------------------------
nsDataObj::CStream::~CStream() {}

//-----------------------------------------------------------------------------
// helper - initializes the stream
nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
                                  uint32_t aContentPolicyType,
                                  nsIPrincipal* aRequestingPrincipal) {
  // 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_DATA_INHERITS,
                     aContentPolicyType,
                     nullptr,  // nsICookieSettings
                     nullptr,  // PerformanceStorage
                     nullptr,  // loadGroup
                     nullptr,  // aCallbacks
                     nsIRequest::LOAD_FROM_CACHE);

  NS_ENSURE_SUCCESS(rv, 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
{
  // Extend the write buffer for the incoming data.
  uint8_t* buffer = mChannelData.AppendElements(aCount, fallible);
  if (!buffer) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  NS_ASSERTION((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.
  nsresult rv;
  uint32_t odaBytesReadTotal = 0;
  do {
    uint32_t bytesReadByCall = 0;
    rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), aCount,
                            &bytesReadByCall);
    odaBytesReadTotal += bytesReadByCall;
  } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv));
  return rv;
}

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([&]() { return mChannelRead; });

  if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;

  return mChannelResult;
}

//-----------------------------------------------------------------------------
// IStream
STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream) { return E_NOTIMPL; }

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags) { return E_NOTIMPL; }

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream,
                                        ULARGE_INTEGER nBytesToCopy,
                                        ULARGE_INTEGER* nBytesRead,
                                        ULARGE_INTEGER* nBytesWritten) {
  return E_NOTIMPL;
}

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::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::CStream::Revert(void) { return E_NOTIMPL; }

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::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::CStream::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.LowPart = (DWORD)mChannelData.Length();
  statstg->grfMode = STGM_READ;
  statstg->grfLocksSupported = LOCK_ONLYONCE;
  statstg->clsid = CLSID_NULL;

  return S_OK;
}

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart,
                                              ULARGE_INTEGER nBytes,
                                              DWORD dwFlags) {
  return E_NOTIMPL;
}

//-----------------------------------------------------------------------------
STDMETHODIMP nsDataObj::CStream::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 requestingPrincipal from the transferable and add it to the new
  // channel
  nsCOMPtr<nsIPrincipal> requestingPrincipal =
      mTransferable->GetRequestingPrincipal();
  MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");

  uint32_t contentPolicyType = mTransferable->GetContentPolicyType();
  rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal);
  if (NS_FAILED(rv)) {
    pStream->Release();
    return E_FAIL;
  }
  *outStream = pStream;

  return S_OK;
}

/*
 * deliberately not using MAX_PATH. This is because on platforms < XP
 * a file created with a long filename may be mishandled by the shell
 * resulting in it not being able to be deleted or moved.
 * See bug 250392 for more details.
 */
#define NS_MAX_FILEDESCRIPTOR 128 + 1

/*
 * Class nsDataObj
 */

//-----------------------------------------------------
// construction
//-----------------------------------------------------
nsDataObj::nsDataObj(nsIURI* uri)
    : m_cRef(0),
      mTransferable(nullptr),
      mIsAsyncMode(FALSE),
      mIsInOperation(FALSE) {
  mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
                                 NS_LITERAL_CSTRING("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() {
  NS_IF_RELEASE(mTransferable);

  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));
  return m_cRef;
}

namespace {
class RemoveTempFileHelper final : public nsIObserver {
 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

 private:
  ~RemoveTempFileHelper() {
    if (mTempFile) {
      mTempFile->Remove(false);
    }
  }

  nsCOMPtr<nsIFile> mTempFile;
  nsCOMPtr<nsITimer> mTimer;
};

NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver);

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;
}
}  // namespace

//-----------------------------------------------------
STDMETHODIMP_(ULONG) nsDataObj::Release() {
  --m_cRef;

  NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
  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();
  }

  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;

  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);

  // 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& df = mDataFlavors.ElementAt(dfInx);
    if (FormatsMatch(fe, *aFormat)) {
      pSTM->pUnkForRelease =
          nullptr;  // caller is responsible for deleting this data
      CLIPFORMAT format = aFormat->cfFormat;
      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);

        default:
          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);
      }  // switch
    }    // 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) {
  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(NS_LITERAL_STRING("bpp=32;"));
  if (aFormat.cfFormat == CF_DIBV5) {
    options.AppendLiteral("version=5");
  } else {
    options.AppendLiteral("version=3");
  }

  nsCOMPtr<nsIInputStream> inputStream;
  nsresult rv = imgTools->EncodeImage(image, NS_LITERAL_CSTRING(IMAGE_BMP),
                                      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;
  }

  // We don't want the file header.
  src += BFH_LENGTH;
  size -= BFH_LENGTH;

  HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
  if (!glob) {
    return E_FAIL;
  }

  char* dst = (char*)::GlobalLock(glob);
  ::CopyMemory(dst, src, size);
  ::GlobalUnlock(glob);

  aSTG.hGlobal = glob;
  aSTG.tymed = TYMED_HGLOBAL;
  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

//
// Given a unicode string, we ensure that it contains only characters which are
// valid within the file system. Remove all forbidden characters from the name,
// and completely disallow any title that starts with a forbidden name and
// extension (e.g. "nul" is invalid, but "nul." and "nul.txt" are also invalid
// and will cause problems).
//
// It would seem that this is more functionality suited to being in nsIFile.
//
static void MangleTextToValidFilename(nsString& aText) {
  static const char* forbiddenNames[] = {
      "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",  "COM8",
      "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6",  "LPT7",
      "LPT8", "LPT9", "CON",  "PRN",  "AUX",  "NUL",  "CLOCK$"};

  aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
  aText.CompressWhitespace(true, true);
  uint32_t nameLen;
  for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
    nameLen = (uint32_t)strlen(forbiddenNames[n]);
    if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
      // invalid name is either the entire string, or a prefix with a period
      if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
        aText.Truncate();
        break;
      }
    }
  }
}

//
// Given a unicode string, convert it down to a valid local charset filename
// with the supplied extension. 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 CreateFilenameFromTextA(nsString& aText, const char* aExtension,
                                    char* aFilename, uint32_t aFilenameLen) {
  // ensure that the supplied name doesn't have invalid characters. If
  // a valid mangled filename couldn't be created then it will leave the
  // text empty.
  MangleTextToValidFilename(aText);
  if (aText.IsEmpty()) return false;

  // repeatably call WideCharToMultiByte as long as the title doesn't fit in the
  // buffer available to us. Continually reduce the length of the source title
  // until the MBCS version will fit in the buffer with room for the supplied
  // extension. Doing it this way ensures that even in MBCS environments there
  // will be a valid MBCS filename of the correct length.
  int maxUsableFilenameLen =
      aFilenameLen - strlen(aExtension) - 1;  // space for ext + null byte
  int currLen, textLen = (int)std::min(aText.Length(), aFilenameLen);
  char defaultChar = '_';
  do {
    currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
                                  aText.get(), textLen--, aFilename,
                                  maxUsableFilenameLen, &defaultChar, nullptr);
  } while (currLen == 0 && textLen > 0 &&
           GetLastError() == ERROR_INSUFFICIENT_BUFFER);
  if (currLen > 0 && textLen > 0) {
    strcpy(&aFilename[currLen], aExtension);
    return true;
  } else {
    // empty names aren't permitted
    return false;
  }
}

static bool CreateFilenameFromTextW(nsString& aText, const wchar_t* aExtension,
                                    wchar_t* aFilename, uint32_t aFilenameLen) {
  // ensure that the supplied name doesn't have invalid characters. If
  // a valid mangled filename couldn't be created then it will leave the
  // text empty.
  MangleTextToValidFilename(aText);
  if (aText.IsEmpty()) return false;

  const int extensionLen = wcslen(aExtension);
  if (aText.Length() + extensionLen + 1 > aFilenameLen)
    aText.Truncate(aFilenameLen - extensionLen - 1);
  wcscpy(&aFilename[0], aText.get());
  wcscpy(&aFilename[aText.Length()], aExtension);
  return true;
}

#define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"

static bool GetLocalizedString(const char* aName, nsAString& aString) {
  nsCOMPtr<nsIStringBundleService> stringService =
      mozilla::services::GetStringBundleService();
  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 (!CreateFilenameFromTextA(title, ".URL", fileGroupDescA->fgd[0].cFileName,
                               NS_MAX_FILEDESCRIPTOR)) {
    nsAutoString untitled;
    if (!GetLocalizedString("noPageTitle", untitled) ||
        !CreateFilenameFromTextA(untitled, ".URL",
                                 fileGroupDescA->fgd[0].cFileName,
                                 NS_MAX_FILEDESCRIPTOR)) {
      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 (!CreateFilenameFromTextW(title, L".URL", fileGroupDescW->fgd[0].cFileName,
                               NS_MAX_FILEDESCRIPTOR)) {
    nsAutoString untitled;
    if (!GetLocalizedString("noPageTitle", untitled) ||
        !CreateFilenameFromTextW(untitled, L".URL",
                                 fileGroupDescW->fgd[0].cFileName,
                                 NS_MAX_FILEDESCRIPTOR)) {
      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;
  }

  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 {
    nsCOMPtr<nsIFile> icoFile;

    nsAutoString aUriHash;

    mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash,
                                                         mIOThread, true);

    rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
    NS_ENSURE_SUCCESS(rv, E_FAIL);
    nsString path;
    rv = icoFile->GetPath(path);
    NS_ENSURE_SUCCESS(rv, E_FAIL);

    if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
      LossyCopyUTF16toASCII(path, asciiPath);
      shortcutFormatStr =
          "[InternetShortcut]\r\nURL=%s\r\n"
          "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
          "IconIndex=0\r\n";
    } else {
      int len =
          WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
                              path.Length(), nullptr, 0, nullptr, nullptr);
      NS_ENSURE_TRUE(len > 0, E_FAIL);
      asciiPath.SetLength(len);
      WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
                          path.Length(), asciiPath.BeginWriting(), len, nullptr,
                          nullptr);
      shortcutFormatStr =
          "[InternetShortcut]\r\nURL=%s\r\n"
          "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
          "[InternetShortcut.W]\r\nIconFile=%s\r\n";
    }
    const int formatLen = strlen(shortcutFormatStr) - 2 * 2;  // no %s twice
    totalLen = formatLen + asciiUrl.Length() +
               asciiPath.Length();  // we don't want a null character on the end
  }

  // create a global memory area and build up the file contents w/in it
  HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen);
  if (!hGlobalMemory) return E_OUTOFMEMORY;

  char* contents = reinterpret_cast<char*>(::GlobalLock(hGlobalMemory));
  if (!contents) {
    ::GlobalFree(hGlobalMemory);
    return E_OUTOFMEMORY;
  }

  // NOTE: we intentionally use the Microsoft version of snprintf here because
  // it does NOT null
  // terminate strings which reach the maximum size of the buffer. Since we know
  // that the formatted length here is totalLen, this call to _snprintf will
  // format the string into the buffer without appending the null character.

  if (!Preferences::GetBool(kShellIconPref, true)) {
    _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
  } else {
    _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
              asciiPath.get());
  }

  ::GlobalUnlock(hGlobalMemory);
  aSTG.hGlobal = hGlobalMemory;
  aSTG.tymed = TYMED_HGLOBAL;

  return S_OK;
}  // GetFileContentsInternetShortcut

// check if specified flavour is present in the transferable
bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
  bool retval = false;
  NS_ENSURE_TRUE(mTransferable, false);

  // get the list of flavors available in the transferable
  nsTArray<nsCString> flavors;
  nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
  NS_ENSURE_SUCCESS(rv, false);

  // try to find requested flavour
  for (uint32_t i = 0; i < flavors.Length(); ++i) {
    if (flavors[i].Equals(inFlavour)) {
      retval = true;  // found it!
      break;
    }
  }  // for each flavor

  return retval;
}

HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
  HRESULT res = S_OK;
  aSTG.tymed = TYMED_HGLOBAL;
  aSTG.pUnkForRelease = nullptr;
  HGLOBAL hGlobalMemory = nullptr;
  hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
  if (hGlobalMemory) {
    DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
    // The PreferredDropEffect clipboard format is only registered if a
    // drag/drop of an image happens from Mozilla to the desktop.  We want its
    // value to be DROPEFFECT_MOVE in that case so that the file is moved from
    // the temporary location, not copied. This value should, ideally, be set on
    // the data object via SetData() but our IDataObject implementation doesn't
    // implement SetData.  It adds data to the data object lazily only when the
    // drop target asks for it.
    *pdw = (DWORD)DROPEFFECT_MOVE;
    GlobalUnlock(hGlobalMemory);
  } else {
    res = E_OUTOFMEMORY;
  }
  aSTG.hGlobal = hGlobalMemory;
  return res;
}

//-----------------------------------------------------
HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
                           STGMEDIUM& aSTG) {
  void* data = nullptr;

  // if someone asks for text/plain, look up text/unicode instead in the
  // transferable.
  const char* flavorStr;
  const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor);
  if (aDataFlavor.EqualsLiteral("text/plain"))
    flavorStr = kUnicodeMime;
  else
    flavorStr = flat.get();

  // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
  nsCOMPtr<nsISupports> genericDataWrapper;
  nsresult rv = mTransferable->GetTransferData(
      flavorStr, getter_AddRefs(genericDataWrapper));
  if (NS_FAILED(rv) || !genericDataWrapper) {
    return E_FAIL;
  }

  uint32_t len;
  nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(flavorStr),
                                              genericDataWrapper, &data, &len);
  if (!data) return E_FAIL;

  HGLOBAL hGlobalMemory = nullptr;

  aSTG.tymed = TYMED_HGLOBAL;
  aSTG.pUnkForRelease = nullptr;

  // We play games under the hood and advertise flavors that we know we
  // can support, only they require a bit of conversion or munging of the data.
  // Do that here.
  //
  // The transferable gives us data that is null-terminated, but this isn't
  // reflected in the |len| parameter. Windoze apps expect this null to be there
  // so bump our data buffer by the appropriate size to account for the null
  // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
  DWORD allocLen = (DWORD)len;
  if (aFE.cfFormat == CF_TEXT) {
    // Someone is asking for text/plain; convert the unicode (assuming it's
    // present) to text with the correct platform encoding.
    size_t bufferSize = sizeof(char) * (len + 2);
    char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
    char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
    int32_t plainTextLen =
        WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
                            plainTextData, bufferSize, NULL, NULL);
    // replace the unicode data with our plaintext data. Recall that
    // |plainTextLen| doesn't include the null in the length.
    free(data);
    if (plainTextLen) {
      data = plainTextData;
      allocLen = plainTextLen;
    } else {
      free(plainTextData);
      NS_WARNING("Oh no, couldn't convert unicode to plain text");
      return S_OK;
    }
  } else if (aFE.cfFormat == nsClipboard::CF_HTML) {
    // Someone is asking for win32's HTML flavor. Convert our html fragment
    // from unicode to UTF-8 then put it into a format specified by msft.
    NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
    char* utf8HTML = nullptr;
    nsresult rv =
        BuildPlatformHTML(converter.get(), &utf8HTML);  // null terminates

    free(data);
    if (NS_SUCCEEDED(rv) && utf8HTML) {
      // replace the unicode data with our HTML data. Don't forget the null.
      data = utf8HTML;
      allocLen = strlen(utf8HTML) + sizeof(char);
    } else {
      NS_WARNING("Oh no, couldn't convert to HTML");
      return S_OK;
    }
  } else if (aFE.cfFormat != nsClipboard::CF_CUSTOMTYPES) {
    // we assume that any data that isn't caught above is unicode. This may
    // be an erroneous assumption, but is true so far.
    allocLen += sizeof(char16_t);
  }

  hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);

  // Copy text to Global Memory Area
  if (hGlobalMemory) {
    char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
    char* source = reinterpret_cast<char*>(data);
    memcpy(dest, source, allocLen);  // copies the null as well
    GlobalUnlock(hGlobalMemory);
  }
  aSTG.hGlobal = hGlobalMemory;

  // Now, delete the memory that was created by CreateDataFromPrimitive (or our
  // text/plain data)
  free(data);

  return S_OK;
}

//-----------------------------------------------------
HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
  uint32_t dfInx = 0;
  ULONG count;
  FORMATETC fe;
  m_enumFE->Reset();
  while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
         dfInx < mDataFlavors.Length()) {
    if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
      return DropImage(aFE, aSTG);
    if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
      return DropFile(aFE, aSTG);
    if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
      return DropTempFile(aFE, aSTG);
    dfInx++;
  }
  return E_FAIL;
}

HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
  nsresult rv;
  nsCOMPtr<nsISupports> genericDataWrapper;

  if (NS_FAILED(mTransferable->GetTransferData(
          kFileMime, getter_AddRefs(genericDataWrapper)))) {
    return E_FAIL;
  }
  nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
  if (!file) return E_FAIL;

  aSTG.tymed = TYMED_HGLOBAL;
  aSTG.pUnkForRelease = nullptr;

  nsAutoString path;
  rv = file->GetPath(path);
  if (NS_FAILED(rv)) return E_FAIL;

  uint32_t allocLen = path.Length() + 2;
  HGLOBAL hGlobalMemory = nullptr;
  char16_t* dest;

  hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
                              sizeof(DROPFILES) + allocLen * sizeof(char16_t));
  if (!hGlobalMemory) return E_FAIL;

  DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);

  // First, populate the drop file structure
  pDropFile->pFiles = sizeof(DROPFILES);  // Offset to start of file name string
  pDropFile->fNC = 0;
  pDropFile->pt.x = 0;
  pDropFile->pt.y = 0;
  pDropFile->fWide = TRUE;

  // Copy the filename right after the DROPFILES structure
  dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
  memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));

  // Two null characters are needed at the end of the file name.
  // Lookup the CF_HDROP shell clipboard format for more info.
  // Add the second null character right after the first one.
  dest[allocLen - 1] = L'\0';

  GlobalUnlock(hGlobalMemory);

  aSTG.hGlobal = hGlobalMemory;

  return S_OK;
}

HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
  nsresult rv;
  if (!mCachedTempFile) {
    nsCOMPtr<nsISupports> genericDataWrapper;

    if (NS_FAILED(mTransferable->GetTransferData(
            kNativeImageMime, 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");
    nsCOMPtr<nsIInputStream> inputStream;
    rv = imgTools->EncodeImage(image, NS_LITERAL_CSTRING(IMAGE_BMP),
                               NS_LITERAL_STRING("bpp=32;version=3"),
                               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)) {
      return E_FAIL;
    }

    char* src = nullptr;
    rv = encoder->GetImageBuffer(&src);
    if (NS_FAILED(rv) || !src) {
      return E_FAIL;
    }

    // Save the bitmap to a temporary location.
    nsCOMPtr<nsIFile> dropFile;
    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
    if (!dropFile) {
      return E_FAIL;
    }

    // Filename must be random so as not to confuse apps like
    // Photoshop which handle multiple drags into a single window.
    char buf[13];
    nsCString filename;
    NS_MakeRandomString(buf, 8);
    memcpy(buf + 8, ".bmp", 5);
    filename.Append(nsDependentCString(buf, 12));
    dropFile->AppendNative(filename);
    rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
    if (NS_FAILED(rv)) {
      return E_FAIL;
    }

    // Cache the temp file so we can delete it later and so
    // it doesn't get recreated over and over on multiple calls
    // which does occur from windows shell.
    dropFile->Clone(getter_AddRefs(mCachedTempFile));

    // Write the data to disk.
    nsCOMPtr<nsIOutputStream> outStream;
    rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
    if (NS_FAILED(rv)) {
      return E_FAIL;
    }

    uint32_t written = 0;
    rv = outStream->Write(src, size, &written);
    if (NS_FAILED(rv) || written != size) {
      return E_FAIL;
    }

    outStream->Close();
  }

  // Pass the file name back to the drop target so that it can access the file.
  nsAutoString path;
  rv = mCachedTempFile->GetPath(path);
  if (NS_FAILED(rv)) return E_FAIL;

  // Two null characters are needed to terminate the file name list.
  HGLOBAL hGlobalMemory = nullptr;

  uint32_t allocLen = path.Length() + 2;

  aSTG.tymed = TYMED_HGLOBAL;
  aSTG.pUnkForRelease = nullptr;

  hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
                              sizeof(DROPFILES) + allocLen * sizeof(char16_t));
  if (!hGlobalMemory) return E_FAIL;

  DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);

  // First, populate the drop file structure.
  pDropFile->pFiles =
      sizeof(DROPFILES);  // Offset to start of file name char array.
  pDropFile->fNC = 0;
  pDropFile->pt.x = 0;
  pDropFile->pt.y = 0;
  pDropFile->fWide = TRUE;

  // Copy the filename right after the DROPFILES structure.
  char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
  memcpy(dest, path.get(),
         (allocLen - 1) *
             sizeof(char16_t));  // Copies the null character in path as well.

  // Two null characters are needed at the end of the file name.
  // Lookup the CF_HDROP shell clipboard format for more info.
  // Add the second null character right after the first one.
  dest[allocLen - 1] = L'\0';

  GlobalUnlock(hGlobalMemory);

  aSTG.hGlobal = hGlobalMemory;

  return S_OK;
}

HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
  nsresult rv;
  if (!mCachedTempFile) {
    // Tempfile will need a temporary location.
    nsCOMPtr<nsIFile> dropFile;
    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
    if (!dropFile) return E_FAIL;

    // Filename must be random
    nsCString filename;
    nsAutoString wideFileName;
    nsCOMPtr<nsIURI> sourceURI;
    HRESULT res;
    res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
    if (FAILED(res)) return res;
    NS_CopyUnicodeToNative(wideFileName, filename);

    dropFile->AppendNative(filename);
    rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
    if (NS_FAILED(rv)) return E_FAIL;

    // Cache the temp file so we can delete it later and so
    // it doesn't get recreated over and over on multiple calls
    // which does occur from windows shell.
    dropFile->Clone(getter_AddRefs(mCachedTempFile));

    // Write the data to disk.
    nsCOMPtr<nsIOutputStream> outStream;
    rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
    if (NS_FAILED(rv)) return E_FAIL;

    IStream* pStream = nullptr;
    nsDataObj::CreateStream(&pStream);
    NS_ENSURE_TRUE(pStream, E_FAIL);

    char buffer[512];
    ULONG readCount = 0;
    uint32_t writeCount = 0;
    while (1) {
      HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
      if (FAILED(hres)) return E_FAIL;
      if (readCount == 0) break;
      rv = outStream->Write(buffer, readCount, &writeCount);
      if (NS_FAILED(rv)) return E_FAIL;
    }
    outStream->Close();
    pStream->Release();
  }

  // Pass the file name back to the drop target so that it can access the file.
  nsAutoString path;
  rv = mCachedTempFile->GetPath(path);
  if (NS_FAILED(rv)) return E_FAIL;

  uint32_t allocLen = path.Length() + 2;

  // Two null characters are needed to terminate the file name list.
  HGLOBAL hGlobalMemory = nullptr;

  aSTG.tymed = TYMED_HGLOBAL;
  aSTG.pUnkForRelease = nullptr;

  hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
                              sizeof(DROPFILES) + allocLen * sizeof(char16_t));
  if (!hGlobalMemory) return E_FAIL;

  DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);

  // First, populate the drop file structure.
  pDropFile->pFiles =
      sizeof(DROPFILES);  // Offset to start of file name char array.
  pDropFile->fNC = 0;
  pDropFile->pt.x = 0;
  pDropFile->pt.y = 0;
  pDropFile->fWide = TRUE;

  // Copy the filename right after the DROPFILES structure.
  char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
  memcpy(dest, path.get(),
         (allocLen - 1) *
             sizeof(char16_t));  // Copies the null character in path as well.

  // Two null characters are needed at the end of the file name.
  // Lookup the CF_HDROP shell clipboard format for more info.
  // Add the second null character right after the first one.
  dest[allocLen - 1] = L'\0';

  GlobalUnlock(hGlobalMemory);

  aSTG.hGlobal = hGlobalMemory;

  return S_OK;
}

//-----------------------------------------------------
// Registers the DataFlavor/FE pair.
//-----------------------------------------------------
void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
  // These two lists are the mapping to and from data flavors and FEs.
  // Later, OLE will tell us it needs a certain type of FORMATETC (text,
  // unicode, etc) unicode, etc), so we will look up the data flavor that
  // corresponds to the FE and then ask the transferable for that type of data.
  mDataFlavors.AppendElement(aDataFlavor);
  m_enumFE->AddFormatEtc(aFE);
}

//-----------------------------------------------------
// Sets the transferable object
//-----------------------------------------------------
void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
  NS_IF_RELEASE(mTransferable);

  mTransferable = aTransferable;
  if (nullptr == mTransferable) {
    return;
  }

  NS_ADDREF(mTransferable);

  return;
}

//
// ExtractURL
//
// Roots around in the transferable for the appropriate flavor that indicates
// a url and pulls out the url portion of the data. Used mostly for creating
// internet shortcuts on the desktop. The url flavor is of the format:
//
//   <url> <linefeed> <page title>
//
nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
  NS_ASSERTION(mTransferable, "We don't have a good transferable");
  nsresult rv = NS_ERROR_FAILURE;

  nsCOMPtr<nsISupports> genericURL;
  if (NS_SUCCEEDED(mTransferable->GetTransferData(
          kURLMime, getter_AddRefs(genericURL)))) {
    nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
    if (urlObject) {
      nsAutoString url;
      urlObject->GetData(url);
      outURL = url;

      // find the first linefeed in the data, that's where the url ends. trunc
      // the result string at that point.
      int32_t lineIndex = outURL.FindChar('\n');
      NS_ASSERTION(lineIndex > 0,
                   "Format for url flavor is <url> <linefeed> <page title>");
      if (lineIndex > 0) {
        outURL.Truncate(lineIndex);
        rv = NS_OK;
      }
    }
  } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
                 kURLDataMime, getter_AddRefs(genericURL))) ||
             NS_SUCCEEDED(mTransferable->GetTransferData(
                 kURLPrivateMime, getter_AddRefs(genericURL)))) {
    nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
    if (urlObject) {
      nsAutoString url;
      urlObject->GetData(url);
      outURL = url;

      rv = NS_OK;
    }

  }  // if found flavor

  return rv;

}  // ExtractShortcutURL

//
// ExtractShortcutTitle
//
// Roots around in the transferable for the appropriate flavor that indicates
// a url and pulls out the title portion of the data. Used mostly for creating
// internet shortcuts on the desktop. The url flavor is of the format:
//
//   <url> <linefeed> <page title>
//
nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
  NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
  nsresult rv = NS_ERROR_FAILURE;

  nsCOMPtr<nsISupports> genericURL;
  if (NS_SUCCEEDED(mTransferable->GetTransferData(
          kURLMime, getter_AddRefs(genericURL)))) {
    nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
    if (urlObject) {
      nsAutoString url;
      urlObject->GetData(url);

      // find the first linefeed in the data, that's where the url ends. we want
      // everything after that linefeed. FindChar() returns -1 if we can't find
      int32_t lineIndex = url.FindChar('\n');
      NS_ASSERTION(lineIndex != -1,
                   "Format for url flavor is <url> <linefeed> <page title>");
      if (lineIndex != -1) {
        url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
        rv = NS_OK;
      }
    }
  }  // if found flavor

  return rv;

}  // ExtractShortcutTitle

//
// BuildPlatformHTML
//
// Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
// header information on it. This will null terminate |outPlatformHTML|. See
//  http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
// for details.
//
// We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
// or <BODY> tags). We'll wrap the fragment with them to make other apps
// happy.
//
nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
                                       char** outPlatformHTML) {
  *outPlatformHTML = nullptr;

  nsDependentCString inHTMLString(inOurHTML);
  const char* const numPlaceholder = "00000000";
  const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:";
  const char* const endHTMLPrefix = "\r\nEndHTML:";
  const char* const startFragPrefix = "\r\nStartFragment:";
  const char* const endFragPrefix = "\r\nEndFragment:";
  const char* const startSourceURLPrefix = "\r\nSourceURL:";
  const char* const endFragTrailer = "\r\n";

  // Do we already have mSourceURL from a drag?
  if (mSourceURL.IsEmpty()) {
    nsAutoString url;
    ExtractShortcutURL(url);

    AppendUTF16toUTF8(url, mSourceURL);
  }

  const int32_t kSourceURLLength = mSourceURL.Length();
  const int32_t kNumberLength = strlen(numPlaceholder);

  const int32_t kTotalHeaderLen =
      strlen(startHTMLPrefix) + strlen(endHTMLPrefix) +
      strlen(startFragPrefix) + strlen(endFragPrefix) + strlen(endFragTrailer) +
      (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) +
      kSourceURLLength + (4 * kNumberLength);

  NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "<html><body>\r\n");

  NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, "<!--StartFragment-->");

  nsDependentCString trailingString(
      "<!--EndFragment-->\r\n"
      "</body>\r\n"
      "</html>");

  // calculate the offsets
  int32_t startHTMLOffset = kTotalHeaderLen;
  int32_t startFragOffset = startHTMLOffset + htmlHeaderString.Length() +
                            fragmentHeaderString.Length();

  int32_t endFragOffset = startFragOffset + inHTMLString.Length();

  int32_t endHTMLOffset = endFragOffset + trailingString.Length();

  // now build the final version
  nsCString clipboardString;
  clipboardString.SetCapacity(endHTMLOffset);

  clipboardString.Append(startHTMLPrefix);
  clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset));

  clipboardString.Append(endHTMLPrefix);
  clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset));

  clipboardString.Append(startFragPrefix);
  clipboardString.Append(nsPrintfCString("%08u", startFragOffset));

  clipboardString.Append(endFragPrefix);
  clipboardString.Append(nsPrintfCString("%08u", endFragOffset));

  if (kSourceURLLength > 0) {
    clipboardString.Append(startSourceURLPrefix);
    clipboardString.Append(mSourceURL);
  }

  clipboardString.Append(endFragTrailer);

  clipboardString.Append(htmlHeaderString);
  clipboardString.Append(fragmentHeaderString);
  clipboardString.Append(inHTMLString);
  clipboardString.Append(trailingString);

  *outPlatformHTML = ToNewCString(clipboardString);
  if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;

  return NS_OK;
}

HRESULT
nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
                                      bool aIsUnicode) {
  HRESULT res = S_OK;
  if (IsFlavourPresent(kURLMime)) {
    if (aIsUnicode)
      res = ExtractUniformResourceLocatorW(aFE, aSTG);
    else
      res = ExtractUniformResourceLocatorA(aFE, aSTG);
  } else
    NS_WARNING("Not yet implemented\n");
  return res;
}

HRESULT
nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
  HRESULT result = S_OK;

  nsAutoString url;
  if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;

  NS_LossyConvertUTF16toASCII asciiUrl(url);
  const int totalLen = asciiUrl.Length() + 1;
  HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
  if (!hGlobalMemory) return E_OUTOFMEMORY;

  char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
  if (!contents) {
    GlobalFree(hGlobalMemory);
    return E_OUTOFMEMORY;
  }

  strcpy(contents, asciiUrl.get());
  GlobalUnlock(hGlobalMemory);
  aSTG.hGlobal = hGlobalMemory;
  aSTG.tymed = TYMED_HGLOBAL;

  return result;
}

HRESULT
nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
  HRESULT result = S_OK;

  nsAutoString url;
  if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;

  const int totalLen = (url.Length() + 1) * sizeof(char16_t);
  HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
  if (!hGlobalMemory) return E_OUTOFMEMORY;

  wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
  if (!contents) {
    GlobalFree(hGlobalMemory);
    return E_OUTOFMEMORY;
  }

  wcscpy(contents, url.get());
  GlobalUnlock(hGlobalMemory);
  aSTG.hGlobal = hGlobalMemory;
  aSTG.tymed = TYMED_HGLOBAL;

  return result;
}

// Gets the filename from the kFilePromiseURLMime flavour
HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
                                      nsAString& aFilename) {
  *aSourceURI = nullptr;

  NS_ENSURE_TRUE(mTransferable, E_FAIL);

  // get the URI from the kFilePromiseURLMime flavor
  nsCOMPtr<nsISupports> urlPrimitive;
  nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
                                               getter_AddRefs(urlPrimitive));
  NS_ENSURE_SUCCESS(rv, E_FAIL);
  nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
  NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);

  nsAutoString srcUri;
  srcUrlPrimitive->GetData(srcUri);
  if (srcUri.IsEmpty()) return E_FAIL;
  nsCOMPtr<nsIURI> sourceURI;
  NS_NewURI(getter_AddRefs(sourceURI), srcUri);

  nsAutoString srcFileName;
  nsCOMPtr<nsISupports> fileNamePrimitive;
  Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
                                           getter_AddRefs(fileNamePrimitive));
  nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
      do_QueryInterface(fileNamePrimitive);
  if (srcFileNamePrimitive) {
    srcFileNamePrimitive->GetData(srcFileName);
  } else {
    nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
    if (!sourceURL) return E_FAIL;

    nsAutoCString urlFileName;
    sourceURL->GetFileName(urlFileName);
    NS_UnescapeURL(urlFileName);
    CopyUTF8toUTF16(urlFileName, srcFileName);
  }
  if (srcFileName.IsEmpty()) return E_FAIL;

  // make the name safe for the filesystem
  MangleTextToValidFilename(srcFileName);

  sourceURI.swap(*aSourceURI);
  aFilename = srcFileName;
  return S_OK;
}

HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
  HGLOBAL fileGroupDescHandle =
      ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
  NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);

  LPFILEGROUPDESCRIPTORA fileGroupDescA =
      reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
  if (!fileGroupDescA) {
    ::GlobalFree(fileGroupDescHandle);
    return E_OUTOFMEMORY;
  }

  nsAutoString wideFileName;
  HRESULT res;
  nsCOMPtr<nsIURI> sourceURI;
  res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
  if (FAILED(res)) {
    ::GlobalFree(fileGroupDescHandle);
    return res;
  }

  nsAutoCString nativeFileName;
  NS_CopyUnicodeToNative(wideFileName, nativeFileName);

  strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(),
          NS_MAX_FILEDESCRIPTOR - 1);
  fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';

  // one file in the file block
  fileGroupDescA->cItems = 1;
  fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;

  GlobalUnlock(fileGroupDescHandle);
  aSTG.hGlobal = fileGroupDescHandle;
  aSTG.tymed = TYMED_HGLOBAL;

  return S_OK;
}

HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
  HGLOBAL fileGroupDescHandle =
      ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
  NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);

  LPFILEGROUPDESCRIPTORW fileGroupDescW =
      reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
  if (!fileGroupDescW) {
    ::GlobalFree(fileGroupDescHandle);
    return E_OUTOFMEMORY;
  }

  nsAutoString wideFileName;
  HRESULT res;
  nsCOMPtr<nsIURI> sourceURI;
  res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
  if (FAILED(res)) {
    ::GlobalFree(fileGroupDescHandle);
    return res;
  }

  wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(),
          NS_MAX_FILEDESCRIPTOR - 1);
  fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0';
  // one file in the file block
  fileGroupDescW->cItems = 1;
  fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;

  GlobalUnlock(fileGroupDescHandle);
  aSTG.hGlobal = fileGroupDescHandle;
  aSTG.tymed = TYMED_HGLOBAL;

  return S_OK;
}

HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
  IStream* pStream = nullptr;

  nsDataObj::CreateStream(&pStream);
  NS_ENSURE_TRUE(pStream, E_FAIL);

  aSTG.tymed = TYMED_ISTREAM;
  aSTG.pstm = pStream;
  aSTG.pUnkForRelease = nullptr;

  return S_OK;
}
