/* * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "ClipboardWin.h" #include "CachedImage.h" #include "ClipboardUtilitiesWin.h" #include "csshelper.h" #include "CString.h" #include "DeprecatedString.h" #include "Document.h" #include "DragData.h" #include "Element.h" #include "EventHandler.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameView.h" #include "HTMLNames.h" #include "Image.h" #include "MIMETypeRegistry.h" #include "markup.h" #include "Page.h" #include "Pasteboard.h" #include "PlatformMouseEvent.h" #include "PlatformString.h" #include "Range.h" #include "RenderImage.h" #include "ResourceResponse.h" #include "StringHash.h" #include "WCDataObject.h" #include #include #include namespace WebCore { using namespace HTMLNames; // format string for static const char szShellDotUrlTemplate[] = "[InternetShortcut]\r\nURL=%s\r\n"; // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3 enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText }; static ClipboardDataType clipboardTypeFromMIMEType(const String& type) { String qType = type.stripWhiteSpace().lower(); // two special cases for IE compatibility if (qType == "text" || qType == "text/plain" || qType.startsWith("text/plain;")) return ClipboardDataTypeText; if (qType == "url" || qType == "text/uri-list") return ClipboardDataTypeURL; return ClipboardDataTypeNone; } static inline FORMATETC* fileDescriptorFormat() { static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); static FORMATETC fileDescriptorFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; return &fileDescriptorFormat; } static inline FORMATETC* fileContentFormatZero() { static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS); static FORMATETC fileContentFormat = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; return &fileContentFormat; } static inline void pathRemoveBadFSCharacters(PWSTR psz, size_t length) { size_t writeTo = 0; size_t readFrom = 0; while (readFrom < length) { UINT type = PathGetCharType(psz[readFrom]); if (psz[readFrom] == 0 || type & (GCT_LFNCHAR | GCT_SHORTCHAR)) { psz[writeTo++] = psz[readFrom]; } readFrom++; } psz[writeTo] = 0; } static String filesystemPathFromUrlOrTitle(const String& url, const String& title, TCHAR* extension, bool isLink) { bool usedURL = false; WCHAR fsPathBuffer[MAX_PATH + 1]; fsPathBuffer[0] = 0; int extensionLen = extension ? lstrlen(extension) : 0; if (!title.isEmpty()) { size_t len = min(title.length(), MAX_PATH - extensionLen); CopyMemory(fsPathBuffer, title.characters(), len * sizeof(UChar)); fsPathBuffer[len] = 0; pathRemoveBadFSCharacters(fsPathBuffer, len); } if (!lstrlen(fsPathBuffer)) { DWORD len = MAX_PATH; String nullTermURL = url; usedURL = true; if (UrlIsFileUrl((LPCWSTR)nullTermURL.charactersWithNullTermination()) && SUCCEEDED(PathCreateFromUrl((LPCWSTR)nullTermURL.charactersWithNullTermination(), fsPathBuffer, &len, 0))) { // When linking to a file URL we can trivially find the file name PWSTR fn = PathFindFileName(fsPathBuffer); if (fn && fn != fsPathBuffer) lstrcpyn(fsPathBuffer, fn, lstrlen(fn) + 1); } else { // The filename for any content based drag should be the last element of // the path. If we can't find it, or we're coming up with the name for a link // we just use the entire url. KURL kurl(url.deprecatedString()); String lastComponent; if (!isLink && !(lastComponent = kurl.lastPathComponent()).isEmpty()) { len = min(MAX_PATH, lastComponent.length()); CopyMemory(fsPathBuffer, lastComponent.characters(), len * sizeof(UChar)); } else { len = min(MAX_PATH, nullTermURL.length()); CopyMemory(fsPathBuffer, nullTermURL.characters(), len * sizeof(UChar)); } fsPathBuffer[len] = 0; pathRemoveBadFSCharacters(fsPathBuffer, len); } } if (!extension) return String((UChar*)fsPathBuffer); if (!isLink && usedURL) { PathRenameExtension(fsPathBuffer, extension); return String((UChar*)fsPathBuffer); } String result((UChar*)fsPathBuffer); result += String((UChar*)extension); return result; } static HGLOBAL createGlobalURLContent(const String& url, int estimatedFileSize) { HRESULT hr = S_OK; HGLOBAL memObj = 0; char* fileContents; char ansiUrl[INTERNET_MAX_URL_LENGTH + 1]; // Used to generate the buffer. This is null terminated whereas the fileContents won't be. char contentGenerationBuffer[INTERNET_MAX_URL_LENGTH + ARRAYSIZE(szShellDotUrlTemplate) + 1]; if (estimatedFileSize > 0 && estimatedFileSize > ARRAYSIZE(contentGenerationBuffer)) return 0; int ansiUrlSize = ::WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)url.characters(), url.length(), ansiUrl, ARRAYSIZE(ansiUrl) - 1, 0, 0); if (!ansiUrlSize) return 0; ansiUrl[ansiUrlSize] = 0; int fileSize = (int) (ansiUrlSize+strlen(szShellDotUrlTemplate)-2); // -2 to remove the %s ASSERT(estimatedFileSize < 0 || fileSize == estimatedFileSize); memObj = GlobalAlloc(GPTR, fileSize); if (!memObj) return 0; fileContents = (PSTR)GlobalLock(memObj); sprintf_s(contentGenerationBuffer, ARRAYSIZE(contentGenerationBuffer), szShellDotUrlTemplate, ansiUrl); CopyMemory(fileContents, contentGenerationBuffer, fileSize); GlobalUnlock(memObj); return memObj; } static HGLOBAL createGlobalImageFileContent(SharedBuffer* data) { HRESULT hr = S_OK; HGLOBAL memObj = GlobalAlloc(GPTR, data->size()); if (!memObj) return 0; char* fileContents = (PSTR)GlobalLock(memObj); CopyMemory(fileContents, data->data(), data->size()); GlobalUnlock(memObj); return memObj; } static HGLOBAL createGlobalUrlFileDescriptor(const String& url, const String& title, int& /*out*/ estimatedSize) { HRESULT hr = S_OK; HGLOBAL memObj = 0; String fsPath; memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR)); if (!memObj) return 0; FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj); memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR)); fgd->cItems = 1; fgd->fgd[0].dwFlags = FD_FILESIZE; int fileSize = ::WideCharToMultiByte(CP_ACP, 0, url.characters(), url.length(), 0, 0, 0, 0); fileSize += strlen(szShellDotUrlTemplate) - 2; // -2 is for getting rid of %s in the template string fgd->fgd[0].nFileSizeLow = fileSize; estimatedSize = fileSize; fsPath = filesystemPathFromUrlOrTitle(url, title, L".URL", true); if (fsPath.length() <= 0) { GlobalUnlock(memObj); GlobalFree(memObj); return 0; } int maxSize = min(fsPath.length(), ARRAYSIZE(fgd->fgd[0].cFileName)); CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar)); GlobalUnlock(memObj); return memObj; } static HGLOBAL createGlobalImageFileDescriptor(const String& url, const String& title, CachedImage* image) { HRESULT hr = S_OK; HGLOBAL memObj = 0; String fsPath; memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR)); if (!memObj) return 0; FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj); memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR)); fgd->cItems = 1; fgd->fgd[0].dwFlags = FD_FILESIZE; fgd->fgd[0].nFileSizeLow = image->image()->data()->size(); String extension("."); extension += WebCore::MIMETypeRegistry::getPreferredExtensionForMIMEType(image->response().mimeType()); const String& preferredTitle = title.isEmpty() ? image->response().suggestedFilename() : title; fsPath = filesystemPathFromUrlOrTitle(url, preferredTitle, extension.length() ? (TCHAR*)extension.charactersWithNullTermination() : 0, false); if (fsPath.length() <= 0) { GlobalUnlock(memObj); GlobalFree(memObj); return 0; } int maxSize = min(fsPath.length(), ARRAYSIZE(fgd->fgd[0].cFileName)); CopyMemory(fgd->fgd[0].cFileName, (LPCWSTR)fsPath.characters(), maxSize * sizeof(UChar)); GlobalUnlock(memObj); return memObj; } // writeFileToDataObject takes ownership of fileDescriptor and fileContent static HRESULT writeFileToDataObject(IDataObject* dataObject, HGLOBAL fileDescriptor, HGLOBAL fileContent) { HRESULT hr = S_OK; FORMATETC* fe; STGMEDIUM medium = {0}; medium.tymed = TYMED_HGLOBAL; if (!fileDescriptor || !fileContent) goto exit; // Descriptor fe = fileDescriptorFormat(); medium.hGlobal = fileDescriptor; if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE))) goto exit; // Contents fe = fileContentFormatZero(); medium.hGlobal = fileContent; hr = dataObject->SetData(fe, &medium, TRUE); exit: if (FAILED(hr)) { if (fileDescriptor) GlobalFree(fileDescriptor); if (fileContent) GlobalFree(fileContent); } return hr; } ClipboardWin::ClipboardWin(bool isForDragging, IDataObject* dataObject, ClipboardAccessPolicy policy) : Clipboard(policy, isForDragging) , m_dataObject(dataObject) , m_writableDataObject(0) { } ClipboardWin::ClipboardWin(bool isForDragging, WCDataObject* dataObject, ClipboardAccessPolicy policy) : Clipboard(policy, isForDragging) , m_dataObject(dataObject) , m_writableDataObject(dataObject) { } ClipboardWin::~ClipboardWin() { } static bool writeURL(WCDataObject *data, const KURL& url, String title, bool withPlainText, bool withHTML) { ASSERT(data); ASSERT(!url.isEmpty()); if (!url.isValid()) return false; if (title.isEmpty()) { title = url.lastPathComponent(); if (title.isEmpty()) title = url.host(); } STGMEDIUM medium = {0}; medium.tymed = TYMED_HGLOBAL; medium.hGlobal = createGlobalData(url, title); bool success = false; if (medium.hGlobal && FAILED(data->SetData(urlWFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); else success = true; if (withHTML) { medium.hGlobal = createGlobalData(markupToCF_HTML(urlToMarkup(url, title), "")); if (medium.hGlobal && FAILED(data->SetData(htmlFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); else success = true; } if (withPlainText) { medium.hGlobal = createGlobalData(url.url()); if (medium.hGlobal && FAILED(data->SetData(plainTextWFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); else success = true; } return success; } void ClipboardWin::clearData(const String& type) { //FIXME: Need to be able to write to the system clipboard ASSERT(isForDragging()); if (policy() != ClipboardWritable || !m_writableDataObject) return; ClipboardDataType dataType = clipboardTypeFromMIMEType(type); if (dataType == ClipboardDataTypeURL) { m_writableDataObject->clearData(urlWFormat()->cfFormat); m_writableDataObject->clearData(urlFormat()->cfFormat); } if (dataType == ClipboardDataTypeText) { m_writableDataObject->clearData(plainTextFormat()->cfFormat); m_writableDataObject->clearData(plainTextWFormat()->cfFormat); } } void ClipboardWin::clearAllData() { //FIXME: Need to be able to write to the system clipboard ASSERT(isForDragging()); if (policy() != ClipboardWritable) return; m_writableDataObject = 0; WCDataObject::createInstance(&m_writableDataObject); m_dataObject = m_writableDataObject; } String ClipboardWin::getData(const String& type, bool& success) const { success = false; if (policy() != ClipboardReadable || !m_dataObject) { return ""; } ClipboardDataType dataType = clipboardTypeFromMIMEType(type); if (dataType == ClipboardDataTypeText) return getPlainText(m_dataObject.get(), success); else if (dataType == ClipboardDataTypeURL) return getURL(m_dataObject.get(), success); return ""; } bool ClipboardWin::setData(const String &type, const String &data) { //FIXME: Need to be able to write to the system clipboard ASSERT(isForDragging()); if (policy() != ClipboardWritable || !m_writableDataObject) return false; ClipboardDataType winType = clipboardTypeFromMIMEType(type); if (winType == ClipboardDataTypeURL) { KURL url = data.deprecatedString(); if (!url.isValid()) return false; return WebCore::writeURL(m_writableDataObject.get(), url, String(), false, true); } else if ( winType == ClipboardDataTypeText) { STGMEDIUM medium = {0}; medium.tymed = TYMED_HGLOBAL; medium.hGlobal = createGlobalData(data); if (!medium.hGlobal) return false; if (FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE))) { ::GlobalFree(medium.hGlobal); return false; } return true; } return false; } static void addMimeTypesForFormat(HashSet& results, FORMATETC& format) { // URL and Text are provided for compatibility with IE's model if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat) { results.add("URL"); results.add("text/uri-list"); } if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat) { results.add("Text"); results.add("text/plain"); } } // extensions beyond IE's API HashSet ClipboardWin::types() const { HashSet results; if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable) return results; if (!m_dataObject) return results; COMPtr itr; if (FAILED(m_dataObject->EnumFormatEtc(0, &itr))) return results; if (!itr) return results; FORMATETC data; while (SUCCEEDED(itr->Next(1, &data, 0))) { addMimeTypesForFormat(results, data); } return results; } void ClipboardWin::setDragImage(CachedImage* image, Node *node, const IntPoint &loc) { if (policy() != ClipboardImageWritable && policy() != ClipboardWritable) return; if (m_dragImage) m_dragImage->deref(this); m_dragImage = image; if (m_dragImage) m_dragImage->ref(this); m_dragLoc = loc; m_dragImageElement = node; } void ClipboardWin::setDragImage(CachedImage* img, const IntPoint &loc) { setDragImage(img, 0, loc); } void ClipboardWin::setDragImageElement(Node *node, const IntPoint &loc) { setDragImage(0, node, loc); } DragImageRef ClipboardWin::createDragImage(IntPoint& loc) const { HBITMAP result = 0; //FIXME: Need to be able to draw element if (m_dragImage) { result = createDragImageFromImage(m_dragImage->image()); loc = m_dragLoc; } return result; } static String imageToMarkup(const String& url) { String markup(""); return markup; } static CachedImage* getCachedImage(Element* element) { // Attempt to pull CachedImage from element ASSERT(element); RenderObject* renderer = element->renderer(); if (!renderer || !renderer->isImage()) return 0; RenderImage* image = static_cast(renderer); if (image->cachedImage() && !image->cachedImage()->errorOccurred()) return image->cachedImage(); return 0; } static void writeImageToDataObject(IDataObject* dataObject, Element* element, const KURL& url) { // Shove image data into a DataObject for use as a file CachedImage* cachedImage = getCachedImage(element); if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded()) return; SharedBuffer* imageBuffer = cachedImage->image()->data(); if (!imageBuffer->size()) return; HGLOBAL imageFileDescriptor = createGlobalImageFileDescriptor(url.url(), element->getAttribute(altAttr), cachedImage); if (!imageFileDescriptor) return; HGLOBAL imageFileContent = createGlobalImageFileContent(imageBuffer); if (!imageFileContent) { GlobalFree(imageFileDescriptor); return; } writeFileToDataObject(dataObject, imageFileDescriptor, imageFileContent); } void ClipboardWin::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame) { // Order is important here for Explorer's sake if (!m_writableDataObject) return; WebCore::writeURL(m_writableDataObject.get(), url, title, true, false); writeImageToDataObject(m_writableDataObject.get(), element, url); AtomicString imageURL = element->getAttribute(srcAttr); if (imageURL.isEmpty()) return; String fullURL = frame->document()->completeURL(parseURL(imageURL)); if (fullURL.isEmpty()) return; STGMEDIUM medium = {0}; medium.tymed = TYMED_HGLOBAL; ExceptionCode ec = 0; // Put img tag on the clipboard referencing the image medium.hGlobal = createGlobalData(markupToCF_HTML(imageToMarkup(fullURL), "")); if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); } void ClipboardWin::writeURL(const KURL& kurl, const String& titleStr, Frame*) { if (!m_writableDataObject) return; WebCore::writeURL(m_writableDataObject.get(), kurl, titleStr, true, true); int estimatedSize = 0; String url = kurl.url(); HGLOBAL urlFileDescriptor = createGlobalUrlFileDescriptor(url, titleStr, estimatedSize); if (!urlFileDescriptor) return; HGLOBAL urlFileContent = createGlobalURLContent(url, estimatedSize); if (!urlFileContent) { GlobalFree(urlFileDescriptor); return; } writeFileToDataObject(m_writableDataObject.get(), urlFileDescriptor, urlFileContent); } void ClipboardWin::writeRange(Range* selectedRange, Frame* frame) { ASSERT(selectedRange); if (!m_writableDataObject) return; STGMEDIUM medium = {0}; medium.tymed = TYMED_HGLOBAL; ExceptionCode ec = 0; medium.hGlobal = createGlobalData(markupToCF_HTML(createMarkup(selectedRange, 0, AnnotateForInterchange), selectedRange->startContainer(ec)->document()->URL())); if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); String str = frame->selectedText(); replaceNewlinesWithWindowsStyleNewlines(str); replaceNBSPWithSpace(str); medium.hGlobal = createGlobalData(str); if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE))) ::GlobalFree(medium.hGlobal); } bool ClipboardWin::hasData() { if (!m_dataObject) return false; COMPtr itr; if (FAILED(m_dataObject->EnumFormatEtc(0, &itr))) return false; if (!itr) return false; FORMATETC data; if (SUCCEEDED(itr->Next(1, &data, 0))) { // There is at least one item in the IDataObject return true; } return false; } } // namespace WebCore