2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ClipboardWin.h"
29 #include "CachedImage.h"
30 #include "ClipboardUtilitiesWin.h"
31 #include "csshelper.h"
33 #include "DeprecatedString.h"
38 #include "EventHandler.h"
40 #include "FrameLoader.h"
41 #include "FrameView.h"
42 #include "HTMLNames.h"
44 #include "MIMETypeRegistry.h"
47 #include "Pasteboard.h"
48 #include "PlatformMouseEvent.h"
49 #include "PlatformString.h"
51 #include "RenderImage.h"
52 #include "ResourceResponse.h"
53 #include "StringHash.h"
54 #include "WCDataObject.h"
59 #include <wtf/RefPtr.h>
63 using namespace HTMLNames;
66 static const char szShellDotUrlTemplate[] = "[InternetShortcut]\r\nURL=%s\r\n";
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
71 enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText };
73 static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
75 String qType = type.stripWhiteSpace().lower();
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;
83 return ClipboardDataTypeNone;
86 static inline FORMATETC* fileDescriptorFormat()
88 static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
89 static FORMATETC fileDescriptorFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
90 return &fileDescriptorFormat;
93 static inline FORMATETC* fileContentFormatZero()
95 static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS);
96 static FORMATETC fileContentFormat = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
97 return &fileContentFormat;
100 static inline void pathRemoveBadFSCharacters(PWSTR psz, size_t length)
104 while (readFrom < length) {
105 UINT type = PathGetCharType(psz[readFrom]);
106 if (psz[readFrom] == 0 || type & (GCT_LFNCHAR | GCT_SHORTCHAR)) {
107 psz[writeTo++] = psz[readFrom];
115 static String filesystemPathFromUrlOrTitle(const String& url, const String& title, TCHAR* extension, bool isLink)
117 bool usedURL = false;
118 WCHAR fsPathBuffer[MAX_PATH + 1];
120 int extensionLen = extension ? lstrlen(extension) : 0;
122 if (!title.isEmpty()) {
123 size_t len = min<size_t>(title.length(), MAX_PATH - extensionLen);
124 CopyMemory(fsPathBuffer, title.characters(), len * sizeof(UChar));
125 fsPathBuffer[len] = 0;
126 pathRemoveBadFSCharacters(fsPathBuffer, len);
129 if (!lstrlen(fsPathBuffer)) {
130 DWORD len = MAX_PATH;
131 String nullTermURL = url;
133 if (UrlIsFileUrl((LPCWSTR)nullTermURL.charactersWithNullTermination())
134 && SUCCEEDED(PathCreateFromUrl((LPCWSTR)nullTermURL.charactersWithNullTermination(), fsPathBuffer, &len, 0))) {
135 // When linking to a file URL we can trivially find the file name
136 PWSTR fn = PathFindFileName(fsPathBuffer);
137 if (fn && fn != fsPathBuffer)
138 lstrcpyn(fsPathBuffer, fn, lstrlen(fn) + 1);
140 // The filename for any content based drag should be the last element of
141 // the path. If we can't find it, or we're coming up with the name for a link
142 // we just use the entire url.
143 KURL kurl(url.deprecatedString());
144 String lastComponent;
145 if (!isLink && !(lastComponent = kurl.lastPathComponent()).isEmpty()) {
146 len = min<DWORD>(MAX_PATH, lastComponent.length());
147 CopyMemory(fsPathBuffer, lastComponent.characters(), len * sizeof(UChar));
149 len = min<DWORD>(MAX_PATH, nullTermURL.length());
150 CopyMemory(fsPathBuffer, nullTermURL.characters(), len * sizeof(UChar));
152 fsPathBuffer[len] = 0;
153 pathRemoveBadFSCharacters(fsPathBuffer, len);
158 return String((UChar*)fsPathBuffer);
160 if (!isLink && usedURL) {
161 PathRenameExtension(fsPathBuffer, extension);
162 return String((UChar*)fsPathBuffer);
165 String result((UChar*)fsPathBuffer);
166 result += String((UChar*)extension);
170 static HGLOBAL createGlobalURLContent(const String& url, int estimatedFileSize)
176 char ansiUrl[INTERNET_MAX_URL_LENGTH + 1];
177 // Used to generate the buffer. This is null terminated whereas the fileContents won't be.
178 char contentGenerationBuffer[INTERNET_MAX_URL_LENGTH + ARRAYSIZE(szShellDotUrlTemplate) + 1];
180 if (estimatedFileSize > 0 && estimatedFileSize > ARRAYSIZE(contentGenerationBuffer))
183 int ansiUrlSize = ::WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)url.characters(), url.length(), ansiUrl, ARRAYSIZE(ansiUrl) - 1, 0, 0);
187 ansiUrl[ansiUrlSize] = 0;
189 int fileSize = (int) (ansiUrlSize+strlen(szShellDotUrlTemplate)-2); // -2 to remove the %s
190 ASSERT(estimatedFileSize < 0 || fileSize == estimatedFileSize);
192 memObj = GlobalAlloc(GPTR, fileSize);
196 fileContents = (PSTR)GlobalLock(memObj);
198 sprintf_s(contentGenerationBuffer, ARRAYSIZE(contentGenerationBuffer), szShellDotUrlTemplate, ansiUrl);
199 CopyMemory(fileContents, contentGenerationBuffer, fileSize);
201 GlobalUnlock(memObj);
206 static HGLOBAL createGlobalImageFileContent(SharedBuffer* data)
208 HGLOBAL memObj = GlobalAlloc(GPTR, data->size());
212 char* fileContents = (PSTR)GlobalLock(memObj);
214 CopyMemory(fileContents, data->data(), data->size());
216 GlobalUnlock(memObj);
221 static HGLOBAL createGlobalHDropContent(const KURL& url, String& fileName, SharedBuffer* data)
223 if (fileName.isEmpty() || !data )
226 WCHAR filePath[MAX_PATH];
228 if (url.isLocalFile()) {
229 DeprecatedString path = url.path();
230 // windows does not enjoy a leading slash on paths
233 String localPath = path.ascii();
234 LPCTSTR localPathStr = localPath.charactersWithNullTermination();
235 if (wcslen(localPathStr) + 1 < MAX_PATH)
236 wcscpy_s(filePath, MAX_PATH, localPathStr);
240 WCHAR tempPath[MAX_PATH];
241 WCHAR extension[MAX_PATH];
242 if (!::GetTempPath(ARRAYSIZE(tempPath), tempPath))
244 if (!::PathAppend(tempPath, fileName.charactersWithNullTermination()))
246 LPCWSTR foundExtension = ::PathFindExtension(tempPath);
247 if (foundExtension) {
248 if (wcscpy_s(extension, MAX_PATH, foundExtension))
252 ::PathRemoveExtension(tempPath);
253 for (int i = 1; i < 10000; i++) {
254 if (swprintf_s(filePath, MAX_PATH, TEXT("%s-%d%s"), tempPath, i, extension) == -1)
256 if (!::PathFileExists(filePath))
259 HANDLE tempFileHandle = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
260 if (tempFileHandle == INVALID_HANDLE_VALUE)
263 // Write the data to this temp file.
265 BOOL tempWriteSucceeded = WriteFile(tempFileHandle, data->data(), data->size(), &written, 0);
266 CloseHandle(tempFileHandle);
267 if (!tempWriteSucceeded)
271 SIZE_T dropFilesSize = sizeof(DROPFILES) + (sizeof(WCHAR) * (wcslen(filePath) + 2));
272 HGLOBAL memObj = GlobalAlloc(GHND | GMEM_SHARE, dropFilesSize);
276 DROPFILES* dropFiles = (DROPFILES*) GlobalLock(memObj);
277 dropFiles->pFiles = sizeof(DROPFILES);
278 dropFiles->fWide = TRUE;
279 wcscpy((LPWSTR)(dropFiles + 1), filePath);
280 GlobalUnlock(memObj);
285 static HGLOBAL createGlobalUrlFileDescriptor(const String& url, const String& title, int& /*out*/ estimatedSize)
290 memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
294 FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj);
295 memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR));
297 fgd->fgd[0].dwFlags = FD_FILESIZE;
298 int fileSize = ::WideCharToMultiByte(CP_ACP, 0, url.characters(), url.length(), 0, 0, 0, 0);
299 fileSize += strlen(szShellDotUrlTemplate) - 2; // -2 is for getting rid of %s in the template string
300 fgd->fgd[0].nFileSizeLow = fileSize;
301 estimatedSize = fileSize;
302 fsPath = filesystemPathFromUrlOrTitle(url, title, L".URL", true);
304 if (fsPath.length() <= 0) {
305 GlobalUnlock(memObj);
310 int maxSize = min(fsPath.length(), ARRAYSIZE(fgd->fgd[0].cFileName));
311 CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar));
312 GlobalUnlock(memObj);
318 static HGLOBAL createGlobalImageFileDescriptor(const String& url, const String& title, CachedImage* image)
320 ASSERT_ARG(image, image);
321 ASSERT(image->image()->data());
326 memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
330 FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj);
331 memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR));
333 fgd->fgd[0].dwFlags = FD_FILESIZE;
334 fgd->fgd[0].nFileSizeLow = image->image()->data()->size();
336 String extension(".");
337 extension += WebCore::MIMETypeRegistry::getPreferredExtensionForMIMEType(image->response().mimeType());
338 const String& preferredTitle = title.isEmpty() ? image->response().suggestedFilename() : title;
339 fsPath = filesystemPathFromUrlOrTitle(url, preferredTitle, extension.length() ? (TCHAR*)extension.charactersWithNullTermination() : 0, false);
341 if (fsPath.length() <= 0) {
342 GlobalUnlock(memObj);
347 int maxSize = min(fsPath.length(), ARRAYSIZE(fgd->fgd[0].cFileName));
348 CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar));
349 GlobalUnlock(memObj);
355 // writeFileToDataObject takes ownership of fileDescriptor and fileContent
356 static HRESULT writeFileToDataObject(IDataObject* dataObject, HGLOBAL fileDescriptor, HGLOBAL fileContent, HGLOBAL hDropContent)
360 STGMEDIUM medium = {0};
361 medium.tymed = TYMED_HGLOBAL;
363 if (!fileDescriptor || !fileContent)
367 fe = fileDescriptorFormat();
369 medium.hGlobal = fileDescriptor;
371 if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
375 fe = fileContentFormatZero();
376 medium.hGlobal = fileContent;
377 if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
382 medium.hGlobal = hDropContent;
383 hr = dataObject->SetData(cfHDropFormat(), &medium, TRUE);
389 GlobalFree(fileDescriptor);
391 GlobalFree(fileContent);
393 GlobalFree(hDropContent);
398 ClipboardWin::ClipboardWin(bool isForDragging, IDataObject* dataObject, ClipboardAccessPolicy policy)
399 : Clipboard(policy, isForDragging)
400 , m_dataObject(dataObject)
401 , m_writableDataObject(0)
405 ClipboardWin::ClipboardWin(bool isForDragging, WCDataObject* dataObject, ClipboardAccessPolicy policy)
406 : Clipboard(policy, isForDragging)
407 , m_dataObject(dataObject)
408 , m_writableDataObject(dataObject)
412 ClipboardWin::~ClipboardWin()
416 static bool writeURL(WCDataObject *data, const KURL& url, String title, bool withPlainText, bool withHTML)
423 if (title.isEmpty()) {
424 title = url.lastPathComponent();
429 STGMEDIUM medium = {0};
430 medium.tymed = TYMED_HGLOBAL;
432 medium.hGlobal = createGlobalData(url, title);
433 bool success = false;
434 if (medium.hGlobal && FAILED(data->SetData(urlWFormat(), &medium, TRUE)))
435 ::GlobalFree(medium.hGlobal);
440 medium.hGlobal = createGlobalData(markupToCF_HTML(urlToMarkup(url, title), ""));
441 if (medium.hGlobal && FAILED(data->SetData(htmlFormat(), &medium, TRUE)))
442 ::GlobalFree(medium.hGlobal);
448 medium.hGlobal = createGlobalData(url.string());
449 if (medium.hGlobal && FAILED(data->SetData(plainTextWFormat(), &medium, TRUE)))
450 ::GlobalFree(medium.hGlobal);
458 void ClipboardWin::clearData(const String& type)
460 //FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
461 ASSERT(isForDragging());
462 if (policy() != ClipboardWritable || !m_writableDataObject)
465 ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
467 if (dataType == ClipboardDataTypeURL) {
468 m_writableDataObject->clearData(urlWFormat()->cfFormat);
469 m_writableDataObject->clearData(urlFormat()->cfFormat);
471 if (dataType == ClipboardDataTypeText) {
472 m_writableDataObject->clearData(plainTextFormat()->cfFormat);
473 m_writableDataObject->clearData(plainTextWFormat()->cfFormat);
478 void ClipboardWin::clearAllData()
480 //FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
481 ASSERT(isForDragging());
482 if (policy() != ClipboardWritable)
485 m_writableDataObject = 0;
486 WCDataObject::createInstance(&m_writableDataObject);
487 m_dataObject = m_writableDataObject;
490 String ClipboardWin::getData(const String& type, bool& success) const
493 if (policy() != ClipboardReadable || !m_dataObject) {
497 ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
498 if (dataType == ClipboardDataTypeText)
499 return getPlainText(m_dataObject.get(), success);
500 else if (dataType == ClipboardDataTypeURL)
501 return getURL(m_dataObject.get(), success);
506 bool ClipboardWin::setData(const String &type, const String &data)
508 // FIXME: Need to be able to write to the system clipboard <rdar://problem/5015941>
509 ASSERT(isForDragging());
510 if (policy() != ClipboardWritable || !m_writableDataObject)
513 ClipboardDataType winType = clipboardTypeFromMIMEType(type);
515 if (winType == ClipboardDataTypeURL)
516 return WebCore::writeURL(m_writableDataObject.get(), data.deprecatedString(), String(), false, true);
518 if (winType == ClipboardDataTypeText) {
519 STGMEDIUM medium = {0};
520 medium.tymed = TYMED_HGLOBAL;
521 medium.hGlobal = createGlobalData(data);
525 if (FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE))) {
526 ::GlobalFree(medium.hGlobal);
535 static void addMimeTypesForFormat(HashSet<String>& results, FORMATETC& format)
537 // URL and Text are provided for compatibility with IE's model
538 if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat) {
540 results.add("text/uri-list");
543 if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat) {
545 results.add("text/plain");
549 // extensions beyond IE's API
550 HashSet<String> ClipboardWin::types() const
552 HashSet<String> results;
553 if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
559 COMPtr<IEnumFORMATETC> itr;
561 if (FAILED(m_dataObject->EnumFormatEtc(0, &itr)))
569 while (SUCCEEDED(itr->Next(1, &data, 0))) {
570 addMimeTypesForFormat(results, data);
576 void ClipboardWin::setDragImage(CachedImage* image, Node *node, const IntPoint &loc)
578 if (policy() != ClipboardImageWritable && policy() != ClipboardWritable)
582 m_dragImage->deref(this);
585 m_dragImage->ref(this);
588 m_dragImageElement = node;
591 void ClipboardWin::setDragImage(CachedImage* img, const IntPoint &loc)
593 setDragImage(img, 0, loc);
596 void ClipboardWin::setDragImageElement(Node *node, const IntPoint &loc)
598 setDragImage(0, node, loc);
601 DragImageRef ClipboardWin::createDragImage(IntPoint& loc) const
604 //FIXME: Need to be able to draw element <rdar://problem/5015942>
606 result = createDragImageFromImage(m_dragImage->image());
612 static String imageToMarkup(const String& url)
614 String markup("<img src=\"");
616 markup.append("\"/>");
620 static CachedImage* getCachedImage(Element* element)
622 // Attempt to pull CachedImage from element
624 RenderObject* renderer = element->renderer();
625 if (!renderer || !renderer->isImage())
628 RenderImage* image = static_cast<RenderImage*>(renderer);
629 if (image->cachedImage() && !image->cachedImage()->errorOccurred())
630 return image->cachedImage();
635 static void writeImageToDataObject(IDataObject* dataObject, Element* element, const KURL& url)
637 // Shove image data into a DataObject for use as a file
638 CachedImage* cachedImage = getCachedImage(element);
639 if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded())
642 SharedBuffer* imageBuffer = cachedImage->image()->data();
643 if (!imageBuffer || !imageBuffer->size())
646 HGLOBAL imageFileDescriptor = createGlobalImageFileDescriptor(url.string(), element->getAttribute(altAttr), cachedImage);
647 if (!imageFileDescriptor)
650 HGLOBAL imageFileContent = createGlobalImageFileContent(imageBuffer);
651 if (!imageFileContent) {
652 GlobalFree(imageFileDescriptor);
656 String fileName = cachedImage->response().suggestedFilename();
657 HGLOBAL hDropContent = createGlobalHDropContent(url, fileName, imageBuffer);
659 GlobalFree(hDropContent);
663 writeFileToDataObject(dataObject, imageFileDescriptor, imageFileContent, hDropContent);
666 void ClipboardWin::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
668 // Order is important here for Explorer's sake
669 if (!m_writableDataObject)
671 WebCore::writeURL(m_writableDataObject.get(), url, title, true, false);
673 writeImageToDataObject(m_writableDataObject.get(), element, url);
675 AtomicString imageURL = element->getAttribute(srcAttr);
676 if (imageURL.isEmpty())
679 String fullURL = frame->document()->completeURL(parseURL(imageURL));
680 if (fullURL.isEmpty())
682 STGMEDIUM medium = {0};
683 medium.tymed = TYMED_HGLOBAL;
684 ExceptionCode ec = 0;
686 // Put img tag on the clipboard referencing the image
687 medium.hGlobal = createGlobalData(markupToCF_HTML(imageToMarkup(fullURL), ""));
688 if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
689 ::GlobalFree(medium.hGlobal);
692 void ClipboardWin::writeURL(const KURL& kurl, const String& titleStr, Frame*)
694 if (!m_writableDataObject)
696 WebCore::writeURL(m_writableDataObject.get(), kurl, titleStr, true, true);
698 int estimatedSize = 0;
699 String url = kurl.string();
701 HGLOBAL urlFileDescriptor = createGlobalUrlFileDescriptor(url, titleStr, estimatedSize);
702 if (!urlFileDescriptor)
704 HGLOBAL urlFileContent = createGlobalURLContent(url, estimatedSize);
705 if (!urlFileContent) {
706 GlobalFree(urlFileDescriptor);
709 writeFileToDataObject(m_writableDataObject.get(), urlFileDescriptor, urlFileContent, 0);
712 void ClipboardWin::writeRange(Range* selectedRange, Frame* frame)
714 ASSERT(selectedRange);
715 if (!m_writableDataObject)
718 STGMEDIUM medium = {0};
719 medium.tymed = TYMED_HGLOBAL;
720 ExceptionCode ec = 0;
722 medium.hGlobal = createGlobalData(markupToCF_HTML(createMarkup(selectedRange, 0, AnnotateForInterchange), selectedRange->startContainer(ec)->document()->url()));
723 if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
724 ::GlobalFree(medium.hGlobal);
726 String str = frame->selectedText();
727 replaceNewlinesWithWindowsStyleNewlines(str);
728 replaceNBSPWithSpace(str);
729 medium.hGlobal = createGlobalData(str);
730 if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
731 ::GlobalFree(medium.hGlobal);
734 if (frame->editor()->canSmartCopyOrDelete())
735 m_writableDataObject->SetData(smartPasteFormat(), &medium, TRUE);
738 bool ClipboardWin::hasData()
743 COMPtr<IEnumFORMATETC> itr;
744 if (FAILED(m_dataObject->EnumFormatEtc(0, &itr)))
752 if (SUCCEEDED(itr->Next(1, &data, 0))) {
753 // There is at least one item in the IDataObject
760 } // namespace WebCore