1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <shlobj.h>
7 
8 #include "nsDataObjCollection.h"
9 #include "nsClipboard.h"
10 #include "IEnumFE.h"
11 
12 #include <ole2.h>
13 
14 // {25589C3E-1FAC-47b9-BF43-CAEA89B79533}
15 const IID IID_IDataObjCollection = {
16     0x25589c3e,
17     0x1fac,
18     0x47b9,
19     {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}};
20 
21 /*
22  * Class nsDataObjCollection
23  */
24 
nsDataObjCollection()25 nsDataObjCollection::nsDataObjCollection() {}
26 
~nsDataObjCollection()27 nsDataObjCollection::~nsDataObjCollection() { mDataObjects.Clear(); }
28 
29 // IUnknown interface methods - see iunknown.h for documentation
QueryInterface(REFIID riid,void ** ppv)30 STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) {
31   *ppv = nullptr;
32 
33   if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
34     *ppv = static_cast<IDataObject*>(this);
35     AddRef();
36     return NOERROR;
37   }
38 
39   if (IID_IDataObjCollection == riid) {
40     *ppv = static_cast<nsIDataObjCollection*>(this);
41     AddRef();
42     return NOERROR;
43   }
44   // offer to operate asynchronously (required by nsDragService)
45   if (IID_IDataObjectAsyncCapability == riid) {
46     *ppv = static_cast<IDataObjectAsyncCapability*>(this);
47     AddRef();
48     return NOERROR;
49   }
50 
51   return E_NOINTERFACE;
52 }
53 
STDMETHODIMP_(ULONG)54 STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() { return ++m_cRef; }
55 
STDMETHODIMP_(ULONG)56 STDMETHODIMP_(ULONG) nsDataObjCollection::Release() {
57   if (0 != --m_cRef) return m_cRef;
58 
59   delete this;
60 
61   return 0;
62 }
63 
64 // IDataObject methods
GetData(LPFORMATETC pFE,LPSTGMEDIUM pSTM)65 STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
66   static CLIPFORMAT fileDescriptorFlavorA =
67       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
68   static CLIPFORMAT fileDescriptorFlavorW =
69       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
70   static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
71 
72   switch (pFE->cfFormat) {
73     case CF_TEXT:
74     case CF_UNICODETEXT:
75       return GetText(pFE, pSTM);
76     case CF_HDROP:
77       return GetFile(pFE, pSTM);
78     default:
79       if (pFE->cfFormat == fileDescriptorFlavorA ||
80           pFE->cfFormat == fileDescriptorFlavorW) {
81         return GetFileDescriptors(pFE, pSTM);
82       }
83       if (pFE->cfFormat == fileFlavor) {
84         return GetFileContents(pFE, pSTM);
85       }
86   }
87   return GetFirstSupporting(pFE, pSTM);
88 }
89 
GetDataHere(LPFORMATETC pFE,LPSTGMEDIUM pSTM)90 STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE,
91                                               LPSTGMEDIUM pSTM) {
92   return E_FAIL;
93 }
94 
95 // Other objects querying to see if we support a particular format
QueryGetData(LPFORMATETC pFE)96 STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) {
97   UINT format = nsClipboard::GetFormat(MULTI_MIME);
98 
99   if (format == pFE->cfFormat) {
100     return S_OK;
101   }
102 
103   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
104     IDataObject* dataObj = mDataObjects.ElementAt(i);
105     if (S_OK == dataObj->QueryGetData(pFE)) {
106       return S_OK;
107     }
108   }
109 
110   return DV_E_FORMATETC;
111 }
112 
SetData(LPFORMATETC pFE,LPSTGMEDIUM pSTM,BOOL fRelease)113 STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
114                                           BOOL fRelease) {
115   // Set arbitrary data formats on the first object in the collection and let
116   // it handle the heavy lifting
117   if (mDataObjects.Length() == 0) return E_FAIL;
118   return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease);
119 }
120 
121 // Registers a DataFlavor/FE pair
AddDataFlavor(const char * aDataFlavor,LPFORMATETC aFE)122 void nsDataObjCollection::AddDataFlavor(const char* aDataFlavor,
123                                         LPFORMATETC aFE) {
124   // Add the FormatEtc to our list if it's not already there.  We don't care
125   // about the internal aDataFlavor because nsDataObj handles that.
126   IEnumFORMATETC* ifEtc;
127   FORMATETC fEtc;
128   ULONG num;
129   if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) return;
130   while (S_OK == ifEtc->Next(1, &fEtc, &num)) {
131     NS_ASSERTION(
132         1 == num,
133         "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor");
134     if (FormatsMatch(fEtc, *aFE)) {
135       ifEtc->Release();
136       return;
137     }
138   }  // If we didn't find a matching format, add this one
139   ifEtc->Release();
140   m_enumFE->AddFormatEtc(aFE);
141 }
142 
143 // We accept ownership of the nsDataObj which we free on destruction
AddDataObject(IDataObject * aDataObj)144 void nsDataObjCollection::AddDataObject(IDataObject* aDataObj) {
145   nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj);
146   mDataObjects.AppendElement(dataObj);
147 }
148 
149 // Methods for getting data
GetFile(LPFORMATETC pFE,LPSTGMEDIUM pSTM)150 HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
151   STGMEDIUM workingmedium;
152   FORMATETC fe = *pFE;
153   HGLOBAL hGlobalMemory;
154   HRESULT hr;
155   // Make enough space for the header and the trailing null
156   uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t);
157   uint32_t alloclen = 0;
158   char16_t* realbuffer;
159   nsAutoString filename;
160 
161   hGlobalMemory = GlobalAlloc(GHND, buffersize);
162 
163   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
164     nsDataObj* dataObj = mDataObjects.ElementAt(i);
165     hr = dataObj->GetData(&fe, &workingmedium);
166     if (hr != S_OK) {
167       switch (hr) {
168         case DV_E_FORMATETC:
169           continue;
170         default:
171           return hr;
172       }
173     }
174     // Now we need to pull out the filename
175     char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
176     if (buffer == nullptr) return E_FAIL;
177     buffer += sizeof(DROPFILES) / sizeof(char16_t);
178     filename = buffer;
179     GlobalUnlock(workingmedium.hGlobal);
180     ReleaseStgMedium(&workingmedium);
181     // Now put the filename into our buffer
182     alloclen = (filename.Length() + 1) * sizeof(char16_t);
183     hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
184     if (hGlobalMemory == nullptr) return E_FAIL;
185     realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
186     if (!realbuffer) return E_FAIL;
187     realbuffer--;  // Overwrite the preceding null
188     memcpy(realbuffer, filename.get(), alloclen);
189     GlobalUnlock(hGlobalMemory);
190     buffersize += alloclen;
191   }
192   // We get the last null (on the double null terminator) for free since we used
193   // the zero memory flag when we allocated.  All we need to do is fill the
194   // DROPFILES structure
195   DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory);
196   if (!df) return E_FAIL;
197   df->pFiles = sizeof(DROPFILES);  // Offset to start of file name string
198   df->fNC = 0;
199   df->pt.x = 0;
200   df->pt.y = 0;
201   df->fWide = TRUE;  // utf-16 chars
202   GlobalUnlock(hGlobalMemory);
203   // Finally fill out the STGMEDIUM struct
204   pSTM->tymed = TYMED_HGLOBAL;
205   pSTM->pUnkForRelease = nullptr;  // Caller gets to free the data
206   pSTM->hGlobal = hGlobalMemory;
207   return S_OK;
208 }
209 
GetText(LPFORMATETC pFE,LPSTGMEDIUM pSTM)210 HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
211   STGMEDIUM workingmedium;
212   FORMATETC fe = *pFE;
213   HGLOBAL hGlobalMemory;
214   HRESULT hr;
215   uint32_t buffersize = 1;
216   uint32_t alloclen = 0;
217 
218   hGlobalMemory = GlobalAlloc(GHND, buffersize);
219 
220   if (pFE->cfFormat == CF_TEXT) {
221     nsAutoCString text;
222     for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
223       nsDataObj* dataObj = mDataObjects.ElementAt(i);
224       hr = dataObj->GetData(&fe, &workingmedium);
225       if (hr != S_OK) {
226         switch (hr) {
227           case DV_E_FORMATETC:
228             continue;
229           default:
230             return hr;
231         }
232       }
233       // Now we need to pull out the text
234       char* buffer = (char*)GlobalLock(workingmedium.hGlobal);
235       if (buffer == nullptr) return E_FAIL;
236       text = buffer;
237       GlobalUnlock(workingmedium.hGlobal);
238       ReleaseStgMedium(&workingmedium);
239       // Now put the text into our buffer
240       alloclen = text.Length();
241       hGlobalMemory =
242           ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
243       if (hGlobalMemory == nullptr) return E_FAIL;
244       buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize);
245       if (!buffer) return E_FAIL;
246       buffer--;  // Overwrite the preceding null
247       memcpy(buffer, text.get(), alloclen);
248       GlobalUnlock(hGlobalMemory);
249       buffersize += alloclen;
250     }
251     pSTM->tymed = TYMED_HGLOBAL;
252     pSTM->pUnkForRelease = nullptr;  // Caller gets to free the data
253     pSTM->hGlobal = hGlobalMemory;
254     return S_OK;
255   }
256   if (pFE->cfFormat == CF_UNICODETEXT) {
257     buffersize = sizeof(char16_t);
258     nsAutoString text;
259     for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
260       nsDataObj* dataObj = mDataObjects.ElementAt(i);
261       hr = dataObj->GetData(&fe, &workingmedium);
262       if (hr != S_OK) {
263         switch (hr) {
264           case DV_E_FORMATETC:
265             continue;
266           default:
267             return hr;
268         }
269       }
270       // Now we need to pull out the text
271       char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
272       if (buffer == nullptr) return E_FAIL;
273       text = buffer;
274       GlobalUnlock(workingmedium.hGlobal);
275       ReleaseStgMedium(&workingmedium);
276       // Now put the text into our buffer
277       alloclen = text.Length() * sizeof(char16_t);
278       hGlobalMemory =
279           ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
280       if (hGlobalMemory == nullptr) return E_FAIL;
281       buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
282       if (!buffer) return E_FAIL;
283       buffer--;  // Overwrite the preceding null
284       memcpy(buffer, text.get(), alloclen);
285       GlobalUnlock(hGlobalMemory);
286       buffersize += alloclen;
287     }
288     pSTM->tymed = TYMED_HGLOBAL;
289     pSTM->pUnkForRelease = nullptr;  // Caller gets to free the data
290     pSTM->hGlobal = hGlobalMemory;
291     return S_OK;
292   }
293 
294   return E_FAIL;
295 }
296 
GetFileDescriptors(LPFORMATETC pFE,LPSTGMEDIUM pSTM)297 HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE,
298                                                 LPSTGMEDIUM pSTM) {
299   STGMEDIUM workingmedium;
300   FORMATETC fe = *pFE;
301   HGLOBAL hGlobalMemory;
302   HRESULT hr;
303   uint32_t buffersize = sizeof(UINT);
304   uint32_t alloclen = sizeof(FILEDESCRIPTOR);
305 
306   hGlobalMemory = GlobalAlloc(GHND, buffersize);
307 
308   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
309     nsDataObj* dataObj = mDataObjects.ElementAt(i);
310     hr = dataObj->GetData(&fe, &workingmedium);
311     if (hr != S_OK) {
312       switch (hr) {
313         case DV_E_FORMATETC:
314           continue;
315         default:
316           return hr;
317       }
318     }
319     // Now we need to pull out the filedescriptor
320     FILEDESCRIPTOR* buffer =
321         (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) +
322                           sizeof(UINT));
323     if (buffer == nullptr) return E_FAIL;
324     hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
325     if (hGlobalMemory == nullptr) return E_FAIL;
326     FILEGROUPDESCRIPTOR* realbuffer =
327         (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory);
328     if (!realbuffer) return E_FAIL;
329     FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize);
330     memcpy(copyloc, buffer, alloclen);
331     realbuffer->cItems++;
332     GlobalUnlock(hGlobalMemory);
333     GlobalUnlock(workingmedium.hGlobal);
334     ReleaseStgMedium(&workingmedium);
335     buffersize += alloclen;
336   }
337   pSTM->tymed = TYMED_HGLOBAL;
338   pSTM->pUnkForRelease = nullptr;  // Caller gets to free the data
339   pSTM->hGlobal = hGlobalMemory;
340   return S_OK;
341 }
342 
GetFileContents(LPFORMATETC pFE,LPSTGMEDIUM pSTM)343 HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE,
344                                              LPSTGMEDIUM pSTM) {
345   ULONG num = 0;
346   ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex;
347   FORMATETC fEtc = *pFE;
348   fEtc.lindex = -1;  // We're lying to the data object so it thinks it's alone
349 
350   // The key for this data type is to figure out which data object the index
351   // corresponds to and then just pass it along
352   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
353     nsDataObj* dataObj = mDataObjects.ElementAt(i);
354     if (dataObj->QueryGetData(&fEtc) != S_OK) continue;
355     if (num == numwanted) return dataObj->GetData(pFE, pSTM);
356     num++;
357   }
358   return DV_E_LINDEX;
359 }
360 
GetFirstSupporting(LPFORMATETC pFE,LPSTGMEDIUM pSTM)361 HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE,
362                                                 LPSTGMEDIUM pSTM) {
363   // There is no way to pass more than one of this, so just find the first data
364   // object that supports it and pass it along
365   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
366     if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK)
367       return mDataObjects.ElementAt(i)->GetData(pFE, pSTM);
368   }
369   return DV_E_FORMATETC;
370 }
371