1/*
2 * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ClipboardWin.h"
28
29#include "CachedImage.h"
30#include "ClipboardUtilitiesWin.h"
31#include "Document.h"
32#include "DragData.h"
33#include "Editor.h"
34#include "Element.h"
35#include "EventHandler.h"
36#include "FileList.h"
37#include "Frame.h"
38#include "FrameLoader.h"
39#include "FrameView.h"
40#include "HTMLNames.h"
41#include "HTMLParserIdioms.h"
42#include "Image.h"
43#include "MIMETypeRegistry.h"
44#include "NotImplemented.h"
45#include "Page.h"
46#include "Pasteboard.h"
47#include "PlatformMouseEvent.h"
48#include "PlatformString.h"
49#include "Range.h"
50#include "RenderImage.h"
51#include "ResourceResponse.h"
52#include "SharedBuffer.h"
53#include "WCDataObject.h"
54#include "markup.h"
55#include <shlwapi.h>
56#include <wininet.h>
57#include <wtf/RefPtr.h>
58#include <wtf/text/CString.h>
59#include <wtf/text/StringConcatenate.h>
60#include <wtf/text/StringHash.h>
61
62using namespace std;
63
64namespace WebCore {
65
66using namespace HTMLNames;
67
68// We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
69// see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
70
71enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText, ClipboardDataTypeTextHTML };
72
73static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
74{
75    String qType = type.stripWhiteSpace().lower();
76
77    // two special cases for IE compatibility
78    if (qType == "text" || qType == "text/plain" || qType.startsWith("text/plain;"))
79        return ClipboardDataTypeText;
80    if (qType == "url" || qType == "text/uri-list")
81        return ClipboardDataTypeURL;
82    if (qType == "text/html")
83        return ClipboardDataTypeTextHTML;
84
85    return ClipboardDataTypeNone;
86}
87
88static inline FORMATETC* fileDescriptorFormat()
89{
90    static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
91    static FORMATETC fileDescriptorFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
92    return &fileDescriptorFormat;
93}
94
95static inline FORMATETC* fileContentFormatZero()
96{
97    static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS);
98    static FORMATETC fileContentFormat = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
99    return &fileContentFormat;
100}
101
102#if !OS(WINCE)
103static inline void pathRemoveBadFSCharacters(PWSTR psz, size_t length)
104{
105    size_t writeTo = 0;
106    size_t readFrom = 0;
107    while (readFrom < length) {
108        UINT type = PathGetCharType(psz[readFrom]);
109        if (!psz[readFrom] || type & (GCT_LFNCHAR | GCT_SHORTCHAR))
110            psz[writeTo++] = psz[readFrom];
111
112        readFrom++;
113    }
114    psz[writeTo] = 0;
115}
116#endif
117
118static String filesystemPathFromUrlOrTitle(const String& url, const String& title, const UChar* extension, bool isLink)
119{
120#if OS(WINCE)
121    notImplemented();
122    return String();
123#else
124    static const size_t fsPathMaxLengthExcludingNullTerminator = MAX_PATH - 1;
125    bool usedURL = false;
126    WCHAR fsPathBuffer[MAX_PATH];
127    fsPathBuffer[0] = 0;
128    int extensionLen = extension ? lstrlen(extension) : 0;
129    int fsPathMaxLengthExcludingExtension = fsPathMaxLengthExcludingNullTerminator - extensionLen;
130
131    if (!title.isEmpty()) {
132        size_t len = min<size_t>(title.length(), fsPathMaxLengthExcludingExtension);
133        CopyMemory(fsPathBuffer, title.characters(), len * sizeof(UChar));
134        fsPathBuffer[len] = 0;
135        pathRemoveBadFSCharacters(fsPathBuffer, len);
136    }
137
138    if (!lstrlen(fsPathBuffer)) {
139        KURL kurl(ParsedURLString, url);
140        usedURL = true;
141        // The filename for any content based drag or file url should be the last element of
142        // the path.  If we can't find it, or we're coming up with the name for a link
143        // we just use the entire url.
144        DWORD len = fsPathMaxLengthExcludingExtension;
145        String lastComponent = kurl.lastPathComponent();
146        if (kurl.isLocalFile() || (!isLink && !lastComponent.isEmpty())) {
147            len = min<DWORD>(fsPathMaxLengthExcludingExtension, lastComponent.length());
148            CopyMemory(fsPathBuffer, lastComponent.characters(), len * sizeof(UChar));
149        } else {
150            len = min<DWORD>(fsPathMaxLengthExcludingExtension, url.length());
151            CopyMemory(fsPathBuffer, url.characters(), len * sizeof(UChar));
152        }
153        fsPathBuffer[len] = 0;
154        pathRemoveBadFSCharacters(fsPathBuffer, len);
155    }
156
157    if (!extension)
158        return String(static_cast<UChar*>(fsPathBuffer));
159
160    if (!isLink && usedURL) {
161        PathRenameExtension(fsPathBuffer, extension);
162        return String(static_cast<UChar*>(fsPathBuffer));
163    }
164
165    String result(static_cast<UChar*>(fsPathBuffer));
166    result += String(extension);
167    return result;
168#endif
169}
170
171static HGLOBAL createGlobalImageFileContent(SharedBuffer* data)
172{
173    HGLOBAL memObj = GlobalAlloc(GPTR, data->size());
174    if (!memObj)
175        return 0;
176
177    char* fileContents = (PSTR)GlobalLock(memObj);
178
179    CopyMemory(fileContents, data->data(), data->size());
180
181    GlobalUnlock(memObj);
182
183    return memObj;
184}
185
186static HGLOBAL createGlobalHDropContent(const KURL& url, String& fileName, SharedBuffer* data)
187{
188    if (fileName.isEmpty() || !data)
189        return 0;
190
191    WCHAR filePath[MAX_PATH];
192
193    if (url.isLocalFile()) {
194        String localPath = decodeURLEscapeSequences(url.path());
195        // windows does not enjoy a leading slash on paths
196        if (localPath[0] == '/')
197            localPath = localPath.substring(1);
198        LPCWSTR localPathStr = localPath.charactersWithNullTermination();
199        if (wcslen(localPathStr) + 1 < MAX_PATH)
200            wcscpy_s(filePath, MAX_PATH, localPathStr);
201        else
202            return 0;
203    } else {
204#if OS(WINCE)
205        notImplemented();
206        return 0;
207#else
208        WCHAR tempPath[MAX_PATH];
209        WCHAR extension[MAX_PATH];
210        if (!::GetTempPath(WTF_ARRAY_LENGTH(tempPath), tempPath))
211            return 0;
212        if (!::PathAppend(tempPath, fileName.charactersWithNullTermination()))
213            return 0;
214        LPCWSTR foundExtension = ::PathFindExtension(tempPath);
215        if (foundExtension) {
216            if (wcscpy_s(extension, MAX_PATH, foundExtension))
217                return 0;
218        } else
219            *extension = 0;
220        ::PathRemoveExtension(tempPath);
221        for (int i = 1; i < 10000; i++) {
222            if (swprintf_s(filePath, MAX_PATH, TEXT("%s-%d%s"), tempPath, i, extension) == -1)
223                return 0;
224            if (!::PathFileExists(filePath))
225                break;
226        }
227        HANDLE tempFileHandle = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
228        if (tempFileHandle == INVALID_HANDLE_VALUE)
229            return 0;
230
231        // Write the data to this temp file.
232        DWORD written;
233        BOOL tempWriteSucceeded = WriteFile(tempFileHandle, data->data(), data->size(), &written, 0);
234        CloseHandle(tempFileHandle);
235        if (!tempWriteSucceeded)
236            return 0;
237#endif
238    }
239
240    SIZE_T dropFilesSize = sizeof(DROPFILES) + (sizeof(WCHAR) * (wcslen(filePath) + 2));
241    HGLOBAL memObj = GlobalAlloc(GHND | GMEM_SHARE, dropFilesSize);
242    if (!memObj)
243        return 0;
244
245    DROPFILES* dropFiles = (DROPFILES*) GlobalLock(memObj);
246    dropFiles->pFiles = sizeof(DROPFILES);
247    dropFiles->fWide = TRUE;
248    wcscpy((LPWSTR)(dropFiles + 1), filePath);
249    GlobalUnlock(memObj);
250
251    return memObj;
252}
253
254static HGLOBAL createGlobalImageFileDescriptor(const String& url, const String& title, CachedImage* image)
255{
256    ASSERT_ARG(image, image);
257    ASSERT(image->image()->data());
258
259    HRESULT hr = S_OK;
260    HGLOBAL memObj = 0;
261    String fsPath;
262    memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
263    if (!memObj)
264        return 0;
265
266    FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj);
267    memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR));
268    fgd->cItems = 1;
269    fgd->fgd[0].dwFlags = FD_FILESIZE;
270    fgd->fgd[0].nFileSizeLow = image->image()->data()->size();
271
272    const String& preferredTitle = title.isEmpty() ? image->response().suggestedFilename() : title;
273    String extension = image->image()->filenameExtension();
274    if (extension.isEmpty()) {
275        // Do not continue processing in the rare and unusual case where a decoded image is not able
276        // to provide a filename extension. Something tricky (like a bait-n-switch) is going on
277        return 0;
278    }
279    extension.insert(".", 0);
280    fsPath = filesystemPathFromUrlOrTitle(url, preferredTitle, extension.charactersWithNullTermination(), false);
281
282    if (fsPath.length() <= 0) {
283        GlobalUnlock(memObj);
284        GlobalFree(memObj);
285        return 0;
286    }
287
288    int maxSize = min(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
289    CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar));
290    GlobalUnlock(memObj);
291
292    return memObj;
293}
294
295
296// writeFileToDataObject takes ownership of fileDescriptor and fileContent
297static HRESULT writeFileToDataObject(IDataObject* dataObject, HGLOBAL fileDescriptor, HGLOBAL fileContent, HGLOBAL hDropContent)
298{
299    HRESULT hr = S_OK;
300    FORMATETC* fe;
301    STGMEDIUM medium = {0};
302    medium.tymed = TYMED_HGLOBAL;
303
304    if (!fileDescriptor || !fileContent)
305        goto exit;
306
307    // Descriptor
308    fe = fileDescriptorFormat();
309
310    medium.hGlobal = fileDescriptor;
311
312    if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
313        goto exit;
314
315    // Contents
316    fe = fileContentFormatZero();
317    medium.hGlobal = fileContent;
318    if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
319        goto exit;
320
321#if USE(CF)
322    // HDROP
323    if (hDropContent) {
324        medium.hGlobal = hDropContent;
325        hr = dataObject->SetData(cfHDropFormat(), &medium, TRUE);
326    }
327#endif
328
329exit:
330    if (FAILED(hr)) {
331        if (fileDescriptor)
332            GlobalFree(fileDescriptor);
333        if (fileContent)
334            GlobalFree(fileContent);
335        if (hDropContent)
336            GlobalFree(hDropContent);
337    }
338    return hr;
339}
340
341PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
342{
343    if (dragData->platformData())
344        return ClipboardWin::create(DragAndDrop, dragData->platformData(), policy, frame);
345    return ClipboardWin::create(DragAndDrop, dragData->dragDataMap(), policy, frame);
346}
347
348ClipboardWin::ClipboardWin(ClipboardType clipboardType, IDataObject* dataObject, ClipboardAccessPolicy policy, Frame* frame)
349    : Clipboard(policy, clipboardType)
350    , m_dataObject(dataObject)
351    , m_writableDataObject(0)
352    , m_frame(frame)
353{
354}
355
356ClipboardWin::ClipboardWin(ClipboardType clipboardType, WCDataObject* dataObject, ClipboardAccessPolicy policy, Frame* frame)
357    : Clipboard(policy, clipboardType)
358    , m_dataObject(dataObject)
359    , m_writableDataObject(dataObject)
360    , m_frame(frame)
361{
362}
363
364ClipboardWin::ClipboardWin(ClipboardType clipboardType, const DragDataMap& dataMap, ClipboardAccessPolicy policy, Frame* frame)
365    : Clipboard(policy, clipboardType)
366    , m_dataObject(0)
367    , m_writableDataObject(0)
368    , m_frame(frame)
369    , m_dragDataMap(dataMap)
370{
371}
372
373ClipboardWin::~ClipboardWin()
374{
375}
376
377static bool writeURL(WCDataObject *data, const KURL& url, String title, bool withPlainText, bool withHTML)
378{
379    ASSERT(data);
380
381    if (url.isEmpty())
382        return false;
383
384    if (title.isEmpty()) {
385        title = url.lastPathComponent();
386        if (title.isEmpty())
387            title = url.host();
388    }
389
390    STGMEDIUM medium = {0};
391    medium.tymed = TYMED_HGLOBAL;
392
393    medium.hGlobal = createGlobalData(url, title);
394    bool success = false;
395    if (medium.hGlobal && FAILED(data->SetData(urlWFormat(), &medium, TRUE)))
396        ::GlobalFree(medium.hGlobal);
397    else
398        success = true;
399
400    if (withHTML) {
401        Vector<char> cfhtmlData;
402        markupToCFHTML(urlToMarkup(url, title), "", cfhtmlData);
403        medium.hGlobal = createGlobalData(cfhtmlData);
404        if (medium.hGlobal && FAILED(data->SetData(htmlFormat(), &medium, TRUE)))
405            ::GlobalFree(medium.hGlobal);
406        else
407            success = true;
408    }
409
410    if (withPlainText) {
411        medium.hGlobal = createGlobalData(url.string());
412        if (medium.hGlobal && FAILED(data->SetData(plainTextWFormat(), &medium, TRUE)))
413            ::GlobalFree(medium.hGlobal);
414        else
415            success = true;
416    }
417
418    return success;
419}
420
421void ClipboardWin::clearData(const String& type)
422{
423    // FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
424    ASSERT(isForDragAndDrop());
425    if (policy() != ClipboardWritable || !m_writableDataObject)
426        return;
427
428    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
429
430    if (dataType == ClipboardDataTypeURL) {
431        m_writableDataObject->clearData(urlWFormat()->cfFormat);
432        m_writableDataObject->clearData(urlFormat()->cfFormat);
433    }
434    if (dataType == ClipboardDataTypeText) {
435        m_writableDataObject->clearData(plainTextFormat()->cfFormat);
436        m_writableDataObject->clearData(plainTextWFormat()->cfFormat);
437    }
438
439}
440
441void ClipboardWin::clearAllData()
442{
443    // FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
444    ASSERT(isForDragAndDrop());
445    if (policy() != ClipboardWritable)
446        return;
447
448    m_writableDataObject = 0;
449    WCDataObject::createInstance(&m_writableDataObject);
450    m_dataObject = m_writableDataObject;
451}
452
453String ClipboardWin::getData(const String& type, bool& success) const
454{
455    success = false;
456    if (policy() != ClipboardReadable || (!m_dataObject && m_dragDataMap.isEmpty()))
457        return "";
458
459    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
460    if (dataType == ClipboardDataTypeText)
461        return m_dataObject ? getPlainText(m_dataObject.get(), success) : getPlainText(&m_dragDataMap);
462    if (dataType == ClipboardDataTypeURL)
463        return m_dataObject ? getURL(m_dataObject.get(), DragData::DoNotConvertFilenames, success) : getURL(&m_dragDataMap, DragData::DoNotConvertFilenames);
464    else if (dataType == ClipboardDataTypeTextHTML) {
465        String data = m_dataObject ? getTextHTML(m_dataObject.get(), success) : getTextHTML(&m_dragDataMap);
466        if (success)
467            return data;
468        return m_dataObject ? getCFHTML(m_dataObject.get(), success) : getCFHTML(&m_dragDataMap);
469    }
470
471    return "";
472}
473
474bool ClipboardWin::setData(const String& type, const String& data)
475{
476    // FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
477    ASSERT(isForDragAndDrop());
478    if (policy() != ClipboardWritable || !m_writableDataObject)
479        return false;
480
481    ClipboardDataType winType = clipboardTypeFromMIMEType(type);
482
483    if (winType == ClipboardDataTypeURL)
484        return WebCore::writeURL(m_writableDataObject.get(), KURL(ParsedURLString, data), String(), false, true);
485
486    if (winType == ClipboardDataTypeText) {
487        STGMEDIUM medium = {0};
488        medium.tymed = TYMED_HGLOBAL;
489        medium.hGlobal = createGlobalData(data);
490        if (!medium.hGlobal)
491            return false;
492
493        if (FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE))) {
494            ::GlobalFree(medium.hGlobal);
495            return false;
496        }
497        return true;
498    }
499
500    return false;
501}
502
503static void addMimeTypesForFormat(HashSet<String>& results, const FORMATETC& format)
504{
505    // URL and Text are provided for compatibility with IE's model
506    if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat) {
507        results.add("URL");
508        results.add("text/uri-list");
509    }
510
511    if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat) {
512        results.add("Text");
513        results.add("text/plain");
514    }
515}
516
517// extensions beyond IE's API
518HashSet<String> ClipboardWin::types() const
519{
520    HashSet<String> results;
521    if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
522        return results;
523
524    if (!m_dataObject && m_dragDataMap.isEmpty())
525        return results;
526
527    if (m_dataObject) {
528        COMPtr<IEnumFORMATETC> itr;
529
530        if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
531            return results;
532
533        if (!itr)
534            return results;
535
536        FORMATETC data;
537
538        // IEnumFORMATETC::Next returns S_FALSE if there are no more items.
539        while (itr->Next(1, &data, 0) == S_OK)
540            addMimeTypesForFormat(results, data);
541    } else {
542        for (DragDataMap::const_iterator it = m_dragDataMap.begin(); it != m_dragDataMap.end(); ++it) {
543            FORMATETC data;
544            data.cfFormat = (*it).first;
545            addMimeTypesForFormat(results, data);
546        }
547    }
548
549    return results;
550}
551
552PassRefPtr<FileList> ClipboardWin::files() const
553{
554#if OS(WINCE)
555    notImplemented();
556    return 0;
557#else
558    RefPtr<FileList> files = FileList::create();
559    if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
560        return files.release();
561
562    if (!m_dataObject && m_dragDataMap.isEmpty())
563        return files.release();
564
565    if (m_dataObject) {
566        STGMEDIUM medium;
567        if (FAILED(m_dataObject->GetData(cfHDropFormat(), &medium)))
568            return files.release();
569
570        HDROP hdrop = reinterpret_cast<HDROP>(GlobalLock(medium.hGlobal));
571        if (!hdrop)
572            return files.release();
573
574        WCHAR filename[MAX_PATH];
575        UINT fileCount = DragQueryFileW(hdrop, 0xFFFFFFFF, 0, 0);
576        for (UINT i = 0; i < fileCount; i++) {
577            if (!DragQueryFileW(hdrop, i, filename, WTF_ARRAY_LENGTH(filename)))
578                continue;
579            files->append(File::create(reinterpret_cast<UChar*>(filename)));
580        }
581
582        GlobalUnlock(medium.hGlobal);
583        ReleaseStgMedium(&medium);
584        return files.release();
585    }
586    if (!m_dragDataMap.contains(cfHDropFormat()->cfFormat))
587        return files.release();
588    Vector<String> filesVector = m_dragDataMap.get(cfHDropFormat()->cfFormat);
589    for (Vector<String>::iterator it = filesVector.begin(); it != filesVector.end(); ++it)
590        files->append(File::create((*it).characters()));
591    return files.release();
592#endif
593}
594
595void ClipboardWin::setDragImage(CachedImage* image, Node *node, const IntPoint &loc)
596{
597    if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
598        return;
599
600    if (m_dragImage)
601        m_dragImage->removeClient(this);
602    m_dragImage = image;
603    if (m_dragImage)
604        m_dragImage->addClient(this);
605
606    m_dragLoc = loc;
607    m_dragImageElement = node;
608}
609
610void ClipboardWin::setDragImage(CachedImage* img, const IntPoint &loc)
611{
612    setDragImage(img, 0, loc);
613}
614
615void ClipboardWin::setDragImageElement(Node *node, const IntPoint &loc)
616{
617    setDragImage(0, node, loc);
618}
619
620DragImageRef ClipboardWin::createDragImage(IntPoint& loc) const
621{
622    HBITMAP result = 0;
623    if (m_dragImage) {
624        result = createDragImageFromImage(m_dragImage->image());
625        loc = m_dragLoc;
626    } else if (m_dragImageElement) {
627        Node* node = m_dragImageElement.get();
628        result = node->document()->frame()->nodeImage(node);
629        loc = m_dragLoc;
630    }
631    return result;
632}
633
634static String imageToMarkup(const String& url)
635{
636    String markup("<img src=\"");
637    markup.append(url);
638    markup.append("\"/>");
639    return markup;
640}
641
642static CachedImage* getCachedImage(Element* element)
643{
644    // Attempt to pull CachedImage from element
645    ASSERT(element);
646    RenderObject* renderer = element->renderer();
647    if (!renderer || !renderer->isImage())
648        return 0;
649
650    RenderImage* image = toRenderImage(renderer);
651    if (image->cachedImage() && !image->cachedImage()->errorOccurred())
652        return image->cachedImage();
653
654    return 0;
655}
656
657static void writeImageToDataObject(IDataObject* dataObject, Element* element, const KURL& url)
658{
659    // Shove image data into a DataObject for use as a file
660    CachedImage* cachedImage = getCachedImage(element);
661    if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded())
662        return;
663
664    SharedBuffer* imageBuffer = cachedImage->image()->data();
665    if (!imageBuffer || !imageBuffer->size())
666        return;
667
668    HGLOBAL imageFileDescriptor = createGlobalImageFileDescriptor(url.string(), element->getAttribute(altAttr), cachedImage);
669    if (!imageFileDescriptor)
670        return;
671
672    HGLOBAL imageFileContent = createGlobalImageFileContent(imageBuffer);
673    if (!imageFileContent) {
674        GlobalFree(imageFileDescriptor);
675        return;
676    }
677
678    String fileName = cachedImage->response().suggestedFilename();
679    HGLOBAL hDropContent = createGlobalHDropContent(url, fileName, imageBuffer);
680    if (!hDropContent) {
681        GlobalFree(hDropContent);
682        return;
683    }
684
685    writeFileToDataObject(dataObject, imageFileDescriptor, imageFileContent, hDropContent);
686}
687
688void ClipboardWin::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
689{
690    // Order is important here for Explorer's sake
691    if (!m_writableDataObject)
692         return;
693    WebCore::writeURL(m_writableDataObject.get(), url, title, true, false);
694
695    writeImageToDataObject(m_writableDataObject.get(), element, url);
696
697    AtomicString imageURL = element->getAttribute(srcAttr);
698    if (imageURL.isEmpty())
699        return;
700
701    String fullURL = frame->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(imageURL)).string();
702    if (fullURL.isEmpty())
703        return;
704    STGMEDIUM medium = {0};
705    medium.tymed = TYMED_HGLOBAL;
706
707    // Put img tag on the clipboard referencing the image
708    Vector<char> data;
709    markupToCFHTML(imageToMarkup(fullURL), "", data);
710    medium.hGlobal = createGlobalData(data);
711    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
712        ::GlobalFree(medium.hGlobal);
713}
714
715void ClipboardWin::writeURL(const KURL& kurl, const String& titleStr, Frame*)
716{
717    if (!m_writableDataObject)
718         return;
719    WebCore::writeURL(m_writableDataObject.get(), kurl, titleStr, true, true);
720
721    String url = kurl.string();
722    ASSERT(url.containsOnlyASCII()); // KURL::string() is URL encoded.
723
724    String fsPath = filesystemPathFromUrlOrTitle(url, titleStr, L".URL", true);
725    CString content = makeString("[InternetShortcut]\r\nURL=", url, "\r\n").ascii();
726
727    if (fsPath.length() <= 0)
728        return;
729
730    HGLOBAL urlFileDescriptor = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
731    if (!urlFileDescriptor)
732        return;
733
734    HGLOBAL urlFileContent = GlobalAlloc(GPTR, content.length());
735    if (!urlFileContent) {
736        GlobalFree(urlFileDescriptor);
737        return;
738    }
739
740    FILEGROUPDESCRIPTOR* fgd = static_cast<FILEGROUPDESCRIPTOR*>(GlobalLock(urlFileDescriptor));
741    ZeroMemory(fgd, sizeof(FILEGROUPDESCRIPTOR));
742    fgd->cItems = 1;
743    fgd->fgd[0].dwFlags = FD_FILESIZE;
744    fgd->fgd[0].nFileSizeLow = content.length();
745
746    unsigned maxSize = min(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
747    CopyMemory(fgd->fgd[0].cFileName, fsPath.characters(), maxSize * sizeof(UChar));
748    GlobalUnlock(urlFileDescriptor);
749
750    char* fileContents = static_cast<char*>(GlobalLock(urlFileContent));
751    CopyMemory(fileContents, content.data(), content.length());
752    GlobalUnlock(urlFileContent);
753
754    writeFileToDataObject(m_writableDataObject.get(), urlFileDescriptor, urlFileContent, 0);
755}
756
757void ClipboardWin::writeRange(Range* selectedRange, Frame* frame)
758{
759    ASSERT(selectedRange);
760    if (!m_writableDataObject)
761         return;
762
763    STGMEDIUM medium = {0};
764    medium.tymed = TYMED_HGLOBAL;
765    ExceptionCode ec = 0;
766
767    Vector<char> data;
768    markupToCFHTML(createMarkup(selectedRange, 0, AnnotateForInterchange),
769        selectedRange->startContainer(ec)->document()->url().string(), data);
770    medium.hGlobal = createGlobalData(data);
771    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
772        ::GlobalFree(medium.hGlobal);
773
774    String str = frame->editor()->selectedText();
775    replaceNewlinesWithWindowsStyleNewlines(str);
776    replaceNBSPWithSpace(str);
777    medium.hGlobal = createGlobalData(str);
778    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
779        ::GlobalFree(medium.hGlobal);
780
781    medium.hGlobal = 0;
782    if (frame->editor()->canSmartCopyOrDelete())
783        m_writableDataObject->SetData(smartPasteFormat(), &medium, TRUE);
784}
785
786void ClipboardWin::writePlainText(const String& text)
787{
788    if (!m_writableDataObject)
789        return;
790
791    STGMEDIUM medium = {0};
792    medium.tymed = TYMED_HGLOBAL;
793
794    String str = text;
795    replaceNewlinesWithWindowsStyleNewlines(str);
796    replaceNBSPWithSpace(str);
797    medium.hGlobal = createGlobalData(str);
798    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
799        ::GlobalFree(medium.hGlobal);
800
801    medium.hGlobal = 0;
802}
803
804bool ClipboardWin::hasData()
805{
806    if (!m_dataObject && m_dragDataMap.isEmpty())
807        return false;
808
809    if (m_dataObject) {
810        COMPtr<IEnumFORMATETC> itr;
811        if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
812            return false;
813
814        if (!itr)
815            return false;
816
817        FORMATETC data;
818
819        // IEnumFORMATETC::Next returns S_FALSE if there are no more items.
820        if (itr->Next(1, &data, 0) == S_OK) {
821            // There is at least one item in the IDataObject
822            return true;
823        }
824
825        return false;
826    }
827    return !m_dragDataMap.isEmpty();
828}
829
830void ClipboardWin::setExternalDataObject(IDataObject *dataObject)
831{
832    ASSERT(isForDragAndDrop());
833
834    m_writableDataObject = 0;
835    m_dataObject = dataObject;
836}
837
838} // namespace WebCore
839