/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include "DOTransferable.hxx" #include "../misc/ImplHelper.hxx" #include #include "DTransHelper.hxx" #include "TxtCnvtHlp.hxx" #include "MimeAttrib.hxx" #include "FmtFilter.hxx" #include "Fetc.hxx" #include #include #include #include #include using namespace std; using namespace osl; using namespace cppu; using namespace com::sun::star::uno; using namespace com::sun::star::datatransfer; using namespace com::sun::star::io; using namespace com::sun::star::lang; using namespace com::sun::star::container; namespace { const Type CPPUTYPE_SEQINT8 = cppu::UnoType>::get(); const Type CPPUTYPE_OUSTRING = cppu::UnoType::get(); bool isValidFlavor( const DataFlavor& aFlavor ) { return ( aFlavor.MimeType.getLength( ) && ( ( aFlavor.DataType == CPPUTYPE_SEQINT8 ) || ( aFlavor.DataType == CPPUTYPE_OUSTRING ) ) ); } void clipDataToByteStream( CLIPFORMAT cf, STGMEDIUM stgmedium, CDOTransferable::ByteSequence_t& aByteSequence ) { CStgTransferHelper memTransferHelper; LPSTREAM pStream = nullptr; switch( stgmedium.tymed ) { case TYMED_HGLOBAL: memTransferHelper.init( stgmedium.hGlobal ); break; case TYMED_MFPICT: memTransferHelper.init( stgmedium.hMetaFilePict ); break; case TYMED_ENHMF: memTransferHelper.init( stgmedium.hEnhMetaFile ); break; case TYMED_ISTREAM: pStream = stgmedium.pstm; break; default: throw UnsupportedFlavorException( ); break; } if (pStream) { // We have a stream, read from it. STATSTG aStat; HRESULT hr = pStream->Stat(&aStat, STATFLAG_NONAME); if (FAILED(hr)) { SAL_WARN("dtrans", "clipDataToByteStream: Stat() failed"); return; } size_t nMemSize = aStat.cbSize.QuadPart; aByteSequence.realloc(nMemSize); LARGE_INTEGER li; li.QuadPart = 0; hr = pStream->Seek(li, STREAM_SEEK_SET, nullptr); if (FAILED(hr)) { SAL_WARN("dtrans", "clipDataToByteStream: Seek() failed"); } ULONG nRead = 0; hr = pStream->Read(aByteSequence.getArray(), nMemSize, &nRead); if (FAILED(hr)) { SAL_WARN("dtrans", "clipDataToByteStream: Read() failed"); } if (nRead < nMemSize) { SAL_WARN("dtrans", "clipDataToByteStream: Read() was partial"); } return; } int nMemSize = memTransferHelper.memSize( cf ); aByteSequence.realloc( nMemSize ); memTransferHelper.read( aByteSequence.getArray( ), nMemSize ); } OUString byteStreamToOUString( CDOTransferable::ByteSequence_t& aByteStream ) { sal_Int32 nWChars; sal_Int32 nMemSize = aByteStream.getLength( ); // if there is a trailing L"\0" subtract 1 from length // for unknown reason, the sequence may sometimes arrive empty if ( aByteStream.getLength( ) > 1 && 0 == aByteStream[ aByteStream.getLength( ) - 2 ] && 0 == aByteStream[ aByteStream.getLength( ) - 1 ] ) nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ) - 1; else nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ); return OUString( reinterpret_cast< sal_Unicode* >( aByteStream.getArray( ) ), nWChars ); } Any byteStreamToAny( CDOTransferable::ByteSequence_t& aByteStream, const Type& aRequestedDataType ) { Any aAny; if ( aRequestedDataType == CPPUTYPE_OUSTRING ) { OUString str = byteStreamToOUString( aByteStream ); if (str.isEmpty()) throw RuntimeException(); aAny <<= str; } else aAny <<= aByteStream; return aAny; } bool cmpFullMediaType( const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) { return xLhs->getFullMediaType().equalsIgnoreAsciiCase( xRhs->getFullMediaType( ) ); } bool cmpAllContentTypeParameter( const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) { Sequence< OUString > xLhsFlavors = xLhs->getParameters( ); Sequence< OUString > xRhsFlavors = xRhs->getParameters( ); bool bRet = true; try { if ( xLhsFlavors.getLength( ) == xRhsFlavors.getLength( ) ) { OUString pLhs; OUString pRhs; for ( sal_Int32 i = 0; i < xLhsFlavors.getLength( ); i++ ) { pLhs = xLhs->getParameterValue( xLhsFlavors[i] ); pRhs = xRhs->getParameterValue( xLhsFlavors[i] ); if ( !pLhs.equalsIgnoreAsciiCase( pRhs ) ) { bRet = false; break; } } } else bRet = false; } catch( NoSuchElementException& ) { bRet = false; } catch( IllegalArgumentException& ) { bRet = false; } return bRet; } } // end namespace Reference< XTransferable > CDOTransferable::create( const Reference< XComponentContext >& rxContext, IDataObjectPtr pIDataObject ) { CDOTransferable* pTransf = new CDOTransferable(rxContext, pIDataObject); Reference refDOTransf(pTransf); pTransf->acquire(); pTransf->initFlavorList(); pTransf->release(); return refDOTransf; } CDOTransferable::CDOTransferable( const Reference< XComponentContext >& rxContext, IDataObjectPtr rDataObject ) : m_rDataObject( rDataObject ), m_xContext( rxContext ), m_DataFormatTranslator( rxContext ), m_bUnicodeRegistered( false ), m_TxtFormatOnClipboard( CF_INVALID ) { } Any SAL_CALL CDOTransferable::getTransferData( const DataFlavor& aFlavor ) { OSL_ASSERT( isValidFlavor( aFlavor ) ); MutexGuard aGuard( m_aMutex ); // convert dataflavor to formatetc CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor ); OSL_ASSERT( CF_INVALID != fetc.getClipformat() ); // get the data from clipboard in a byte stream ByteSequence_t clipDataStream; try { clipDataStream = getClipboardData( fetc ); } catch( UnsupportedFlavorException& ) { if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat( ) ) && m_bUnicodeRegistered ) { OUString aUnicodeText = synthesizeUnicodeText( ); Any aAny = makeAny( aUnicodeText ); return aAny; } // #i124085# CF_DIBV5 should not be possible, but keep for reading from the // clipboard for being on the safe side else if(CF_DIBV5 == fetc.getClipformat()) { // #i123407# CF_DIBV5 has priority; if the try to fetch this failed, // check CF_DIB availability as an alternative fetc.setClipformat(CF_DIB); clipDataStream = getClipboardData( fetc ); // pass UnsupportedFlavorException out, tried all possibilities } else throw; // pass through exception } // return the data as any return byteStreamToAny( clipDataStream, aFlavor.DataType ); } // getTransferDataFlavors Sequence< DataFlavor > SAL_CALL CDOTransferable::getTransferDataFlavors( ) { return m_FlavorList; } // isDataFlavorSupported // returns true if we find a DataFlavor with the same MimeType and // DataType sal_Bool SAL_CALL CDOTransferable::isDataFlavorSupported( const DataFlavor& aFlavor ) { OSL_ASSERT( isValidFlavor( aFlavor ) ); for ( DataFlavor const & df : std::as_const(m_FlavorList) ) if ( compareDataFlavors( aFlavor, df ) ) return true; return false; } // the list of dataflavors currently on the clipboard will be initialized // only once; if the client of this Transferable will hold a reference // to it and the underlying clipboard content changes, the client does // possible operate on an invalid list // if there is only text on the clipboard we will also offer unicode text // an synthesize this format on the fly if requested, to accomplish this // we save the first offered text format which we will later use for the // conversion void CDOTransferable::initFlavorList( ) { sal::systools::COMReference pEnumFormatEtc; HRESULT hr = m_rDataObject->EnumFormatEtc( DATADIR_GET, &pEnumFormatEtc ); if ( SUCCEEDED( hr ) ) { pEnumFormatEtc->Reset( ); FORMATETC fetc; while ( S_OK == pEnumFormatEtc->Next( 1, &fetc, nullptr ) ) { // we use locales only to determine the // charset if there is text on the cliboard // we don't offer this format if ( CF_LOCALE == fetc.cfFormat ) continue; DataFlavor aFlavor = formatEtcToDataFlavor( fetc ); // if text or oemtext is offered we also pretend to have unicode text if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc.cfFormat ) && !m_bUnicodeRegistered ) { addSupportedFlavor( aFlavor ); m_TxtFormatOnClipboard = fetc.cfFormat; m_bUnicodeRegistered = true; // register unicode text as accompany format aFlavor = formatEtcToDataFlavor( CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT ) ); addSupportedFlavor( aFlavor ); } else if ( (CF_UNICODETEXT == fetc.cfFormat) && !m_bUnicodeRegistered ) { addSupportedFlavor( aFlavor ); m_bUnicodeRegistered = true; } else addSupportedFlavor( aFlavor ); // see MSDN IEnumFORMATETC CoTaskMemFree( fetc.ptd ); } } } inline void CDOTransferable::addSupportedFlavor( const DataFlavor& aFlavor ) { // we ignore all formats that couldn't be translated if ( aFlavor.MimeType.getLength( ) ) { OSL_ASSERT( isValidFlavor( aFlavor ) ); m_FlavorList.realloc( m_FlavorList.getLength( ) + 1 ); m_FlavorList[m_FlavorList.getLength( ) - 1] = aFlavor; } } DataFlavor CDOTransferable::formatEtcToDataFlavor( const FORMATETC& aFormatEtc ) { LCID lcid = 0; // for non-unicode text format we must provide a locale to get // the character-set of the text, if there is no locale on the // clipboard we assume the text is in a charset appropriate for // the current thread locale if ( (CF_TEXT == aFormatEtc.cfFormat) || (CF_OEMTEXT == aFormatEtc.cfFormat) ) lcid = getLocaleFromClipboard( ); return m_DataFormatTranslator.getDataFlavorFromFormatEtc( aFormatEtc, lcid ); } // returns the current locale on clipboard; if there is no locale on // clipboard the function returns the current thread locale LCID CDOTransferable::getLocaleFromClipboard( ) { LCID lcid = GetThreadLocale( ); try { CFormatEtc fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE ); ByteSequence_t aLCIDSeq = getClipboardData( fetc ); lcid = *reinterpret_cast( aLCIDSeq.getArray( ) ); // because of a Win95/98 Bug; there the high word // of a locale has the same value as the // low word e.g. 0x07040704 that's not right // correct is 0x00000704 lcid &= 0x0000FFFF; } catch(...) { // we take the default locale } return lcid; } // I think it's not necessary to call ReleaseStgMedium // in case of failures because nothing should have been // allocated etc. CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc ) { STGMEDIUM stgmedium; HRESULT hr = m_rDataObject->GetData( aFormatEtc, &stgmedium ); // in case of failure to get a WMF metafile handle, try to get a memory block if( FAILED( hr ) && ( CF_METAFILEPICT == aFormatEtc.getClipformat() ) && ( TYMED_MFPICT == aFormatEtc.getTymed() ) ) { CFormatEtc aTempFormat( aFormatEtc ); aTempFormat.setTymed( TYMED_HGLOBAL ); hr = m_rDataObject->GetData( aTempFormat, &stgmedium ); } if (FAILED(hr) && aFormatEtc.getTymed() == TYMED_HGLOBAL) { // Handle type is not memory, try stream. CFormatEtc aTempFormat(aFormatEtc); aTempFormat.setTymed(TYMED_ISTREAM); hr = m_rDataObject->GetData(aTempFormat, &stgmedium); } if ( FAILED( hr ) ) { OSL_ASSERT( (hr != E_INVALIDARG) && (hr != DV_E_DVASPECT) && (hr != DV_E_LINDEX) && (hr != DV_E_TYMED) ); if ( DV_E_FORMATETC == hr ) throw UnsupportedFlavorException( ); else if ( STG_E_MEDIUMFULL == hr ) throw IOException( ); else throw RuntimeException( ); } ByteSequence_t byteStream; try { if ( CF_ENHMETAFILE == aFormatEtc.getClipformat() ) byteStream = WinENHMFPictToOOMFPict( stgmedium.hEnhMetaFile ); else if (CF_HDROP == aFormatEtc.getClipformat()) byteStream = CF_HDROPToFileList(stgmedium.hGlobal); else if ( CF_BITMAP == aFormatEtc.getClipformat() ) { byteStream = WinBITMAPToOOBMP(stgmedium.hBitmap); if( aFormatEtc.getTymed() == TYMED_GDI && ! stgmedium.pUnkForRelease ) { DeleteObject(stgmedium.hBitmap); } } else { clipDataToByteStream( aFormatEtc.getClipformat( ), stgmedium, byteStream ); // format conversion if necessary // #i124085# DIBV5 should not happen currently, but keep as a hint here if(CF_DIBV5 == aFormatEtc.getClipformat() || CF_DIB == aFormatEtc.getClipformat()) { byteStream = WinDIBToOOBMP(byteStream); } else if(CF_METAFILEPICT == aFormatEtc.getClipformat()) { byteStream = WinMFPictToOOMFPict(byteStream); } } ReleaseStgMedium( &stgmedium ); } catch( CStgTransferHelper::CStgTransferException& ) { ReleaseStgMedium( &stgmedium ); throw IOException( ); } return byteStream; } OUString CDOTransferable::synthesizeUnicodeText( ) { ByteSequence_t aTextSequence; CFormatEtc fetc; LCID lcid = getLocaleFromClipboard( ); sal_uInt32 cpForTxtCnvt = 0; if ( CF_TEXT == m_TxtFormatOnClipboard ) { fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ); aTextSequence = getClipboardData( fetc ); // determine the codepage used for text conversion cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTANSICODEPAGE ).toInt32( ); } else if ( CF_OEMTEXT == m_TxtFormatOnClipboard ) { fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT ); aTextSequence = getClipboardData( fetc ); // determine the codepage used for text conversion cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTCODEPAGE ).toInt32( ); } else OSL_ASSERT( false ); CStgTransferHelper stgTransferHelper; // convert the text MultiByteToWideCharEx( cpForTxtCnvt, reinterpret_cast( aTextSequence.getArray( ) ), sal::static_int_cast(-1), // Huh ? stgTransferHelper, false); CRawHGlobalPtr ptrHGlob(stgTransferHelper); sal_Unicode* pWChar = static_cast(ptrHGlob.GetMemPtr()); return OUString(pWChar); } bool CDOTransferable::compareDataFlavors( const DataFlavor& lhs, const DataFlavor& rhs ) { if ( !m_rXMimeCntFactory.is( ) ) { m_rXMimeCntFactory = MimeContentTypeFactory::create( m_xContext ); } bool bRet = false; try { Reference< XMimeContentType > xLhs( m_rXMimeCntFactory->createMimeContentType( lhs.MimeType ) ); Reference< XMimeContentType > xRhs( m_rXMimeCntFactory->createMimeContentType( rhs.MimeType ) ); if ( cmpFullMediaType( xLhs, xRhs ) ) { bRet = cmpAllContentTypeParameter( xLhs, xRhs ); } } catch( IllegalArgumentException& ) { OSL_FAIL( "Invalid content type detected" ); bRet = false; } return bRet; } css::uno::Any SAL_CALL CDOTransferable::getData( const Sequence< sal_Int8>& aProcessId ) { Any retVal; sal_Int8 const * arProcCaller= aProcessId.getConstArray(); sal_uInt8 arId[16]; rtl_getGlobalProcessId(arId); if( ! memcmp( arId, arProcCaller,16)) { if (m_rDataObject.is()) { IDataObject* pObj= m_rDataObject.get(); pObj->AddRef(); retVal.setValue( &pObj, cppu::UnoType::get()); } } return retVal; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */