/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fileobj.hxx" #include "impldde.hxx" #include #include #include #include using ::com::sun::star::uno::UNO_QUERY; using ::com::sun::star::uno::Reference; using ::com::sun::star::lang::XComponent; using ::com::sun::star::util::XCloseable; namespace sfx2 { namespace { class SvxInternalLink : public sfx2::SvLinkSource { public: SvxInternalLink() {} virtual bool Connect( sfx2::SvBaseLink* ) override; }; } LinkManager::LinkManager(SfxObjectShell* p) : pPersist( p ) { } LinkManager::~LinkManager() { for(tools::SvRef & rTmp : aLinkTbl) { if( rTmp.is() ) { rTmp->Disconnect(); rTmp->SetLinkManager( nullptr ); } } } void LinkManager::InsertCachedComp(const Reference& xComp) { maCachedComps.push_back(xComp); } void LinkManager::CloseCachedComps() { for (const auto& rxCachedComp : maCachedComps) { Reference xCloseable(rxCachedComp, UNO_QUERY); if (!xCloseable.is()) continue; xCloseable->close(true); } maCachedComps.clear(); } void LinkManager::Remove( SvBaseLink const *pLink ) { // No duplicate links inserted bool bFound = false; for( size_t n = 0; n < aLinkTbl.size(); ) { tools::SvRef& rTmp = aLinkTbl[ n ]; if( pLink == rTmp.get() ) { rTmp->Disconnect(); rTmp->SetLinkManager( nullptr ); rTmp.clear(); bFound = true; } // Remove empty ones if they exist if( !rTmp.is() ) { aLinkTbl.erase( aLinkTbl.begin() + n ); if( bFound ) return ; } else ++n; } } void LinkManager::Remove( size_t nPos, size_t nCnt ) { if( !nCnt || nPos >= aLinkTbl.size() ) return; if (sal::static_int_cast(nPos + nCnt) > aLinkTbl.size()) nCnt = aLinkTbl.size() - nPos; for( size_t n = nPos; n < nPos + nCnt; ++n) { tools::SvRef& rTmp = aLinkTbl[ n ]; if( rTmp.is() ) { rTmp->Disconnect(); rTmp->SetLinkManager( nullptr ); } } aLinkTbl.erase( aLinkTbl.begin() + nPos, aLinkTbl.begin() + nPos + nCnt ); } bool LinkManager::Insert( SvBaseLink* pLink ) { for( size_t n = 0; n < aLinkTbl.size(); ++n ) { tools::SvRef& rTmp = aLinkTbl[ n ]; if( !rTmp.is() ) { aLinkTbl.erase( aLinkTbl.begin() + n-- ); } else if( pLink == rTmp.get() ) return false; // No duplicate links inserted } pLink->SetLinkManager( this ); aLinkTbl.emplace_back(pLink ); return true; } bool LinkManager::InsertLink( SvBaseLink * pLink, SvBaseLinkObjectType nObjType, SfxLinkUpdateMode nUpdateMode, const OUString* pName ) { // This First pLink->SetObjType( nObjType ); if( pName ) pLink->SetName( *pName ); pLink->SetUpdateMode( nUpdateMode ); return Insert( pLink ); } void LinkManager::InsertDDELink( SvBaseLink * pLink, const OUString& rServer, std::u16string_view rTopic, std::u16string_view rItem ) { if( !isClientType( pLink->GetObjType() ) ) return; OUString sCmd; ::sfx2::MakeLnkName( sCmd, &rServer, rTopic, rItem ); pLink->SetObjType( SvBaseLinkObjectType::ClientDde ); pLink->SetName( sCmd ); Insert( pLink ); } void LinkManager::InsertDDELink( SvBaseLink * pLink ) { DBG_ASSERT( isClientType(pLink->GetObjType()), "no OBJECT_CLIENT_SO" ); if( !isClientType( pLink->GetObjType() ) ) return; if( pLink->GetObjType() == SvBaseLinkObjectType::ClientSo ) pLink->SetObjType( SvBaseLinkObjectType::ClientDde ); Insert( pLink ); } // Obtain the string for the dialog bool LinkManager::GetDisplayNames( const SvBaseLink * pLink, OUString* pType, OUString* pFile, OUString* pLinkStr, OUString* pFilter ) { bool bRet = false; const OUString& sLNm( pLink->GetLinkSourceName() ); if( !sLNm.isEmpty() ) { switch( pLink->GetObjType() ) { case SvBaseLinkObjectType::ClientFile: case SvBaseLinkObjectType::ClientGraphic: case SvBaseLinkObjectType::ClientOle: { sal_Int32 nPos = 0; OUString sFile( sLNm.getToken( 0, ::sfx2::cTokenSeparator, nPos ) ); OUString sRange( sLNm.getToken( 0, ::sfx2::cTokenSeparator, nPos ) ); if( pFile ) *pFile = sFile; if( pLinkStr ) *pLinkStr = sRange; if( pFilter ) *pFilter = nPos == -1 ? OUString() : sLNm.copy(nPos); if( pType ) { SvBaseLinkObjectType nObjType = pLink->GetObjType(); *pType = SfxResId( ( SvBaseLinkObjectType::ClientFile == nObjType || SvBaseLinkObjectType::ClientOle == nObjType ) ? RID_SVXSTR_FILELINK : RID_SVXSTR_GRAPHICLINK); } bRet = true; } break; case SvBaseLinkObjectType::ClientDde: { sal_Int32 nTmp = 0; OUString sServer( sLNm.getToken( 0, cTokenSeparator, nTmp ) ); OUString sTopic( sLNm.getToken( 0, cTokenSeparator, nTmp ) ); if( pType ) *pType = sServer; if( pFile ) *pFile = sTopic; if( pLinkStr ) *pLinkStr = nTmp != -1 ? sLNm.copy(nTmp) : OUString(); bRet = true; } break; default: break; } } return bRet; } void LinkManager::UpdateAllLinks( bool bAskUpdate, bool bUpdateGrfLinks, weld::Window* pParentWin ) { // First make a copy of the array in order to update links // links in ... no contact between them! std::vector aTmpArr; for( size_t n = 0; n < aLinkTbl.size(); ++n ) { tools::SvRef& rLink = aLinkTbl[ n ]; if( !rLink.is() ) { Remove( n-- ); continue; } aTmpArr.push_back( rLink.get() ); } for(SvBaseLink* pLink : aTmpArr) { // search first in the array after the entry bool bFound = false; for(const tools::SvRef & i : aLinkTbl) if( pLink == i.get() ) { bFound = true; break; } if( !bFound ) continue; // was not available! // Graphic-Links not to update yet if( !pLink->IsVisible() || ( !bUpdateGrfLinks && SvBaseLinkObjectType::ClientGraphic == pLink->GetObjType() )) continue; if( bAskUpdate ) { OUString aMsg = SfxResId(STR_QUERY_UPDATE_LINKS); INetURLObject aURL(pPersist->getDocumentBaseURL()); aMsg = aMsg.replaceFirst("%{filename}", aURL.GetLastName()); std::unique_ptr xQueryBox(Application::CreateMessageDialog(pParentWin, VclMessageType::Question, VclButtonsType::YesNo, aMsg)); xQueryBox->set_default_response(RET_YES); int nRet = xQueryBox->run(); if( RET_YES != nRet ) { SfxObjectShell* pShell = pLink->GetLinkManager()->GetPersist(); if(pShell) { comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = pShell->getEmbeddedObjectContainer(); rEmbeddedObjectContainer.setUserAllowsLinkUpdate(false); } return ; // nothing should be updated } bAskUpdate = false; // once is enough } pLink->Update(); } CloseCachedComps(); } SvLinkSourceRef LinkManager::CreateObj( SvBaseLink const * pLink ) { switch( pLink->GetObjType() ) { case SvBaseLinkObjectType::ClientFile: case SvBaseLinkObjectType::ClientGraphic: case SvBaseLinkObjectType::ClientOle: return new SvFileObject; case SvBaseLinkObjectType::Internal: return new SvxInternalLink; case SvBaseLinkObjectType::ClientDde: return new SvDDEObject; default: return SvLinkSourceRef(); } } bool LinkManager::InsertServer( SvLinkSource* pObj ) { // no duplicate inserts if( !pObj ) return false; return aServerTbl.insert( pObj ).second; } void LinkManager::RemoveServer( SvLinkSource* pObj ) { aServerTbl.erase( pObj ); } void MakeLnkName( OUString& rName, const OUString* pType, std::u16string_view rFile, std::u16string_view rLink, const OUString* pFilter ) { if( pType ) { rName = comphelper::string::strip(*pType, ' ') + OUStringChar(cTokenSeparator); } else rName.clear(); rName += rFile; rName = comphelper::string::strip(rName, ' ') + OUStringChar(cTokenSeparator); rName = comphelper::string::strip(rName, ' ') + rLink; if( pFilter ) { rName += OUStringChar(cTokenSeparator) + *pFilter; rName = comphelper::string::strip(rName, ' '); } } void LinkManager::ReconnectDdeLink(SfxObjectShell& rServer) { SfxMedium* pMed = rServer.GetMedium(); if (!pMed) return; const ::sfx2::SvBaseLinks& rLinks = GetLinks(); size_t n = rLinks.size(); for (size_t i = 0; i < n; ++i) { ::sfx2::SvBaseLink* p = rLinks[i].get(); OUString aType, aFile, aLink, aFilter; if (!GetDisplayNames(p, &aType, &aFile, &aLink, &aFilter)) continue; if (aType != "soffice") // DDE connections between OOo apps are always named 'soffice'. continue; OUString aTmp; OUString aURL = aFile; if (osl::FileBase::getFileURLFromSystemPath(aFile, aTmp) == osl::FileBase::E_None) aURL = aTmp; if (!aURL.equalsIgnoreAsciiCase(pMed->GetName())) // This DDE link is not associated with this server shell... Skip it. continue; if (aLink.isEmpty()) continue; LinkServerShell(aLink, rServer, *p); } } void LinkManager::LinkServerShell(const OUString& rPath, SfxObjectShell& rServer, ::sfx2::SvBaseLink& rLink) { ::sfx2::SvLinkSource* pSrvSrc = rServer.DdeCreateLinkSource(rPath); if (pSrvSrc) { css::datatransfer::DataFlavor aFl; SotExchange::GetFormatDataFlavor(rLink.GetContentType(), aFl); rLink.SetObj(pSrvSrc); pSrvSrc->AddDataAdvise( &rLink, aFl.MimeType, SfxLinkUpdateMode::ONCALL == rLink.GetUpdateMode() ? ADVISEMODE_ONLYONCE : 0); } } void LinkManager::InsertFileLink( sfx2::SvBaseLink& rLink, SvBaseLinkObjectType nFileType, std::u16string_view rFileNm, const OUString* pFilterNm, const OUString* pRange) { if (!isClientType(rLink.GetObjType())) return; OUStringBuffer aBuf(64); aBuf.append(rFileNm); aBuf.append(sfx2::cTokenSeparator); if (pRange) aBuf.append(*pRange); if (pFilterNm) { aBuf.append(sfx2::cTokenSeparator); aBuf.append(*pFilterNm); } OUString aCmd = aBuf.makeStringAndClear(); InsertLink(&rLink, nFileType, SfxLinkUpdateMode::ONCALL, &aCmd); } // A transfer is aborted, so cancel all download media // (for now this is only of interest for the file links!) void LinkManager::CancelTransfers() { const sfx2::SvBaseLinks& rLnks = GetLinks(); for( size_t n = rLnks.size(); n; ) { const sfx2::SvBaseLink& rLnk = *rLnks[--n]; if (isClientFileType(rLnk.GetObjType())) { if (SvFileObject* pFileObj = static_cast(rLnk.GetObj())) pFileObj->CancelTransfers(); } } } // For the purpose of sending Status information from the file object to // the base link, there exist a dedicated ClipBoardId. The SvData-object // gets the appropriate information as a string // For now this is required for file object in conjunction with JavaScript // - needs information about Load/Abort/Error SotClipboardFormatId LinkManager::RegisterStatusInfoId() { static SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; if( nFormat == SotClipboardFormatId::NONE ) { nFormat = SotExchange::RegisterFormatName( "StatusInfo from SvxInternalLink"); } return nFormat; } bool LinkManager::GetGraphicFromAny(const OUString& rMimeType, const css::uno::Any & rValue, Graphic& rGraphic, weld::Window* pParentWin) { bool bRet = false; if (!rValue.hasValue()) return bRet; if (rValue.has()) { OUString sReferer; SfxObjectShell* sh = GetPersist(); if (sh && sh->HasName()) sReferer = sh->GetMedium()->GetName(); OUString sURL = rValue.get(); if (!SvtSecurityOptions::isUntrustedReferer(sReferer)) rGraphic = vcl::graphic::loadFromURL(sURL, pParentWin); if (rGraphic.IsNone()) rGraphic.SetDefaultType(); rGraphic.setOriginURL(sURL); return true; } else if (rValue.has>()) { auto aSeq = rValue.get>(); SvMemoryStream aMemStm( const_cast(aSeq.getConstArray()), aSeq.getLength(), StreamMode::READ ); aMemStm.Seek( 0 ); switch( SotExchange::GetFormatIdFromMimeType( rMimeType ) ) { case SotClipboardFormatId::SVXB: { TypeSerializer aSerializer(aMemStm); aSerializer.readGraphic(rGraphic); bRet = true; } break; case SotClipboardFormatId::GDIMETAFILE: { GDIMetaFile aMtf; SvmReader aReader( aMemStm ); aReader.Read( aMtf ); rGraphic = aMtf; bRet = true; } break; case SotClipboardFormatId::BITMAP: { Bitmap aBmp; ReadDIB(aBmp, aMemStm, true); rGraphic = BitmapEx(aBmp); bRet = true; } break; default: break; } } return bRet; } static OUString lcl_DDE_RelToAbs( const OUString& rTopic, std::u16string_view rBaseURL ) { OUString sRet; INetURLObject aURL( rTopic ); if( INetProtocol::NotValid == aURL.GetProtocol() ) osl::FileBase::getFileURLFromSystemPath(rTopic, sRet); if( sRet.isEmpty() ) sRet = URIHelper::SmartRel2Abs( INetURLObject(rBaseURL), rTopic, URIHelper::GetMaybeFileHdl() ); return sRet; } bool SvxInternalLink::Connect( sfx2::SvBaseLink* pLink ) { SfxObjectShell* pFndShell = nullptr; sal_uInt16 nUpdateMode = css::document::UpdateDocMode::NO_UPDATE; OUString sTopic, sItem, sReferer; LinkManager* pLinkMgr = pLink->GetLinkManager(); if (pLinkMgr && sfx2::LinkManager::GetDisplayNames(pLink, nullptr, &sTopic, &sItem) && !sTopic.isEmpty()) { // first only loop over the DocumentShells the shells and find those // with the name: CharClass aCC( LanguageTag( LANGUAGE_SYSTEM) ); bool bFirst = true; SfxObjectShell* pShell = pLinkMgr->GetPersist(); if( pShell && pShell->GetMedium() ) { sReferer = pShell->GetMedium()->GetBaseURL(); const SfxUInt16Item* pItem = SfxItemSet::GetItem(pShell->GetMedium()->GetItemSet(), SID_UPDATEDOCMODE, false); if ( pItem ) nUpdateMode = pItem->GetValue(); } OUString sNmURL(aCC.lowercase(lcl_DDE_RelToAbs(sTopic, sReferer))); if ( !pShell ) { bFirst = false; pShell = SfxObjectShell::GetFirst( nullptr, false ); } OUString sTmp; while( pShell ) { if( sTmp.isEmpty() ) { sTmp = pShell->GetTitle( SFX_TITLE_FULLNAME ); sTmp = lcl_DDE_RelToAbs(sTmp, sReferer ); } sTmp = aCC.lowercase( sTmp ); if( sTmp == sNmURL ) // we want these { pFndShell = pShell; break; } if( bFirst ) { bFirst = false; pShell = SfxObjectShell::GetFirst( nullptr, false ); } else pShell = SfxObjectShell::GetNext( *pShell, nullptr, false ); sTmp.clear(); } } // empty topics are not allowed - which document is it if( sTopic.isEmpty() ) return false; if (pFndShell) { sfx2::SvLinkSource* pNewSrc = pFndShell->DdeCreateLinkSource( sItem ); if( pNewSrc ) { css::datatransfer::DataFlavor aFl; SotExchange::GetFormatDataFlavor( pLink->GetContentType(), aFl ); pLink->SetObj( pNewSrc ); pNewSrc->AddDataAdvise( pLink, aFl.MimeType, SfxLinkUpdateMode::ONCALL == pLink->GetUpdateMode() ? ADVISEMODE_ONLYONCE : 0 ); return true; } } else { // then try to download the file: INetURLObject aURL( sTopic ); INetProtocol eOld = aURL.GetProtocol(); sTopic = lcl_DDE_RelToAbs( sTopic, sReferer ); aURL.SetURL( sTopic ); if( INetProtocol::NotValid != eOld || INetProtocol::Http != aURL.GetProtocol() ) { SfxStringItem aName( SID_FILE_NAME, sTopic ); SfxBoolItem aMinimized(SID_MINIMIZED, true); SfxBoolItem aHidden(SID_HIDDEN, true); SfxStringItem aTarget( SID_TARGETNAME, "_blank" ); SfxStringItem aReferer( SID_REFERER, sReferer ); SfxUInt16Item aUpdate( SID_UPDATEDOCMODE, nUpdateMode ); SfxBoolItem aReadOnly(SID_DOC_READONLY, false); // Disable automatic re-connection to avoid this link instance // being destroyed at re-connection. SfxBoolItem aDdeConnect(SID_DDE_RECONNECT_ONLOAD, false); // #i14200# (DDE-link crashes wordprocessor) SfxAllItemSet aArgs( SfxGetpApp()->GetPool() ); aArgs.Put(aReferer); aArgs.Put(aTarget); aArgs.Put(aHidden); aArgs.Put(aMinimized); aArgs.Put(aName); aArgs.Put(aUpdate); aArgs.Put(aReadOnly); aArgs.Put(aDdeConnect); Reference xComp = SfxObjectShell::CreateAndLoadComponent(aArgs); pFndShell = SfxObjectShell::GetShellFromComponent(xComp); if (xComp.is() && pFndShell && pLinkMgr) { pLinkMgr->InsertCachedComp(xComp); sfx2::LinkManager::LinkServerShell(sItem, *pFndShell, *pLink); return true; } } } return false; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */