1/*
2 * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
3 * Copyright (C) 2013 Xueqing Huang <huangxueqing@baidu.com>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "Pasteboard.h"
29
30#include "BitmapInfo.h"
31#include "CachedImage.h"
32#include "ClipboardUtilitiesWin.h"
33#include "Document.h"
34#include "DocumentFragment.h"
35#include "Editor.h"
36#include "Element.h"
37#include "Frame.h"
38#include "HTMLNames.h"
39#include "HTMLParserIdioms.h"
40#include "HWndDC.h"
41#include "HitTestResult.h"
42#include "Image.h"
43#include "KURL.h"
44#include "NotImplemented.h"
45#include "Page.h"
46#include "Range.h"
47#include "RenderImage.h"
48#include "SharedBuffer.h"
49#include "TextEncoding.h"
50#include "WebCoreInstanceHandle.h"
51#include "WindowsExtras.h"
52#include "markup.h"
53#include <wtf/text/CString.h>
54
55namespace WebCore {
56
57// We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
58// see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
59
60static UINT HTMLClipboardFormat = 0;
61static UINT BookmarkClipboardFormat = 0;
62static UINT WebSmartPasteFormat = 0;
63
64static LRESULT CALLBACK PasteboardOwnerWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
65{
66    LRESULT lresult = 0;
67
68    switch (message) {
69    case WM_RENDERFORMAT:
70        // This message comes when SetClipboardData was sent a null data handle
71        // and now it's come time to put the data on the clipboard.
72        break;
73    case WM_RENDERALLFORMATS:
74        // This message comes when SetClipboardData was sent a null data handle
75        // and now this application is about to quit, so it must put data on
76        // the clipboard before it exits.
77        break;
78    case WM_DESTROY:
79        break;
80#if !OS(WINCE)
81    case WM_DRAWCLIPBOARD:
82        break;
83    case WM_CHANGECBCHAIN:
84        break;
85#endif
86    default:
87        lresult = DefWindowProc(hWnd, message, wParam, lParam);
88        break;
89    }
90    return lresult;
91}
92
93Pasteboard* Pasteboard::generalPasteboard()
94{
95    static Pasteboard* pasteboard = new Pasteboard;
96    return pasteboard;
97}
98
99PassOwnPtr<Pasteboard> Pasteboard::createForCopyAndPaste()
100{
101    OwnPtr<Pasteboard> pasteboard = adoptPtr(new Pasteboard);
102    COMPtr<IDataObject> clipboardData;
103#if !OS(WINCE)
104    if (!SUCCEEDED(OleGetClipboard(&clipboardData)))
105        clipboardData = 0;
106#endif
107    pasteboard->setExternalDataObject(clipboardData.get());
108    return pasteboard.release();
109}
110
111PassOwnPtr<Pasteboard> Pasteboard::createPrivate()
112{
113    // Windows has no "Private pasteboard" concept.
114    return createForCopyAndPaste();
115}
116
117#if ENABLE(DRAG_SUPPORT)
118PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop()
119{
120    COMPtr<WCDataObject> dataObject;
121    WCDataObject::createInstance(&dataObject);
122    return adoptPtr(new Pasteboard(dataObject.get()));
123}
124
125// static
126PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
127{
128    if (dragData.platformData())
129        return adoptPtr(new Pasteboard(dragData.platformData()));
130    // FIXME: Should add a const overload of dragDataMap so we don't need a const_cast here.
131    return adoptPtr(new Pasteboard(const_cast<DragData&>(dragData).dragDataMap()));
132}
133#endif
134
135void Pasteboard::finishCreatingPasteboard()
136{
137    WNDCLASS wc;
138    memset(&wc, 0, sizeof(WNDCLASS));
139    wc.lpfnWndProc    = PasteboardOwnerWndProc;
140    wc.hInstance      = WebCore::instanceHandle();
141    wc.lpszClassName  = L"PasteboardOwnerWindowClass";
142    RegisterClass(&wc);
143
144    m_owner = ::CreateWindow(L"PasteboardOwnerWindowClass", L"PasteboardOwnerWindow", 0, 0, 0, 0, 0,
145        HWND_MESSAGE, 0, 0, 0);
146
147    HTMLClipboardFormat = ::RegisterClipboardFormat(L"HTML Format");
148    BookmarkClipboardFormat = ::RegisterClipboardFormat(L"UniformResourceLocatorW");
149    WebSmartPasteFormat = ::RegisterClipboardFormat(L"WebKit Smart Paste Format");
150}
151
152Pasteboard::Pasteboard()
153    : m_dataObject(0)
154    , m_writableDataObject(0)
155{
156    finishCreatingPasteboard();
157}
158
159Pasteboard::Pasteboard(IDataObject* dataObject)
160    : m_dataObject(dataObject)
161    , m_writableDataObject(0)
162{
163    finishCreatingPasteboard();
164}
165
166Pasteboard::Pasteboard(WCDataObject* dataObject)
167    : m_dataObject(dataObject)
168    , m_writableDataObject(dataObject)
169{
170    finishCreatingPasteboard();
171}
172
173Pasteboard::Pasteboard(const DragDataMap& dataMap)
174    : m_dataObject(0)
175    , m_writableDataObject(0)
176    , m_dragDataMap(dataMap)
177{
178    finishCreatingPasteboard();
179}
180
181void Pasteboard::clear()
182{
183    if (::OpenClipboard(m_owner)) {
184        ::EmptyClipboard();
185        ::CloseClipboard();
186    }
187}
188
189enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText, ClipboardDataTypeTextHTML };
190
191static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
192{
193    String qType = type.stripWhiteSpace().lower();
194
195    // two special cases for IE compatibility
196    if (qType == "text" || qType == "text/plain" || qType.startsWith("text/plain;"))
197        return ClipboardDataTypeText;
198    if (qType == "url" || qType == "text/uri-list")
199        return ClipboardDataTypeURL;
200    if (qType == "text/html")
201        return ClipboardDataTypeTextHTML;
202
203    return ClipboardDataTypeNone;
204}
205
206void Pasteboard::clear(const String& type)
207{
208    if (!m_writableDataObject)
209        return;
210
211    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
212
213    if (dataType == ClipboardDataTypeURL) {
214        m_writableDataObject->clearData(urlWFormat()->cfFormat);
215        m_writableDataObject->clearData(urlFormat()->cfFormat);
216    }
217    if (dataType == ClipboardDataTypeText) {
218        m_writableDataObject->clearData(plainTextFormat()->cfFormat);
219        m_writableDataObject->clearData(plainTextWFormat()->cfFormat);
220    }
221}
222
223bool Pasteboard::hasData()
224{
225    if (!m_dataObject && m_dragDataMap.isEmpty())
226        return false;
227
228    if (m_dataObject) {
229        COMPtr<IEnumFORMATETC> itr;
230        if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
231            return false;
232
233        if (!itr)
234            return false;
235
236        FORMATETC data;
237
238        // IEnumFORMATETC::Next returns S_FALSE if there are no more items.
239        if (itr->Next(1, &data, 0) == S_OK) {
240            // There is at least one item in the IDataObject
241            return true;
242        }
243
244        return false;
245    }
246    return !m_dragDataMap.isEmpty();
247}
248
249static void addMimeTypesForFormat(ListHashSet<String>& results, const FORMATETC& format)
250{
251    // URL and Text are provided for compatibility with IE's model
252    if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat) {
253        results.add("URL");
254        results.add("text/uri-list");
255    }
256
257    if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat) {
258        results.add("Text");
259        results.add("text/plain");
260    }
261}
262
263ListHashSet<String> Pasteboard::types()
264{
265    ListHashSet<String> results;
266
267    if (!m_dataObject && m_dragDataMap.isEmpty())
268        return results;
269
270    if (m_dataObject) {
271        COMPtr<IEnumFORMATETC> itr;
272
273        if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
274            return results;
275
276        if (!itr)
277            return results;
278
279        FORMATETC data;
280
281        // IEnumFORMATETC::Next returns S_FALSE if there are no more items.
282        while (itr->Next(1, &data, 0) == S_OK)
283            addMimeTypesForFormat(results, data);
284    } else {
285        for (DragDataMap::const_iterator it = m_dragDataMap.begin(); it != m_dragDataMap.end(); ++it) {
286            FORMATETC data;
287            data.cfFormat = (*it).key;
288            addMimeTypesForFormat(results, data);
289        }
290    }
291
292    return results;
293}
294
295String Pasteboard::readString(const String& type)
296{
297    if (!m_dataObject && m_dragDataMap.isEmpty())
298        return "";
299
300    ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
301    if (dataType == ClipboardDataTypeText)
302        return m_dataObject ? getPlainText(m_dataObject.get()) : getPlainText(&m_dragDataMap);
303    if (dataType == ClipboardDataTypeURL)
304        return m_dataObject ? getURL(m_dataObject.get(), DragData::DoNotConvertFilenames) : getURL(&m_dragDataMap, DragData::DoNotConvertFilenames);
305    if (dataType == ClipboardDataTypeTextHTML) {
306        String data = m_dataObject ? getTextHTML(m_dataObject.get()) : getTextHTML(&m_dragDataMap);
307        if (!data.isEmpty())
308            return data;
309        return m_dataObject ? getCFHTML(m_dataObject.get()) : getCFHTML(&m_dragDataMap);
310    }
311
312    return "";
313}
314
315Vector<String> Pasteboard::readFilenames()
316{
317    Vector<String> fileNames;
318
319#if USE(CF)
320    if (m_dataObject) {
321        STGMEDIUM medium;
322        if (FAILED(m_dataObject->GetData(cfHDropFormat(), &medium)))
323            return fileNames;
324
325        HDROP hdrop = reinterpret_cast<HDROP>(GlobalLock(medium.hGlobal));
326        if (!hdrop)
327            return fileNames;
328
329        WCHAR filename[MAX_PATH];
330        UINT fileCount = DragQueryFileW(hdrop, 0xFFFFFFFF, 0, 0);
331        for (UINT i = 0; i < fileCount; i++) {
332            if (!DragQueryFileW(hdrop, i, filename, WTF_ARRAY_LENGTH(filename)))
333                continue;
334            fileNames.append(filename);
335        }
336
337        GlobalUnlock(medium.hGlobal);
338        ReleaseStgMedium(&medium);
339        return fileNames;
340    }
341    if (!m_dragDataMap.contains(cfHDropFormat()->cfFormat))
342        return fileNames;
343    return m_dragDataMap.get(cfHDropFormat()->cfFormat);
344#else
345    notImplemented();
346    return fileNames;
347#endif
348}
349
350static bool writeURL(WCDataObject *data, const KURL& url, String title, bool withPlainText, bool withHTML)
351{
352    ASSERT(data);
353
354    if (url.isEmpty())
355        return false;
356
357    if (title.isEmpty()) {
358        title = url.lastPathComponent();
359        if (title.isEmpty())
360            title = url.host();
361    }
362
363    STGMEDIUM medium = {0};
364    medium.tymed = TYMED_HGLOBAL;
365
366    medium.hGlobal = createGlobalData(url, title);
367    bool success = false;
368    if (medium.hGlobal && FAILED(data->SetData(urlWFormat(), &medium, TRUE)))
369        ::GlobalFree(medium.hGlobal);
370    else
371        success = true;
372
373    if (withHTML) {
374        Vector<char> cfhtmlData;
375        markupToCFHTML(urlToMarkup(url, title), "", cfhtmlData);
376        medium.hGlobal = createGlobalData(cfhtmlData);
377        if (medium.hGlobal && FAILED(data->SetData(htmlFormat(), &medium, TRUE)))
378            ::GlobalFree(medium.hGlobal);
379        else
380            success = true;
381    }
382
383    if (withPlainText) {
384        medium.hGlobal = createGlobalData(url.string());
385        if (medium.hGlobal && FAILED(data->SetData(plainTextWFormat(), &medium, TRUE)))
386            ::GlobalFree(medium.hGlobal);
387        else
388            success = true;
389    }
390
391    return success;
392}
393
394bool Pasteboard::writeString(const String& type, const String& data)
395{
396    if (!m_writableDataObject)
397        return false;
398
399    ClipboardDataType winType = clipboardTypeFromMIMEType(type);
400
401    if (winType == ClipboardDataTypeURL)
402        return WebCore::writeURL(m_writableDataObject.get(), KURL(KURL(), data), String(), false, true);
403
404    if (winType == ClipboardDataTypeText) {
405        STGMEDIUM medium = {0};
406        medium.tymed = TYMED_HGLOBAL;
407        medium.hGlobal = createGlobalData(data);
408        if (!medium.hGlobal)
409            return false;
410
411        if (FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE))) {
412            ::GlobalFree(medium.hGlobal);
413            return false;
414        }
415        return true;
416    }
417
418    return false;
419}
420
421#if ENABLE(DRAG_SUPPORT)
422void Pasteboard::setDragImage(DragImageRef, const IntPoint&)
423{
424    // Do nothing in Windows.
425}
426#endif
427
428void Pasteboard::writeRangeToDataObject(Range* selectedRange, Frame* frame)
429{
430    ASSERT(selectedRange);
431    if (!m_writableDataObject)
432        return;
433
434    STGMEDIUM medium = {0};
435    medium.tymed = TYMED_HGLOBAL;
436
437    Vector<char> data;
438    markupToCFHTML(createMarkup(selectedRange, 0, AnnotateForInterchange),
439        selectedRange->startContainer()->document()->url().string(), data);
440    medium.hGlobal = createGlobalData(data);
441    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
442        ::GlobalFree(medium.hGlobal);
443
444    String str = frame->editor().selectedTextForClipboard();
445    replaceNewlinesWithWindowsStyleNewlines(str);
446    replaceNBSPWithSpace(str);
447    medium.hGlobal = createGlobalData(str);
448    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
449        ::GlobalFree(medium.hGlobal);
450
451    medium.hGlobal = 0;
452    if (frame->editor().canSmartCopyOrDelete())
453        m_writableDataObject->SetData(smartPasteFormat(), &medium, TRUE);
454}
455
456void Pasteboard::writeSelection(Range* selectedRange, bool canSmartCopyOrDelete, Frame* frame, ShouldSerializeSelectedTextForClipboard shouldSerializeSelectedTextForClipboard)
457{
458    clear();
459
460    // Put CF_HTML format on the pasteboard
461    if (::OpenClipboard(m_owner)) {
462        Vector<char> data;
463        markupToCFHTML(createMarkup(selectedRange, 0, AnnotateForInterchange),
464            selectedRange->startContainer()->document()->url().string(), data);
465        HGLOBAL cbData = createGlobalData(data);
466        if (!::SetClipboardData(HTMLClipboardFormat, cbData))
467            ::GlobalFree(cbData);
468        ::CloseClipboard();
469    }
470
471    // Put plain string on the pasteboard. CF_UNICODETEXT covers CF_TEXT as well
472    String str = shouldSerializeSelectedTextForClipboard == IncludeImageAltTextForClipboard ? frame->editor().selectedTextForClipboard() : frame->editor().selectedText();
473    replaceNewlinesWithWindowsStyleNewlines(str);
474    replaceNBSPWithSpace(str);
475    if (::OpenClipboard(m_owner)) {
476        HGLOBAL cbData = createGlobalData(str);
477        if (!::SetClipboardData(CF_UNICODETEXT, cbData))
478            ::GlobalFree(cbData);
479        ::CloseClipboard();
480    }
481
482    // enable smart-replacing later on by putting dummy data on the pasteboard
483    if (canSmartCopyOrDelete) {
484        if (::OpenClipboard(m_owner)) {
485            ::SetClipboardData(WebSmartPasteFormat, 0);
486            ::CloseClipboard();
487        }
488    }
489
490    writeRangeToDataObject(selectedRange, frame);
491}
492
493void Pasteboard::writePlainTextToDataObject(const String& text, SmartReplaceOption smartReplaceOption)
494{
495    if (!m_writableDataObject)
496        return;
497
498    STGMEDIUM medium = {0};
499    medium.tymed = TYMED_HGLOBAL;
500
501    String str = text;
502    replaceNewlinesWithWindowsStyleNewlines(str);
503    replaceNBSPWithSpace(str);
504    medium.hGlobal = createGlobalData(str);
505    if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
506        ::GlobalFree(medium.hGlobal);
507
508    medium.hGlobal = 0;
509}
510
511void Pasteboard::writePlainText(const String& text, SmartReplaceOption smartReplaceOption)
512{
513    clear();
514
515    // Put plain string on the pasteboard. CF_UNICODETEXT covers CF_TEXT as well
516    String str = text;
517    replaceNewlinesWithWindowsStyleNewlines(str);
518    if (::OpenClipboard(m_owner)) {
519        HGLOBAL cbData = createGlobalData(str);
520        if (!::SetClipboardData(CF_UNICODETEXT, cbData))
521            ::GlobalFree(cbData);
522        ::CloseClipboard();
523    }
524
525    // enable smart-replacing later on by putting dummy data on the pasteboard
526    if (smartReplaceOption == CanSmartReplace) {
527        if (::OpenClipboard(m_owner)) {
528            ::SetClipboardData(WebSmartPasteFormat, 0);
529            ::CloseClipboard();
530        }
531    }
532
533    writePlainTextToDataObject(text, smartReplaceOption);
534}
535
536#if !OS(WINCE)
537static inline void pathRemoveBadFSCharacters(PWSTR psz, size_t length)
538{
539    size_t writeTo = 0;
540    size_t readFrom = 0;
541    while (readFrom < length) {
542        UINT type = PathGetCharType(psz[readFrom]);
543        if (!psz[readFrom] || type & (GCT_LFNCHAR | GCT_SHORTCHAR))
544            psz[writeTo++] = psz[readFrom];
545
546        readFrom++;
547    }
548    psz[writeTo] = 0;
549}
550#endif
551
552static String filesystemPathFromUrlOrTitle(const String& url, const String& title, const UChar* extension, bool isLink)
553{
554#if OS(WINCE)
555    notImplemented();
556    return String();
557#else
558    static const size_t fsPathMaxLengthExcludingNullTerminator = MAX_PATH - 1;
559    bool usedURL = false;
560    WCHAR fsPathBuffer[MAX_PATH];
561    fsPathBuffer[0] = 0;
562    int extensionLen = extension ? lstrlen(extension) : 0;
563    int fsPathMaxLengthExcludingExtension = fsPathMaxLengthExcludingNullTerminator - extensionLen;
564
565    if (!title.isEmpty()) {
566        size_t len = std::min<size_t>(title.length(), fsPathMaxLengthExcludingExtension);
567        CopyMemory(fsPathBuffer, title.characters(), len * sizeof(UChar));
568        fsPathBuffer[len] = 0;
569        pathRemoveBadFSCharacters(fsPathBuffer, len);
570    }
571
572    if (!lstrlen(fsPathBuffer)) {
573        KURL kurl(KURL(), url);
574        usedURL = true;
575        // The filename for any content based drag or file url should be the last element of
576        // the path. If we can't find it, or we're coming up with the name for a link
577        // we just use the entire url.
578        DWORD len = fsPathMaxLengthExcludingExtension;
579        String lastComponent = kurl.lastPathComponent();
580        if (kurl.isLocalFile() || (!isLink && !lastComponent.isEmpty())) {
581            len = std::min<DWORD>(fsPathMaxLengthExcludingExtension, lastComponent.length());
582            CopyMemory(fsPathBuffer, lastComponent.characters(), len * sizeof(UChar));
583        } else {
584            len = std::min<DWORD>(fsPathMaxLengthExcludingExtension, url.length());
585            CopyMemory(fsPathBuffer, url.characters(), len * sizeof(UChar));
586        }
587        fsPathBuffer[len] = 0;
588        pathRemoveBadFSCharacters(fsPathBuffer, len);
589    }
590
591    if (!extension)
592        return String(static_cast<UChar*>(fsPathBuffer));
593
594    if (!isLink && usedURL) {
595        PathRenameExtension(fsPathBuffer, extension);
596        return String(static_cast<UChar*>(fsPathBuffer));
597    }
598
599    return makeString(static_cast<const UChar*>(fsPathBuffer), extension);
600#endif
601}
602
603// writeFileToDataObject takes ownership of fileDescriptor and fileContent
604static HRESULT writeFileToDataObject(IDataObject* dataObject, HGLOBAL fileDescriptor, HGLOBAL fileContent, HGLOBAL hDropContent)
605{
606    HRESULT hr = S_OK;
607    FORMATETC* fe;
608    STGMEDIUM medium = {0};
609    medium.tymed = TYMED_HGLOBAL;
610
611    if (!fileDescriptor || !fileContent)
612        goto exit;
613
614    // Descriptor
615    fe = fileDescriptorFormat();
616
617    medium.hGlobal = fileDescriptor;
618
619    if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
620        goto exit;
621
622    // Contents
623    fe = fileContentFormatZero();
624    medium.hGlobal = fileContent;
625    if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
626        goto exit;
627
628#if USE(CF)
629    // HDROP
630    if (hDropContent) {
631        medium.hGlobal = hDropContent;
632        hr = dataObject->SetData(cfHDropFormat(), &medium, TRUE);
633    }
634#endif
635
636exit:
637    if (FAILED(hr)) {
638        if (fileDescriptor)
639            GlobalFree(fileDescriptor);
640        if (fileContent)
641            GlobalFree(fileContent);
642        if (hDropContent)
643            GlobalFree(hDropContent);
644    }
645    return hr;
646}
647
648void Pasteboard::writeURLToDataObject(const KURL& kurl, const String& titleStr, Frame* frame)
649{
650    if (!m_writableDataObject)
651        return;
652    WebCore::writeURL(m_writableDataObject.get(), kurl, titleStr, true, true);
653
654    String url = kurl.string();
655    ASSERT(url.containsOnlyASCII()); // KURL::string() is URL encoded.
656
657    String fsPath = filesystemPathFromUrlOrTitle(url, titleStr, L".URL", true);
658    String contentString("[InternetShortcut]\r\nURL=" + url + "\r\n");
659    CString content = contentString.latin1();
660
661    if (fsPath.length() <= 0)
662        return;
663
664    HGLOBAL urlFileDescriptor = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
665    if (!urlFileDescriptor)
666        return;
667
668    HGLOBAL urlFileContent = GlobalAlloc(GPTR, content.length());
669    if (!urlFileContent) {
670        GlobalFree(urlFileDescriptor);
671        return;
672    }
673
674    FILEGROUPDESCRIPTOR* fgd = static_cast<FILEGROUPDESCRIPTOR*>(GlobalLock(urlFileDescriptor));
675    ZeroMemory(fgd, sizeof(FILEGROUPDESCRIPTOR));
676    fgd->cItems = 1;
677    fgd->fgd[0].dwFlags = FD_FILESIZE;
678    fgd->fgd[0].nFileSizeLow = content.length();
679
680    unsigned maxSize = std::min<unsigned>(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
681    CopyMemory(fgd->fgd[0].cFileName, fsPath.characters(), maxSize * sizeof(UChar));
682    GlobalUnlock(urlFileDescriptor);
683
684    char* fileContents = static_cast<char*>(GlobalLock(urlFileContent));
685    CopyMemory(fileContents, content.data(), content.length());
686    GlobalUnlock(urlFileContent);
687
688    writeFileToDataObject(m_writableDataObject.get(), urlFileDescriptor, urlFileContent, 0);
689}
690
691void Pasteboard::writeURL(const KURL& url, const String& titleStr, Frame* frame)
692{
693    ASSERT(!url.isEmpty());
694
695    clear();
696
697    String title(titleStr);
698    if (title.isEmpty()) {
699        title = url.lastPathComponent();
700        if (title.isEmpty())
701            title = url.host();
702    }
703
704    // write to clipboard in format com.apple.safari.bookmarkdata to be able to paste into the bookmarks view with appropriate title
705    if (::OpenClipboard(m_owner)) {
706        HGLOBAL cbData = createGlobalData(url, title);
707        if (!::SetClipboardData(BookmarkClipboardFormat, cbData))
708            ::GlobalFree(cbData);
709        ::CloseClipboard();
710    }
711
712    // write to clipboard in format CF_HTML to be able to paste into contenteditable areas as a link
713    if (::OpenClipboard(m_owner)) {
714        Vector<char> data;
715        markupToCFHTML(urlToMarkup(url, title), "", data);
716        HGLOBAL cbData = createGlobalData(data);
717        if (!::SetClipboardData(HTMLClipboardFormat, cbData))
718            ::GlobalFree(cbData);
719        ::CloseClipboard();
720    }
721
722    // bare-bones CF_UNICODETEXT support
723    if (::OpenClipboard(m_owner)) {
724        HGLOBAL cbData = createGlobalData(url.string());
725        if (!::SetClipboardData(CF_UNICODETEXT, cbData))
726            ::GlobalFree(cbData);
727        ::CloseClipboard();
728    }
729
730    writeURLToDataObject(url, titleStr, frame);
731}
732
733void Pasteboard::writeImage(Node* node, const KURL&, const String&)
734{
735    ASSERT(node);
736
737    if (!(node->renderer() && node->renderer()->isRenderImage()))
738        return;
739
740    RenderImage* renderer = toRenderImage(node->renderer());
741    CachedImage* cachedImage = renderer->cachedImage();
742    if (!cachedImage || cachedImage->errorOccurred())
743        return;
744    Image* image = cachedImage->imageForRenderer(renderer);
745    ASSERT(image);
746
747    clear();
748
749    HWndDC dc(0);
750    HDC compatibleDC = CreateCompatibleDC(0);
751    HDC sourceDC = CreateCompatibleDC(0);
752    OwnPtr<HBITMAP> resultBitmap = adoptPtr(CreateCompatibleBitmap(dc, image->width(), image->height()));
753    HGDIOBJ oldBitmap = SelectObject(compatibleDC, resultBitmap.get());
754
755    BitmapInfo bmInfo = BitmapInfo::create(image->size());
756
757    HBITMAP coreBitmap = CreateDIBSection(dc, &bmInfo, DIB_RGB_COLORS, 0, 0, 0);
758    HGDIOBJ oldSource = SelectObject(sourceDC, coreBitmap);
759    image->getHBITMAP(coreBitmap);
760
761    BitBlt(compatibleDC, 0, 0, image->width(), image->height(), sourceDC, 0, 0, SRCCOPY);
762
763    SelectObject(sourceDC, oldSource);
764    DeleteObject(coreBitmap);
765
766    SelectObject(compatibleDC, oldBitmap);
767    DeleteDC(sourceDC);
768    DeleteDC(compatibleDC);
769
770    if (::OpenClipboard(m_owner)) {
771        ::SetClipboardData(CF_BITMAP, resultBitmap.leakPtr());
772        ::CloseClipboard();
773    }
774}
775
776void Pasteboard::writePasteboard(const Pasteboard& sourcePasteboard)
777{
778    notImplemented();
779}
780
781void Pasteboard::writeClipboard(Clipboard*)
782{
783    notImplemented();
784}
785
786bool Pasteboard::canSmartReplace()
787{
788    return ::IsClipboardFormatAvailable(WebSmartPasteFormat);
789}
790
791String Pasteboard::plainText(Frame* frame)
792{
793    if (::IsClipboardFormatAvailable(CF_UNICODETEXT) && ::OpenClipboard(m_owner)) {
794        HANDLE cbData = ::GetClipboardData(CF_UNICODETEXT);
795        if (cbData) {
796            UChar* buffer = static_cast<UChar*>(GlobalLock(cbData));
797            String fromClipboard(buffer);
798            GlobalUnlock(cbData);
799            ::CloseClipboard();
800            return fromClipboard;
801        }
802        ::CloseClipboard();
803    }
804
805    if (::IsClipboardFormatAvailable(CF_TEXT) && ::OpenClipboard(m_owner)) {
806        HANDLE cbData = ::GetClipboardData(CF_TEXT);
807        if (cbData) {
808            char* buffer = static_cast<char*>(GlobalLock(cbData));
809            String fromClipboard(buffer);
810            GlobalUnlock(cbData);
811            ::CloseClipboard();
812            return fromClipboard;
813        }
814        ::CloseClipboard();
815    }
816
817    return String();
818}
819
820PassRefPtr<DocumentFragment> Pasteboard::documentFragment(Frame* frame, PassRefPtr<Range> context, bool allowPlainText, bool& chosePlainText)
821{
822    chosePlainText = false;
823
824    if (::IsClipboardFormatAvailable(HTMLClipboardFormat) && ::OpenClipboard(m_owner)) {
825        // get data off of clipboard
826        HANDLE cbData = ::GetClipboardData(HTMLClipboardFormat);
827        if (cbData) {
828            SIZE_T dataSize = ::GlobalSize(cbData);
829            String cfhtml(UTF8Encoding().decode(static_cast<char*>(GlobalLock(cbData)), dataSize));
830            GlobalUnlock(cbData);
831            ::CloseClipboard();
832
833            PassRefPtr<DocumentFragment> fragment = fragmentFromCFHTML(frame->document(), cfhtml);
834            if (fragment)
835                return fragment;
836        } else
837            ::CloseClipboard();
838    }
839
840    if (allowPlainText && ::IsClipboardFormatAvailable(CF_UNICODETEXT)) {
841        chosePlainText = true;
842        if (::OpenClipboard(m_owner)) {
843            HANDLE cbData = ::GetClipboardData(CF_UNICODETEXT);
844            if (cbData) {
845                UChar* buffer = static_cast<UChar*>(GlobalLock(cbData));
846                String str(buffer);
847                GlobalUnlock(cbData);
848                ::CloseClipboard();
849                RefPtr<DocumentFragment> fragment = createFragmentFromText(context.get(), str);
850                if (fragment)
851                    return fragment.release();
852            } else
853                ::CloseClipboard();
854        }
855    }
856
857    if (allowPlainText && ::IsClipboardFormatAvailable(CF_TEXT)) {
858        chosePlainText = true;
859        if (::OpenClipboard(m_owner)) {
860            HANDLE cbData = ::GetClipboardData(CF_TEXT);
861            if (cbData) {
862                char* buffer = static_cast<char*>(GlobalLock(cbData));
863                String str(buffer);
864                GlobalUnlock(cbData);
865                ::CloseClipboard();
866                RefPtr<DocumentFragment> fragment = createFragmentFromText(context.get(), str);
867                if (fragment)
868                    return fragment.release();
869            } else
870                ::CloseClipboard();
871        }
872    }
873
874    return 0;
875}
876
877void Pasteboard::setExternalDataObject(IDataObject *dataObject)
878{
879    m_writableDataObject = 0;
880    m_dataObject = dataObject;
881}
882
883static CachedImage* getCachedImage(Element* element)
884{
885    // Attempt to pull CachedImage from element
886    ASSERT(element);
887    RenderObject* renderer = element->renderer();
888    if (!renderer || !renderer->isRenderImage())
889        return 0;
890
891    RenderImage* image = toRenderImage(renderer);
892    if (image->cachedImage() && !image->cachedImage()->errorOccurred())
893        return image->cachedImage();
894
895    return 0;
896}
897
898static HGLOBAL createGlobalImageFileDescriptor(const String& url, const String& title, CachedImage* image)
899{
900    ASSERT_ARG(image, image);
901    ASSERT(image->image()->data());
902
903    HRESULT hr = S_OK;
904    HGLOBAL memObj = 0;
905    String fsPath;
906    memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
907    if (!memObj)
908        return 0;
909
910    FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj);
911    memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR));
912    fgd->cItems = 1;
913    fgd->fgd[0].dwFlags = FD_FILESIZE;
914    fgd->fgd[0].nFileSizeLow = image->image()->data()->size();
915
916    const String& preferredTitle = title.isEmpty() ? image->response().suggestedFilename() : title;
917    String extension = image->image()->filenameExtension();
918    if (extension.isEmpty()) {
919        // Do not continue processing in the rare and unusual case where a decoded image is not able
920        // to provide a filename extension. Something tricky (like a bait-n-switch) is going on
921        return 0;
922    }
923    extension.insert(".", 0);
924    fsPath = filesystemPathFromUrlOrTitle(url, preferredTitle, extension.charactersWithNullTermination(), false);
925
926    if (fsPath.length() <= 0) {
927        GlobalUnlock(memObj);
928        GlobalFree(memObj);
929        return 0;
930    }
931
932    int maxSize = std::min<int>(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
933    CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar));
934    GlobalUnlock(memObj);
935
936    return memObj;
937}
938
939static HGLOBAL createGlobalImageFileContent(SharedBuffer* data)
940{
941    HGLOBAL memObj = GlobalAlloc(GPTR, data->size());
942    if (!memObj)
943        return 0;
944
945    char* fileContents = (PSTR)GlobalLock(memObj);
946
947    CopyMemory(fileContents, data->data(), data->size());
948
949    GlobalUnlock(memObj);
950
951    return memObj;
952}
953
954static HGLOBAL createGlobalHDropContent(const KURL& url, String& fileName, SharedBuffer* data)
955{
956    if (fileName.isEmpty() || !data)
957        return 0;
958
959    WCHAR filePath[MAX_PATH];
960
961    if (url.isLocalFile()) {
962        String localPath = decodeURLEscapeSequences(url.path());
963        // windows does not enjoy a leading slash on paths
964        if (localPath[0] == '/')
965            localPath = localPath.substring(1);
966        LPCWSTR localPathStr = localPath.charactersWithNullTermination();
967        if (wcslen(localPathStr) + 1 < MAX_PATH)
968            wcscpy_s(filePath, MAX_PATH, localPathStr);
969        else
970            return 0;
971    } else {
972#if OS(WINCE)
973        notImplemented();
974        return 0;
975#else
976        WCHAR tempPath[MAX_PATH];
977        WCHAR extension[MAX_PATH];
978        if (!::GetTempPath(WTF_ARRAY_LENGTH(tempPath), tempPath))
979            return 0;
980        if (!::PathAppend(tempPath, fileName.charactersWithNullTermination()))
981            return 0;
982        LPCWSTR foundExtension = ::PathFindExtension(tempPath);
983        if (foundExtension) {
984            if (wcscpy_s(extension, MAX_PATH, foundExtension))
985                return 0;
986        } else
987            *extension = 0;
988        ::PathRemoveExtension(tempPath);
989        for (int i = 1; i < 10000; i++) {
990            if (swprintf_s(filePath, MAX_PATH, TEXT("%s-%d%s"), tempPath, i, extension) == -1)
991                return 0;
992            if (!::PathFileExists(filePath))
993                break;
994        }
995        HANDLE tempFileHandle = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
996        if (tempFileHandle == INVALID_HANDLE_VALUE)
997            return 0;
998
999        // Write the data to this temp file.
1000        DWORD written;
1001        BOOL tempWriteSucceeded = WriteFile(tempFileHandle, data->data(), data->size(), &written, 0);
1002        CloseHandle(tempFileHandle);
1003        if (!tempWriteSucceeded)
1004            return 0;
1005#endif
1006    }
1007
1008    SIZE_T dropFilesSize = sizeof(DROPFILES) + (sizeof(WCHAR) * (wcslen(filePath) + 2));
1009    HGLOBAL memObj = GlobalAlloc(GHND | GMEM_SHARE, dropFilesSize);
1010    if (!memObj)
1011        return 0;
1012
1013    DROPFILES* dropFiles = (DROPFILES*) GlobalLock(memObj);
1014    dropFiles->pFiles = sizeof(DROPFILES);
1015    dropFiles->fWide = TRUE;
1016    wcscpy((LPWSTR)(dropFiles + 1), filePath);
1017    GlobalUnlock(memObj);
1018
1019    return memObj;
1020}
1021
1022void Pasteboard::writeImageToDataObject(Element* element, const KURL& url)
1023{
1024    // Shove image data into a DataObject for use as a file
1025    CachedImage* cachedImage = getCachedImage(element);
1026    if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded())
1027        return;
1028
1029    SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data();
1030    if (!imageBuffer || !imageBuffer->size())
1031        return;
1032
1033    HGLOBAL imageFileDescriptor = createGlobalImageFileDescriptor(url.string(), element->getAttribute(HTMLNames::altAttr), cachedImage);
1034    if (!imageFileDescriptor)
1035        return;
1036
1037    HGLOBAL imageFileContent = createGlobalImageFileContent(imageBuffer);
1038    if (!imageFileContent) {
1039        GlobalFree(imageFileDescriptor);
1040        return;
1041    }
1042
1043    String fileName = cachedImage->response().suggestedFilename();
1044    HGLOBAL hDropContent = createGlobalHDropContent(url, fileName, imageBuffer);
1045    if (!hDropContent) {
1046        GlobalFree(hDropContent);
1047        return;
1048    }
1049
1050    writeFileToDataObject(m_writableDataObject.get(), imageFileDescriptor, imageFileContent, hDropContent);
1051}
1052
1053void Pasteboard::writeURLToWritableDataObject(const KURL& url, const String& title)
1054{
1055    WebCore::writeURL(m_writableDataObject.get(), url, title, true, false);
1056}
1057
1058} // namespace WebCore
1059