Bump version to 24.04.3.4
[LibreOffice.git] / sfx2 / source / doc / docfile.cxx
blobba45c8eb92502e796045d86ac060e6160ea40ea7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
22 #ifdef UNX
23 #include <sys/stat.h>
24 #endif
26 #include <sfx2/docfile.hxx>
27 #include <sfx2/signaturestate.hxx>
29 #include <com/sun/star/task/InteractionHandler.hpp>
30 #include <com/sun/star/task/XStatusIndicator.hpp>
31 #include <com/sun/star/uno/Reference.h>
32 #include <com/sun/star/ucb/XContent.hpp>
33 #include <com/sun/star/beans/XPropertySet.hpp>
34 #include <com/sun/star/container/XChild.hpp>
35 #include <com/sun/star/document/XDocumentRevisionListPersistence.hpp>
36 #include <com/sun/star/document/LockedDocumentRequest.hpp>
37 #include <com/sun/star/document/LockedOnSavingRequest.hpp>
38 #include <com/sun/star/document/OwnLockOnDocumentRequest.hpp>
39 #include <com/sun/star/document/LockFileIgnoreRequest.hpp>
40 #include <com/sun/star/document/LockFileCorruptRequest.hpp>
41 #include <com/sun/star/document/ChangedByOthersRequest.hpp>
42 #include <com/sun/star/document/ReloadEditableRequest.hpp>
43 #include <com/sun/star/embed/XTransactedObject.hpp>
44 #include <com/sun/star/embed/ElementModes.hpp>
45 #include <com/sun/star/embed/UseBackupException.hpp>
46 #include <com/sun/star/embed/XOptimizedStorage.hpp>
47 #include <com/sun/star/frame/Desktop.hpp>
48 #include <com/sun/star/frame/XModel.hpp>
49 #include <com/sun/star/frame/XTerminateListener.hpp>
50 #include <com/sun/star/graphic/XGraphic.hpp>
51 #include <com/sun/star/ucb/ContentCreationException.hpp>
52 #include <com/sun/star/ucb/InteractiveIOException.hpp>
53 #include <com/sun/star/ucb/CommandFailedException.hpp>
54 #include <com/sun/star/ucb/CommandAbortedException.hpp>
55 #include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
56 #include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
57 #include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
58 #include <com/sun/star/ucb/Lock.hpp>
59 #include <com/sun/star/ucb/NameClashException.hpp>
60 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
61 #include <com/sun/star/ucb/XProgressHandler.hpp>
62 #include <com/sun/star/io/XOutputStream.hpp>
63 #include <com/sun/star/io/XInputStream.hpp>
64 #include <com/sun/star/io/XTruncate.hpp>
65 #include <com/sun/star/io/XSeekable.hpp>
66 #include <com/sun/star/io/TempFile.hpp>
67 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
68 #include <com/sun/star/ucb/InsertCommandArgument.hpp>
69 #include <com/sun/star/ucb/NameClash.hpp>
70 #include <com/sun/star/util/XModifiable.hpp>
71 #include <com/sun/star/beans/NamedValue.hpp>
72 #include <com/sun/star/beans/PropertyValue.hpp>
73 #include <com/sun/star/security/DocumentDigitalSignatures.hpp>
74 #include <com/sun/star/security/XCertificate.hpp>
75 #include <tools/urlobj.hxx>
76 #include <tools/fileutil.hxx>
77 #include <unotools/configmgr.hxx>
78 #include <unotools/tempfile.hxx>
79 #include <comphelper/lok.hxx>
80 #include <comphelper/fileurl.hxx>
81 #include <comphelper/processfactory.hxx>
82 #include <comphelper/propertyvalue.hxx>
83 #include <comphelper/interaction.hxx>
84 #include <comphelper/sequence.hxx>
85 #include <comphelper/simplefileaccessinteraction.hxx>
86 #include <comphelper/string.hxx>
87 #include <framework/interaction.hxx>
88 #include <utility>
89 #include <svl/stritem.hxx>
90 #include <svl/eitem.hxx>
91 #include <svtools/sfxecode.hxx>
92 #include <svl/itemset.hxx>
93 #include <svl/intitem.hxx>
94 #include <svtools/svparser.hxx>
95 #include <sal/log.hxx>
97 #include <unotools/streamwrap.hxx>
99 #include <osl/file.hxx>
101 #include <comphelper/storagehelper.hxx>
102 #include <unotools/mediadescriptor.hxx>
103 #include <comphelper/docpasswordhelper.hxx>
104 #include <tools/datetime.hxx>
105 #include <unotools/pathoptions.hxx>
106 #include <svtools/asynclink.hxx>
107 #include <ucbhelper/commandenvironment.hxx>
108 #include <unotools/ucbstreamhelper.hxx>
109 #include <unotools/ucbhelper.hxx>
110 #include <unotools/progresshandlerwrap.hxx>
111 #include <ucbhelper/content.hxx>
112 #include <ucbhelper/interactionrequest.hxx>
113 #include <sot/storage.hxx>
114 #include <svl/documentlockfile.hxx>
115 #include <svl/msodocumentlockfile.hxx>
116 #include <com/sun/star/document/DocumentRevisionListPersistence.hpp>
118 #include <sfx2/app.hxx>
119 #include <sfx2/frame.hxx>
120 #include <sfx2/dispatch.hxx>
121 #include <sfx2/fcontnr.hxx>
122 #include <sfx2/docfilt.hxx>
123 #include <sfx2/sfxsids.hrc>
124 #include <sfx2/sfxuno.hxx>
125 #include <openflag.hxx>
126 #include <officecfg/Office/Common.hxx>
127 #include <comphelper/propertysequence.hxx>
128 #include <vcl/weld.hxx>
129 #include <vcl/svapp.hxx>
130 #include <comphelper/diagnose_ex.hxx>
131 #include <unotools/fltrcfg.hxx>
132 #include <sfx2/digitalsignatures.hxx>
133 #include <sfx2/viewfrm.hxx>
134 #include <comphelper/threadpool.hxx>
135 #include <o3tl/string_view.hxx>
136 #include <condition_variable>
138 #include <com/sun/star/io/WrongFormatException.hpp>
140 #include <memory>
142 using namespace ::com::sun::star;
143 using namespace ::com::sun::star::graphic;
144 using namespace ::com::sun::star::uno;
145 using namespace ::com::sun::star::ucb;
146 using namespace ::com::sun::star::beans;
147 using namespace ::com::sun::star::io;
148 using namespace ::com::sun::star::security;
150 namespace
153 struct ReadOnlyMediumEntry
155 ReadOnlyMediumEntry(std::shared_ptr<std::recursive_mutex> pMutex,
156 std::shared_ptr<bool> pIsDestructed)
157 : _pMutex(std::move(pMutex))
158 , _pIsDestructed(std::move(pIsDestructed))
161 std::shared_ptr<std::recursive_mutex> _pMutex;
162 std::shared_ptr<bool> _pIsDestructed;
167 static std::mutex g_chkReadOnlyGlobalMutex;
168 static bool g_bChkReadOnlyTaskRunning = false;
169 static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_newReadOnlyDocs;
170 static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_existingReadOnlyDocs;
172 namespace {
174 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
176 bool IsSystemFileLockingUsed()
178 #if HAVE_FEATURE_MACOSX_SANDBOX
179 return true;
180 #else
181 return officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get();
182 #endif
186 bool IsOOoLockFileUsed()
188 #if HAVE_FEATURE_MACOSX_SANDBOX
189 return false;
190 #else
191 return officecfg::Office::Common::Misc::UseDocumentOOoLockFile::get();
192 #endif
195 bool IsLockingUsed()
197 return officecfg::Office::Common::Misc::UseLocking::get();
200 #endif
202 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
203 bool IsWebDAVLockingUsed()
205 return officecfg::Office::Common::Misc::UseWebDAVFileLocking::get();
207 #endif
209 /// Gets default attributes of a file:// URL.
210 sal_uInt64 GetDefaultFileAttributes(const OUString& rURL)
212 sal_uInt64 nRet = 0;
214 if (!comphelper::isFileUrl(rURL))
215 return nRet;
217 // Make sure the file exists (and create it if not).
218 osl::File aFile(rURL);
219 osl::File::RC nRes = aFile.open(osl_File_OpenFlag_Create);
220 if (nRes != osl::File::E_None && nRes != osl::File::E_EXIST)
221 return nRet;
223 aFile.close();
225 osl::DirectoryItem aItem;
226 if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None)
227 return nRet;
229 osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
230 if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None)
231 return nRet;
233 nRet = aStatus.getAttributes();
234 return nRet;
237 /// Determines if rURL is safe to move or not.
238 bool IsFileMovable(const INetURLObject& rURL)
240 #ifdef MACOSX
241 (void)rURL;
242 // Hide extension macOS-specific file property would be lost.
243 return false;
244 #else
246 if (rURL.GetProtocol() != INetProtocol::File)
247 // Not a file:// URL.
248 return false;
250 #ifdef UNX
251 OUString sPath = rURL.getFSysPath(FSysStyle::Unix);
252 if (sPath.isEmpty())
253 return false;
255 struct stat buf;
256 if (lstat(sPath.toUtf8().getStr(), &buf) != 0)
257 return false;
259 // Hardlink or symlink: osl::File::move() doesn't play with these nicely.
260 if (buf.st_nlink > 1 || S_ISLNK(buf.st_mode))
261 return false;
262 #elif defined _WIN32
263 if (tools::IsMappedWebDAVPath(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE)))
264 return false;
265 #endif
267 return true;
268 #endif
271 class CheckReadOnlyTaskTerminateListener
272 : public ::cppu::WeakImplHelper<css::frame::XTerminateListener>
274 public:
275 // XEventListener
276 void SAL_CALL disposing(const css::lang::EventObject& Source) override;
278 // XTerminateListener
279 void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) override;
280 void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) override;
282 bool bIsTerminated = false;
283 std::condition_variable mCond;
284 std::mutex mMutex;
287 class CheckReadOnlyTask : public comphelper::ThreadTask
289 public:
290 CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag);
291 ~CheckReadOnlyTask();
293 virtual void doWork() override;
295 private:
296 rtl::Reference<CheckReadOnlyTaskTerminateListener> m_xListener;
299 } // anonymous namespace
301 CheckReadOnlyTask::CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag)
302 : ThreadTask(pTag)
303 , m_xListener(new CheckReadOnlyTaskTerminateListener)
305 Reference<css::frame::XDesktop> xDesktop
306 = css::frame::Desktop::create(comphelper::getProcessComponentContext());
307 if (xDesktop.is() && m_xListener != nullptr)
309 xDesktop->addTerminateListener(m_xListener);
313 CheckReadOnlyTask::~CheckReadOnlyTask()
315 Reference<css::frame::XDesktop> xDesktop
316 = css::frame::Desktop::create(comphelper::getProcessComponentContext());
317 if (xDesktop.is() && m_xListener != nullptr)
319 std::unique_lock<std::mutex> lock(m_xListener->mMutex);
320 if (!m_xListener->bIsTerminated)
322 lock.unlock();
323 xDesktop->removeTerminateListener(m_xListener);
328 namespace
330 void SAL_CALL
331 CheckReadOnlyTaskTerminateListener::disposing(const css::lang::EventObject& /*Source*/)
335 void SAL_CALL
336 CheckReadOnlyTaskTerminateListener::queryTermination(const css::lang::EventObject& /*aEvent*/)
340 void SAL_CALL
341 CheckReadOnlyTaskTerminateListener::notifyTermination(const css::lang::EventObject& /*aEvent*/)
343 std::unique_lock<std::mutex> lock(mMutex);
344 bIsTerminated = true;
345 lock.unlock();
346 mCond.notify_one();
349 /// Temporary file wrapper to handle tmp file lifecyle
350 /// for lok fork a background saving worker issues.
351 class MediumTempFile : public ::utl::TempFileNamed
353 bool m_bWasChild;
354 public:
355 MediumTempFile(const OUString *pParent )
356 : ::utl::TempFileNamed(pParent)
357 , m_bWasChild(comphelper::LibreOfficeKit::isForkedChild())
361 MediumTempFile(const MediumTempFile &rFrom ) = delete;
363 ~MediumTempFile()
365 bool isForked = comphelper::LibreOfficeKit::isForkedChild();
367 // avoid deletion of files created by the parent
368 if (isForked && ! m_bWasChild)
370 EnableKillingFile(false);
376 class SfxMedium_Impl
378 public:
379 StreamMode m_nStorOpenMode;
380 ErrCodeMsg m_eError;
381 ErrCodeMsg m_eWarningError;
383 ::ucbhelper::Content aContent;
384 bool bUpdatePickList:1;
385 bool bIsTemp:1;
386 bool bDownloadDone:1;
387 bool bIsStorage:1;
388 bool bUseInteractionHandler:1;
389 bool bAllowDefaultIntHdl:1;
390 bool bDisposeStorage:1;
391 bool bStorageBasedOnInStream:1;
392 bool m_bSalvageMode:1;
393 bool m_bVersionsAlreadyLoaded:1;
394 bool m_bLocked:1;
395 bool m_bMSOLockFileCreated : 1;
396 bool m_bDisableUnlockWebDAV:1;
397 bool m_bGotDateTime:1;
398 bool m_bRemoveBackup:1;
399 bool m_bOriginallyReadOnly:1;
400 bool m_bOriginallyLoadedReadOnly:1;
401 bool m_bTriedStorage:1;
402 bool m_bRemote:1;
403 bool m_bInputStreamIsReadOnly:1;
404 bool m_bInCheckIn:1;
405 bool m_bDisableFileSync = false;
406 bool m_bNotifyWhenEditable = false;
407 /// if true, xStorage is an inner package and not directly from xStream
408 bool m_bODFWholesomeEncryption = false;
410 OUString m_aName;
411 OUString m_aLogicName;
412 OUString m_aLongName;
414 mutable std::shared_ptr<SfxItemSet> m_pSet;
415 mutable std::unique_ptr<INetURLObject> m_pURLObj;
417 std::shared_ptr<const SfxFilter> m_pFilter;
418 std::shared_ptr<const SfxFilter> m_pCustomFilter;
420 std::shared_ptr<std::recursive_mutex> m_pCheckEditableWorkerMutex;
421 std::shared_ptr<bool> m_pIsDestructed;
422 ImplSVEvent* m_pReloadEvent;
424 std::unique_ptr<SvStream> m_pInStream;
425 std::unique_ptr<SvStream> m_pOutStream;
427 OUString aOrigURL;
428 DateTime aExpireTime;
429 SfxFrameWeakRef wLoadTargetFrame;
430 SvKeyValueIteratorRef xAttributes;
432 svtools::AsynchronLink aDoneLink;
434 uno::Sequence < util::RevisionTag > aVersions;
436 std::unique_ptr<MediumTempFile> pTempFile;
438 uno::Reference<embed::XStorage> xStorage;
439 uno::Reference<embed::XStorage> m_xZipStorage;
440 uno::Reference<io::XInputStream> m_xInputStreamToLoadFrom;
441 uno::Reference<io::XInputStream> xInputStream;
442 uno::Reference<io::XStream> xStream;
443 uno::Reference<io::XStream> m_xLockingStream;
444 uno::Reference<task::XInteractionHandler> xInteraction;
445 uno::Reference<io::XStream> m_xODFDecryptedInnerPackageStream;
446 uno::Reference<embed::XStorage> m_xODFEncryptedOuterStorage;
447 uno::Reference<embed::XStorage> m_xODFDecryptedInnerZipStorage;
449 ErrCodeMsg nLastStorageError;
451 OUString m_aBackupURL;
453 // the following member is changed and makes sense only during saving
454 // TODO/LATER: in future the signature state should be controlled by the medium not by the document
455 // in this case the member will hold this information
456 SignatureState m_nSignatureState;
458 bool m_bHasEmbeddedObjects = false;
460 util::DateTime m_aDateTime;
462 uno::Sequence<beans::PropertyValue> m_aArgs;
464 explicit SfxMedium_Impl();
465 ~SfxMedium_Impl();
466 SfxMedium_Impl(const SfxMedium_Impl&) = delete;
467 SfxMedium_Impl& operator=(const SfxMedium_Impl&) = delete;
469 OUString getFilterMimeType() const
470 { return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); }
473 SfxMedium_Impl::SfxMedium_Impl() :
474 m_nStorOpenMode(SFX_STREAM_READWRITE),
475 m_eError(ERRCODE_NONE),
476 m_eWarningError(ERRCODE_NONE),
477 bUpdatePickList(true),
478 bIsTemp( false ),
479 bDownloadDone( true ),
480 bIsStorage( false ),
481 bUseInteractionHandler( true ),
482 bAllowDefaultIntHdl( false ),
483 bDisposeStorage( false ),
484 bStorageBasedOnInStream( false ),
485 m_bSalvageMode( false ),
486 m_bVersionsAlreadyLoaded( false ),
487 m_bLocked( false ),
488 m_bMSOLockFileCreated( false ),
489 m_bDisableUnlockWebDAV( false ),
490 m_bGotDateTime( false ),
491 m_bRemoveBackup( false ),
492 m_bOriginallyReadOnly(false),
493 m_bOriginallyLoadedReadOnly(false),
494 m_bTriedStorage(false),
495 m_bRemote(false),
496 m_bInputStreamIsReadOnly(false),
497 m_bInCheckIn(false),
498 m_pReloadEvent(nullptr),
499 aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ),
500 nLastStorageError( ERRCODE_NONE ),
501 m_nSignatureState( SignatureState::NOSIGNATURES )
506 SfxMedium_Impl::~SfxMedium_Impl()
508 aDoneLink.ClearPendingCall();
510 pTempFile.reset();
511 m_pSet.reset();
512 std::unique_lock<std::recursive_mutex> chkEditLock;
513 if (m_pCheckEditableWorkerMutex != nullptr)
514 chkEditLock = std::unique_lock<std::recursive_mutex>(*m_pCheckEditableWorkerMutex);
515 m_pURLObj.reset();
518 void SfxMedium::ResetError()
520 pImpl->m_eError = ERRCODE_NONE;
521 if( pImpl->m_pInStream )
522 pImpl->m_pInStream->ResetError();
523 if( pImpl->m_pOutStream )
524 pImpl->m_pOutStream->ResetError();
527 ErrCodeMsg const & SfxMedium::GetWarningError() const
529 return pImpl->m_eWarningError;
532 ErrCodeMsg const & SfxMedium::GetLastStorageCreationState() const
534 return pImpl->nLastStorageError;
537 void SfxMedium::SetError(ErrCodeMsg nError)
539 pImpl->m_eError = nError;
542 void SfxMedium::SetWarningError(const ErrCodeMsg& nWarningError)
544 pImpl->m_eWarningError = nWarningError;
547 ErrCodeMsg SfxMedium::GetErrorCode() const
549 ErrCodeMsg lError = pImpl->m_eError;
550 if(!lError && pImpl->m_pInStream)
551 lError = pImpl->m_pInStream->GetErrorCode();
552 if(!lError && pImpl->m_pOutStream)
553 lError = pImpl->m_pOutStream->GetErrorCode();
554 return lError;
557 void SfxMedium::CheckFileDate( const util::DateTime& aInitDate )
559 GetInitFileDate( true );
560 if ( pImpl->m_aDateTime.Seconds == aInitDate.Seconds
561 && pImpl->m_aDateTime.Minutes == aInitDate.Minutes
562 && pImpl->m_aDateTime.Hours == aInitDate.Hours
563 && pImpl->m_aDateTime.Day == aInitDate.Day
564 && pImpl->m_aDateTime.Month == aInitDate.Month
565 && pImpl->m_aDateTime.Year == aInitDate.Year )
566 return;
568 uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
570 if ( !xHandler.is() )
571 return;
575 ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
576 document::ChangedByOthersRequest() ) );
577 uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
578 new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() ),
579 new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() )
581 xInteractionRequestImpl->setContinuations( aContinuations );
583 xHandler->handle( xInteractionRequestImpl );
585 ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
586 if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
588 SetError(ERRCODE_ABORT);
591 catch ( const uno::Exception& )
595 bool SfxMedium::DocNeedsFileDateCheck() const
597 return ( !IsReadOnly() && ( GetURLObject().GetProtocol() == INetProtocol::File ||
598 GetURLObject().isAnyKnownWebDAVScheme() ) );
601 util::DateTime const & SfxMedium::GetInitFileDate( bool bIgnoreOldValue )
603 if ( ( bIgnoreOldValue || !pImpl->m_bGotDateTime ) && !pImpl->m_aLogicName.isEmpty() )
607 // add a default css::ucb::XCommandEnvironment
608 // in order to have the WebDAV UCP provider manage http/https authentication correctly
609 ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
610 utl::UCBContentHelper::getDefaultCommandEnvironment(),
611 comphelper::getProcessComponentContext() );
613 aContent.getPropertyValue("DateModified") >>= pImpl->m_aDateTime;
614 pImpl->m_bGotDateTime = true;
616 catch ( const css::uno::Exception& )
621 return pImpl->m_aDateTime;
625 Reference < XContent > SfxMedium::GetContent() const
627 if ( !pImpl->aContent.get().is() )
629 Reference < css::ucb::XContent > xContent;
631 // tdf#95144 add a default css::ucb::XCommandEnvironment
632 // in order to have the WebDAV UCP provider manage https protocol certificates correctly
633 css:: uno::Reference< task::XInteractionHandler > xIH(
634 css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) );
636 css::uno::Reference< css::ucb::XProgressHandler > xProgress;
637 rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
639 const SfxUnoAnyItem* pItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_CONTENT, false);
640 if ( pItem )
641 pItem->GetValue() >>= xContent;
643 if ( xContent.is() )
647 pImpl->aContent = ::ucbhelper::Content( xContent, pCommandEnv, comphelper::getProcessComponentContext() );
649 catch ( const Exception& )
653 else
655 // TODO: SAL_WARN( "sfx.doc", "SfxMedium::GetContent()\nCreate Content? This code exists as fallback only. Please clarify, why it's used.");
656 OUString aURL;
657 if ( !pImpl->m_aName.isEmpty() )
658 osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL );
659 else if ( !pImpl->m_aLogicName.isEmpty() )
660 aURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
661 if (!aURL.isEmpty() )
662 (void)::ucbhelper::Content::create( aURL, pCommandEnv, comphelper::getProcessComponentContext(), pImpl->aContent );
666 return pImpl->aContent.get();
669 OUString SfxMedium::GetBaseURL( bool bForSaving )
671 if (bForSaving)
673 bool bIsRemote = IsRemote();
674 if ((bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get())
675 || (!bIsRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()))
676 return OUString();
679 if (const SfxStringItem* pBaseURLItem = GetItemSet().GetItem<SfxStringItem>(SID_DOC_BASEURL))
680 return pBaseURLItem->GetValue();
682 OUString aBaseURL;
683 if (!utl::ConfigManager::IsFuzzing() && GetContent().is())
687 Any aAny = pImpl->aContent.getPropertyValue("BaseURI");
688 aAny >>= aBaseURL;
690 catch ( const css::uno::Exception& )
694 if ( aBaseURL.isEmpty() )
695 aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
697 return aBaseURL;
700 bool SfxMedium::IsSkipImages() const
702 const SfxStringItem* pSkipImagesItem = GetItemSet().GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS);
703 return pSkipImagesItem && pSkipImagesItem->GetValue() == "SkipImages";
706 SvStream* SfxMedium::GetInStream()
708 if ( pImpl->m_pInStream )
709 return pImpl->m_pInStream.get();
711 if ( pImpl->pTempFile )
713 pImpl->m_pInStream.reset( new SvFileStream(pImpl->m_aName, pImpl->m_nStorOpenMode) );
715 pImpl->m_eError = pImpl->m_pInStream->GetError();
717 if (!pImpl->m_eError && (pImpl->m_nStorOpenMode & StreamMode::WRITE)
718 && ! pImpl->m_pInStream->IsWritable() )
720 pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
721 pImpl->m_pInStream.reset();
723 else
724 return pImpl->m_pInStream.get();
727 GetMedium_Impl();
729 if ( GetErrorIgnoreWarning() )
730 return nullptr;
732 return pImpl->m_pInStream.get();
736 void SfxMedium::CloseInStream()
738 CloseInStream_Impl();
741 void SfxMedium::CloseInStream_Impl(bool bInDestruction)
743 // if there is a storage based on the InStream, we have to
744 // close the storage, too, because otherwise the storage
745 // would use an invalid ( deleted ) stream.
746 if ( pImpl->m_pInStream && pImpl->xStorage.is() )
748 if ( pImpl->bStorageBasedOnInStream )
749 CloseStorage();
752 if ( pImpl->m_pInStream && !GetContent().is() && !bInDestruction )
754 CreateTempFile();
755 return;
758 pImpl->m_pInStream.reset();
759 if ( pImpl->m_pSet )
760 pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
762 CloseZipStorage_Impl();
763 pImpl->xInputStream.clear();
765 if ( !pImpl->m_pOutStream )
767 // output part of the stream is not used so the whole stream can be closed
768 // TODO/LATER: is it correct?
769 pImpl->xStream.clear();
770 if ( pImpl->m_pSet )
771 pImpl->m_pSet->ClearItem( SID_STREAM );
776 SvStream* SfxMedium::GetOutStream()
778 if ( !pImpl->m_pOutStream )
780 // Create a temp. file if there is none because we always
781 // need one.
782 CreateTempFile( false );
784 if ( pImpl->pTempFile )
786 // On windows we try to re-use XOutStream from xStream if that exists;
787 // because opening new SvFileStream in this situation may fail with ERROR_SHARING_VIOLATION
788 // TODO: this is a horrible hack that should probably be removed,
789 // somebody needs to investigate this more thoroughly...
790 if (getenv("SFX_MEDIUM_REUSE_STREAM") && pImpl->xStream.is())
792 assert(pImpl->xStream->getOutputStream().is()); // need that...
793 pImpl->m_pOutStream = utl::UcbStreamHelper::CreateStream(
794 pImpl->xStream, false);
796 else
798 // On Unix don't try to re-use XOutStream from xStream if that exists;
799 // it causes fdo#59022 (fails opening files via SMB on Linux)
800 pImpl->m_pOutStream.reset( new SvFileStream(
801 pImpl->m_aName, StreamMode::STD_READWRITE) );
803 CloseStorage();
807 return pImpl->m_pOutStream.get();
811 void SfxMedium::CloseOutStream()
813 CloseOutStream_Impl();
816 void SfxMedium::CloseOutStream_Impl()
818 if ( pImpl->m_pOutStream )
820 // if there is a storage based on the OutStream, we have to
821 // close the storage, too, because otherwise the storage
822 // would use an invalid ( deleted ) stream.
823 //TODO/MBA: how to deal with this?!
824 //maybe we need a new flag when the storage was created from the outstream
825 if ( pImpl->xStorage.is() )
827 CloseStorage();
830 pImpl->m_pOutStream.reset();
833 if ( !pImpl->m_pInStream )
835 // input part of the stream is not used so the whole stream can be closed
836 // TODO/LATER: is it correct?
837 pImpl->xStream.clear();
838 if ( pImpl->m_pSet )
839 pImpl->m_pSet->ClearItem( SID_STREAM );
844 const OUString& SfxMedium::GetPhysicalName() const
846 if ( pImpl->m_aName.isEmpty() && !pImpl->m_aLogicName.isEmpty() )
847 const_cast<SfxMedium*>(this)->CreateFileStream();
849 // return the name then
850 return pImpl->m_aName;
854 void SfxMedium::CreateFileStream()
856 // force synchron
857 if( pImpl->m_pInStream )
859 SvLockBytes* pBytes = pImpl->m_pInStream->GetLockBytes();
860 if( pBytes )
861 pBytes->SetSynchronMode();
864 GetInStream();
865 if( pImpl->m_pInStream )
867 CreateTempFile( false );
868 pImpl->bIsTemp = true;
869 CloseInStream_Impl();
874 bool SfxMedium::Commit()
876 if( pImpl->xStorage.is() )
877 StorageCommit_Impl();
878 else if( pImpl->m_pOutStream )
879 pImpl->m_pOutStream->FlushBuffer();
880 else if( pImpl->m_pInStream )
881 pImpl->m_pInStream->FlushBuffer();
883 if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
885 // does something only in case there is a temporary file ( means aName points to different location than aLogicName )
886 Transfer_Impl();
889 bool bResult = ( GetErrorIgnoreWarning() == ERRCODE_NONE );
891 if ( bResult && DocNeedsFileDateCheck() )
892 GetInitFileDate( true );
894 // remove truncation mode from the flags
895 pImpl->m_nStorOpenMode &= ~StreamMode::TRUNC;
896 return bResult;
900 bool SfxMedium::IsStorage()
902 if ( pImpl->xStorage.is() )
903 return true;
905 if ( pImpl->m_bTriedStorage )
906 return pImpl->bIsStorage;
908 if ( pImpl->pTempFile )
910 OUString aURL;
911 if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL )
912 != osl::FileBase::E_None )
914 SAL_WARN( "sfx.doc", "Physical name '" << pImpl->m_aName << "' not convertible to file URL");
916 pImpl->bIsStorage = SotStorage::IsStorageFile( aURL ) && !SotStorage::IsOLEStorage( aURL);
917 if ( !pImpl->bIsStorage )
918 pImpl->m_bTriedStorage = true;
920 else if ( GetInStream() )
922 pImpl->bIsStorage = SotStorage::IsStorageFile( pImpl->m_pInStream.get() ) && !SotStorage::IsOLEStorage( pImpl->m_pInStream.get() );
923 if ( !pImpl->m_pInStream->GetError() && !pImpl->bIsStorage )
924 pImpl->m_bTriedStorage = true;
927 return pImpl->bIsStorage;
931 bool SfxMedium::IsPreview_Impl() const
933 bool bPreview = false;
934 const SfxBoolItem* pPreview = GetItemSet().GetItem(SID_PREVIEW, false);
935 if ( pPreview )
936 bPreview = pPreview->GetValue();
937 else
939 const SfxStringItem* pFlags = GetItemSet().GetItem(SID_OPTIONS, false);
940 if ( pFlags )
942 OUString aFileFlags = pFlags->GetValue();
943 aFileFlags = aFileFlags.toAsciiUpperCase();
944 if ( -1 != aFileFlags.indexOf( 'B' ) )
945 bPreview = true;
949 return bPreview;
953 void SfxMedium::StorageBackup_Impl()
955 ::ucbhelper::Content aOriginalContent;
956 Reference< css::ucb::XCommandEnvironment > xDummyEnv;
958 bool bBasedOnOriginalFile =
959 !pImpl->pTempFile
960 && ( pImpl->m_aLogicName.isEmpty() || !pImpl->m_bSalvageMode )
961 && !GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ).isEmpty()
962 && GetURLObject().GetProtocol() == INetProtocol::File
963 && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
965 if ( bBasedOnOriginalFile && pImpl->m_aBackupURL.isEmpty()
966 && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aOriginalContent ) )
968 DoInternalBackup_Impl( aOriginalContent );
969 if( pImpl->m_aBackupURL.isEmpty() )
970 SetError(ERRCODE_SFX_CANTCREATEBACKUP);
975 OUString const & SfxMedium::GetBackup_Impl()
977 if ( pImpl->m_aBackupURL.isEmpty() )
978 StorageBackup_Impl();
980 return pImpl->m_aBackupURL;
984 uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()
986 if ( GetErrorIgnoreWarning() )
987 return uno::Reference< embed::XStorage >();
989 // if the medium was constructed with a Storage: use this one, not a temp. storage
990 // if a temporary storage already exists: use it
991 if (pImpl->xStorage.is()
992 && (pImpl->m_bODFWholesomeEncryption || pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile))
994 return pImpl->xStorage;
997 // if necessary close stream that was used for reading
998 if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
999 CloseInStream();
1001 DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" );
1003 // TODO/LATER: The current solution is to store the document temporary and then copy it to the target location;
1004 // in future it should be stored directly and then copied to the temporary location, since in this case no
1005 // file attributes have to be preserved and system copying mechanics could be used instead of streaming.
1006 CreateTempFileNoCopy();
1008 return GetStorage();
1012 bool SfxMedium::SetEncryptionDataToStorage_Impl()
1014 // in case media-descriptor contains password it should be used on opening
1015 if ( !pImpl->xStorage.is() || !pImpl->m_pSet )
1016 return false;
1018 uno::Sequence< beans::NamedValue > aEncryptionData;
1019 if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
1020 return false;
1022 // replace the password with encryption data
1023 pImpl->m_pSet->ClearItem( SID_PASSWORD );
1024 pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) );
1028 ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData );
1030 catch( const uno::Exception& )
1032 SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
1033 SetError(ERRCODE_IO_GENERAL);
1034 return false;
1036 return true;
1039 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1041 // FIXME: Hmm actually lock files should be used for sftp: documents
1042 // even if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT. Only the use of lock
1043 // files for *local* documents is unnecessary in that case. But
1044 // actually, the checks for sftp: here are just wishful thinking; I
1045 // don't this there is any support for actually editing documents
1046 // behind sftp: URLs anyway.
1048 // Sure, there could perhaps be a 3rd-party extension that brings UCB
1049 // the potential to handle files behind sftp:. But there could also be
1050 // an extension that handles some arbitrary foobar: scheme *and* it
1051 // could be that lock files would be the correct thing to use for
1052 // foobar: documents, too. But the hardcoded test below won't know
1053 // that. Clearly the knowledge whether lock files should be used or
1054 // not for some URL scheme belongs in UCB, not here.
1056 namespace
1059 OUString tryMSOwnerFiles(std::u16string_view sDocURL)
1061 svt::MSODocumentLockFile aMSOLockFile(sDocURL);
1062 LockFileEntry aData;
1065 aData = aMSOLockFile.GetLockData();
1067 catch( const uno::Exception& )
1069 return OUString();
1072 OUString sUserData = aData[LockFileComponent::OOOUSERNAME];
1074 if (!sUserData.isEmpty())
1075 sUserData += " (MS Office)"; // Mention the used office suite
1077 return sUserData;
1080 OUString tryForeignLockfiles(std::u16string_view sDocURL)
1082 OUString sUserData = tryMSOwnerFiles(sDocURL);
1083 // here we can test for empty result, and add other known applications' lockfile testing
1084 return sUserData.trim();
1088 SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog(const LockFileEntry& aData,
1089 bool bIsLoading, bool bOwnLock,
1090 bool bHandleSysLocked)
1092 ShowLockResult nResult = ShowLockResult::NoLock;
1094 // tdf#92817: Simple check for empty lock file that needs to be deleted, when system locking is enabled
1095 if( aData[LockFileComponent::OOOUSERNAME].isEmpty() && aData[LockFileComponent::SYSUSERNAME].isEmpty() && !bHandleSysLocked )
1096 bOwnLock=true;
1098 // show the interaction regarding the document opening
1099 uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
1101 if ( xHandler.is() && ( bIsLoading || !bHandleSysLocked || bOwnLock ) )
1103 OUString aDocumentURL
1104 = GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
1105 OUString aInfo;
1106 ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl;
1108 sal_Int32 nContinuations = 3;
1110 if ( bOwnLock )
1112 aInfo = aData[LockFileComponent::EDITTIME];
1114 xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
1115 document::OwnLockOnDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo, !bIsLoading ) ) );
1117 else
1119 // Use a fourth continuation in case there's no filesystem lock:
1120 // "Ignore lock file and open/replace the document"
1121 if (!bHandleSysLocked)
1122 nContinuations = 4;
1124 if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() )
1125 aInfo = aData[LockFileComponent::OOOUSERNAME];
1126 else
1127 aInfo = aData[LockFileComponent::SYSUSERNAME];
1129 if (aInfo.isEmpty() && !GetURLObject().isAnyKnownWebDAVScheme())
1130 // Try to get name of user who has locked the file using other applications
1131 aInfo = tryForeignLockfiles(
1132 GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
1134 if ( !aInfo.isEmpty() && !aData[LockFileComponent::EDITTIME].isEmpty() )
1135 aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )";
1137 if (!bIsLoading) // so, !bHandleSysLocked
1139 xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any(
1140 document::LockedOnSavingRequest(OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo)));
1141 // Currently, only the last "Retry" continuation (meaning ignore the lock and try overwriting) can be returned.
1143 else /*logically therefore bIsLoading is set */
1145 xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
1146 document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) );
1150 uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations);
1151 auto pContinuations = aContinuations.getArray();
1152 pContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
1153 pContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
1154 pContinuations[2] = new ::ucbhelper::InteractionDisapprove( xInteractionRequestImpl.get() );
1155 if (nContinuations > 3)
1157 // We use InteractionRetry to reflect that user wants to
1158 // ignore the (stale?) alien lock file and open/overwrite the document
1159 pContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get());
1161 xInteractionRequestImpl->setContinuations( aContinuations );
1163 xHandler->handle( xInteractionRequestImpl );
1165 bool bOpenReadOnly = false;
1166 ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
1167 if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
1169 SetError(ERRCODE_ABORT);
1171 else if ( uno::Reference< task::XInteractionDisapprove >( xSelected.get(), uno::UNO_QUERY ).is() )
1173 // own lock on loading, user has selected to ignore the lock
1174 // own lock on saving, user has selected to ignore the lock
1175 // alien lock on loading, user has selected to edit a copy of document
1176 // TODO/LATER: alien lock on saving, user has selected to do SaveAs to different location
1177 if ( !bOwnLock ) // bIsLoading implied from outermost condition
1179 // means that a copy of the document should be opened
1180 GetItemSet().Put( SfxBoolItem( SID_TEMPLATE, true ) );
1182 else
1183 nResult = ShowLockResult::Succeeded;
1185 else if (uno::Reference< task::XInteractionRetry >(xSelected.get(), uno::UNO_QUERY).is())
1187 // User decided to ignore the alien (stale?) lock file without filesystem lock
1188 nResult = ShowLockResult::Succeeded;
1190 else if (uno::Reference< task::XInteractionApprove >( xSelected.get(), uno::UNO_QUERY ).is())
1192 bOpenReadOnly = true;
1194 else // user selected "Notify"
1196 pImpl->m_bNotifyWhenEditable = true;
1197 AddToCheckEditableWorkerList();
1198 bOpenReadOnly = true;
1201 if (bOpenReadOnly)
1203 // own lock on loading, user has selected to open readonly
1204 // own lock on saving, user has selected to open readonly
1205 // alien lock on loading, user has selected to retry saving
1206 // TODO/LATER: alien lock on saving, user has selected to retry saving
1208 if (bIsLoading)
1209 GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
1210 else
1211 nResult = ShowLockResult::Try;
1214 else
1216 if ( bIsLoading )
1218 // if no interaction handler is provided the default answer is open readonly
1219 // that usually happens in case the document is loaded per API
1220 // so the document must be opened readonly for backward compatibility
1221 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1223 else
1224 SetError(ERRCODE_IO_ACCESSDENIED);
1228 return nResult;
1231 bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg)
1233 // system file locking is not active, ask user whether he wants to open the document without any locking
1234 uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
1236 if (xHandler.is())
1238 ::rtl::Reference< ::ucbhelper::InteractionRequest > xIgnoreRequestImpl;
1240 switch (nWhichDlg)
1242 case MessageDlg::LockFileIgnore:
1243 xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileIgnoreRequest() ));
1244 break;
1245 case MessageDlg::LockFileCorrupt:
1246 xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileCorruptRequest() ));
1247 break;
1250 uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
1251 new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get()),
1252 new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get())
1254 xIgnoreRequestImpl->setContinuations(aContinuations);
1256 xHandler->handle(xIgnoreRequestImpl);
1258 ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xIgnoreRequestImpl->getSelection();
1259 bool bReadOnly = true;
1261 if (uno::Reference<task::XInteractionAbort>(xSelected.get(), uno::UNO_QUERY).is())
1263 SetError(ERRCODE_ABORT);
1264 bReadOnly = false;
1266 else if (!uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
1268 // user selected "Notify"
1269 pImpl->m_bNotifyWhenEditable = true;
1270 AddToCheckEditableWorkerList();
1273 if (bReadOnly)
1274 GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
1276 return bReadOnly;
1279 return false;
1282 namespace
1284 bool isSuitableProtocolForLocking(const OUString & rLogicName)
1286 INetURLObject aUrl( rLogicName );
1287 INetProtocol eProt = aUrl.GetProtocol();
1288 #if !HAVE_FEATURE_MACOSX_SANDBOX
1289 if (eProt == INetProtocol::File) {
1290 return true;
1292 #endif
1293 return eProt == INetProtocol::Smb || eProt == INetProtocol::Sftp;
1297 namespace
1300 // for LOCK request, suppress dialog on 403, typically indicates read-only
1301 // document and there's a 2nd dialog prompting to open a copy anyway
1302 class LockInteractionHandler : public ::cppu::WeakImplHelper<task::XInteractionHandler>
1304 private:
1305 uno::Reference<task::XInteractionHandler> m_xHandler;
1307 public:
1308 explicit LockInteractionHandler(uno::Reference<task::XInteractionHandler> const& xHandler)
1309 : m_xHandler(xHandler)
1313 virtual void SAL_CALL handle(uno::Reference<task::XInteractionRequest> const& xRequest) override
1315 ucb::InteractiveNetworkWriteException readException;
1316 ucb::InteractiveNetworkReadException writeException;
1317 if ((xRequest->getRequest() >>= readException)
1318 || (xRequest->getRequest() >>= writeException))
1320 return; // 403 gets reported as one of these; ignore to avoid dialog
1322 m_xHandler->handle(xRequest);
1326 } // namespace
1328 #endif // HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1330 // sets SID_DOC_READONLY if the document cannot be opened for editing
1331 // if user cancel the loading the ERROR_ABORT is set
1332 SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bNoUI,
1333 bool bTryIgnoreLockFile,
1334 LockFileEntry* pLockData)
1336 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1337 (void) bLoading;
1338 (void) bNoUI;
1339 (void) bTryIgnoreLockFile;
1340 (void) pLockData;
1341 return LockFileResult::Succeeded;
1342 #else
1343 LockFileResult eResult = LockFileResult::Failed;
1345 // check if path scheme is http:// or https://
1346 // may be this is better if used always, in Android and iOS as well?
1347 // if this code should be always there, remember to move the relevant code in UnlockFile method as well !
1349 if ( GetURLObject().isAnyKnownWebDAVScheme() )
1351 // do nothing if WebDAV locking is disabled
1352 if (!IsWebDAVLockingUsed())
1353 return LockFileResult::Succeeded;
1356 bool bResult = pImpl->m_bLocked;
1357 bool bIsTemplate = false;
1358 // so, this is webdav stuff...
1359 if ( !bResult )
1361 // no read-write access is necessary on loading if the document is explicitly opened as copy
1362 const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
1363 bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
1366 if ( !bIsTemplate && !bResult && !IsReadOnly() )
1368 ShowLockResult bUIStatus = ShowLockResult::NoLock;
1371 if( !bResult )
1373 uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true );
1374 // Dialog with error is superfluous:
1375 // on loading, will result in read-only with infobar.
1376 // bNoUI case for Reload failing, will open dialog later.
1377 if (bLoading || bNoUI)
1379 xCHandler = new LockInteractionHandler(xCHandler);
1381 Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment(
1382 xCHandler, Reference< css::ucb::XProgressHandler >() );
1384 ucbhelper::Content aContentToLock(
1385 GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
1386 xComEnv, comphelper::getProcessComponentContext() );
1390 aContentToLock.lock();
1391 bResult = true;
1393 catch ( ucb::InteractiveLockingLockedException& )
1395 // received when the resource is already locked
1396 if (!bNoUI || pLockData)
1398 // get the lock owner, using a special ucb.webdav property
1399 // the owner property retrieved here is what the other principal send the server
1400 // when activating the lock.
1401 // See http://tools.ietf.org/html/rfc4918#section-14.17 for details
1402 LockFileEntry aLockData;
1403 aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user";
1404 // This solution works right when the LO user name and the WebDAV user
1405 // name are the same.
1406 // A better thing to do would be to obtain the 'real' WebDAV user name,
1407 // but that's not possible from a WebDAV UCP provider client.
1408 LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
1409 // use the current LO user name as the system name
1410 aLockData[LockFileComponent::SYSUSERNAME]
1411 = aOwnData[LockFileComponent::SYSUSERNAME];
1413 uno::Sequence<css::ucb::Lock> aLocks;
1414 // getting the property, send a PROPFIND to the server over the net
1415 if ((aContentToLock.getPropertyValue("DAV:lockdiscovery") >>= aLocks) && aLocks.hasElements())
1417 // got at least a lock, show the owner of the first lock returned
1418 css::ucb::Lock aLock = aLocks[0];
1419 OUString aOwner;
1420 if (aLock.Owner >>= aOwner)
1422 // we need to display the WebDAV user name owning the lock, not the local one
1423 aLockData[LockFileComponent::OOOUSERNAME] = aOwner;
1427 if (!bNoUI)
1429 bUIStatus = ShowLockedDocumentDialog(aLockData, bLoading, false,
1430 true);
1433 if (pLockData)
1435 std::copy(aLockData.begin(), aLockData.end(), pLockData->begin());
1439 catch( ucb::InteractiveNetworkWriteException& )
1441 // This catch it's not really needed, here just for the sake of documentation on the behaviour.
1442 // This is the most likely reason:
1443 // - the remote site is a WebDAV with special configuration: read/only for read operations
1444 // and read/write for write operations, the user is not allowed to lock/write and
1445 // she cancelled the credentials request.
1446 // this is not actually an error, but the exception is sent directly from ucb, avoiding the automatic
1447 // management that takes part in cancelCommandExecution()
1448 // Unfortunately there is no InteractiveNetwork*Exception available to signal this more correctly
1449 // since it mostly happens on read/only part of webdav, this can be the most correct
1450 // exception available
1452 catch( uno::Exception& )
1454 TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
1457 } while( !bResult && bUIStatus == ShowLockResult::Try );
1460 pImpl->m_bLocked = bResult;
1462 if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
1464 // the error should be set in case it is storing process
1465 // or the document has been opened for editing explicitly
1466 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
1468 if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
1469 SetError(ERRCODE_IO_ACCESSDENIED);
1470 else
1471 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1474 // when the file is locked, get the current file date
1475 if ( bResult && DocNeedsFileDateCheck() )
1476 GetInitFileDate( true );
1478 if ( bResult )
1479 eResult = LockFileResult::Succeeded;
1481 return eResult;
1484 if (!IsLockingUsed())
1485 return LockFileResult::Succeeded;
1486 if (GetURLObject().HasError())
1487 return eResult;
1491 if ( pImpl->m_bLocked && bLoading
1492 && GetURLObject().GetProtocol() == INetProtocol::File )
1494 // if the document is already locked the system locking might be temporarily off after storing
1495 // check whether the system file locking should be taken again
1496 GetLockingStream_Impl();
1499 bool bResult = pImpl->m_bLocked;
1501 if ( !bResult )
1503 // no read-write access is necessary on loading if the document is explicitly opened as copy
1504 const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
1505 bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
1508 if ( !bResult && !IsReadOnly() )
1510 bool bContentReadonly = false;
1511 if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File )
1513 // let the original document be opened to check the possibility to open it for editing
1514 // and to let the writable stream stay open to hold the lock on the document
1515 GetLockingStream_Impl();
1518 // "IsReadOnly" property does not allow to detect whether the file is readonly always
1519 // so we try always to open the file for editing
1520 // the file is readonly only in case the read-write stream can not be opened
1521 if ( bLoading && !pImpl->m_xLockingStream.is() )
1525 // MediaDescriptor does this check also, the duplication should be avoided in future
1526 Reference< css::ucb::XCommandEnvironment > xDummyEnv;
1527 ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
1528 aContent.getPropertyValue("IsReadOnly") >>= bContentReadonly;
1530 catch( const uno::Exception& ) {}
1533 // do further checks only if the file not readonly in fs
1534 if ( !bContentReadonly )
1536 // the special file locking should be used only for suitable URLs
1537 if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) )
1540 // in case of storing the document should request the output before locking
1541 if ( bLoading )
1543 // let the stream be opened to check the system file locking
1544 GetMedium_Impl();
1545 if (GetErrorIgnoreWarning() != ERRCODE_NONE) {
1546 return eResult;
1550 ShowLockResult bUIStatus = ShowLockResult::NoLock;
1552 // check whether system file locking has been used, the default value is false
1553 bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed();
1555 // TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem
1556 // if system lock is used the writeable stream should be available
1557 bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream );
1559 // The file is attempted to get locked for the duration of lockfile creation on save
1560 std::unique_ptr<osl::File> pFileLock;
1561 if (!bLoading && bUseSystemLock && pImpl->pTempFile)
1563 INetURLObject aDest(GetURLObject());
1564 OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE));
1566 if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment())
1568 pFileLock = std::make_unique<osl::File>(aDestURL);
1569 auto rc = pFileLock->open(osl_File_OpenFlag_Write);
1570 if (rc == osl::FileBase::E_ACCES)
1571 bHandleSysLocked = true;
1579 ::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName );
1581 std::unique_ptr<svt::MSODocumentLockFile> pMSOLockFile;
1582 const SvtFilterOptions& rOpt = SvtFilterOptions::Get();
1583 if (rOpt.IsMSOLockFileCreationIsEnabled() && svt::MSODocumentLockFile::IsMSOSupportedFileFormat(pImpl->m_aLogicName))
1585 pMSOLockFile.reset(new svt::MSODocumentLockFile(pImpl->m_aLogicName));
1586 pImpl->m_bMSOLockFileCreated = true;
1589 bool bIoErr = false;
1591 if (!bHandleSysLocked)
1595 bResult = aLockFile.CreateOwnLockFile();
1596 if(pMSOLockFile)
1597 bResult &= pMSOLockFile->CreateOwnLockFile();
1599 catch (const uno::Exception&)
1601 if (tools::IsMappedWebDAVPath(GetURLObject().GetMainURL(
1602 INetURLObject::DecodeMechanism::NONE)))
1604 // This is a path that redirects to a WebDAV resource;
1605 // so failure creating lockfile is not an error here.
1606 bResult = true;
1608 else if (bLoading && !bNoUI)
1610 bIoErr = true;
1611 ShowLockFileProblemDialog(MessageDlg::LockFileIgnore);
1612 bResult = true; // always delete the defect lock-file
1616 // in case OOo locking is turned off the lock file is still written if possible
1617 // but it is ignored while deciding whether the document should be opened for editing or not
1618 if (!bResult && !IsOOoLockFileUsed() && !bIoErr)
1620 bResult = true;
1621 // take the ownership over the lock file
1622 aLockFile.OverwriteOwnLockFile();
1624 if(pMSOLockFile)
1625 pMSOLockFile->OverwriteOwnLockFile();
1629 if ( !bResult )
1631 LockFileEntry aData;
1634 aData = aLockFile.GetLockData();
1636 catch (const io::WrongFormatException&)
1638 // we get empty or corrupt data
1639 // info to the user
1640 if (!bIoErr && bLoading && !bNoUI )
1641 bResult = ShowLockFileProblemDialog(MessageDlg::LockFileCorrupt);
1643 // not show the Lock Document Dialog
1644 bIoErr = true;
1646 catch( const uno::Exception& )
1648 // show the Lock Document Dialog, when locked from other app
1649 bIoErr = !bHandleSysLocked;
1652 bool bOwnLock = false;
1654 if (!bHandleSysLocked)
1656 LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
1657 bOwnLock = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
1659 if (bOwnLock
1660 && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
1661 && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
1663 // this is own lock from the same installation, it could remain because of crash
1664 bResult = true;
1668 if ( !bResult && !bIoErr)
1670 if (!bNoUI)
1671 bUIStatus = ShowLockedDocumentDialog(
1672 aData, bLoading, bOwnLock, bHandleSysLocked);
1673 else if (bLoading && bTryIgnoreLockFile && !bHandleSysLocked)
1674 bUIStatus = ShowLockResult::Succeeded;
1676 if ( bUIStatus == ShowLockResult::Succeeded )
1678 // take the ownership over the lock file
1679 bResult = aLockFile.OverwriteOwnLockFile();
1681 if(pMSOLockFile)
1682 pMSOLockFile->OverwriteOwnLockFile();
1684 else if (bLoading && !bHandleSysLocked)
1685 eResult = LockFileResult::FailedLockFile;
1687 if (!bResult && pLockData)
1689 std::copy(aData.begin(), aData.end(), pLockData->begin());
1694 catch( const uno::Exception& )
1697 } while( !bResult && bUIStatus == ShowLockResult::Try );
1699 pImpl->m_bLocked = bResult;
1701 else
1703 // this is no file URL, check whether the file is readonly
1704 bResult = !bContentReadonly;
1707 else // read-only
1709 AddToCheckEditableWorkerList();
1713 if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
1715 // the error should be set in case it is storing process
1716 // or the document has been opened for editing explicitly
1717 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
1719 if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
1720 SetError(ERRCODE_IO_ACCESSDENIED);
1721 else
1722 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1725 // when the file is locked, get the current file date
1726 if ( bResult && DocNeedsFileDateCheck() )
1727 GetInitFileDate( true );
1729 if ( bResult )
1730 eResult = LockFileResult::Succeeded;
1732 catch( const uno::Exception& )
1734 TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: high probability, that the content has not been created" );
1737 return eResult;
1738 #endif
1741 // this either returns non-null or throws exception
1742 uno::Reference<embed::XStorage>
1743 SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const xStorage)
1745 uno::Reference<embed::XStorage> xRet;
1746 if (xStorage->hasByName("encrypted-package"))
1748 uno::Reference<io::XStream> const
1749 xDecryptedInnerPackage = xStorage->openStreamElement(
1750 "encrypted-package",
1751 embed::ElementModes::READ | embed::ElementModes::NOCREATE);
1752 // either this throws due to wrong password or IO error, or returns stream
1753 assert(xDecryptedInnerPackage.is());
1754 // need a seekable stream => copy
1755 Reference<uno::XComponentContext> const xContext(::comphelper::getProcessComponentContext());
1756 uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
1757 xContext->getServiceManager()->createInstanceWithContext(
1758 "com.sun.star.comp.MemoryStream", xContext),
1759 UNO_QUERY_THROW);
1760 comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
1761 xDecryptedInnerPackageStream->getOutputStream()->closeOutput();
1762 #if 0
1763 // debug: dump to temp file
1764 uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
1765 xTempFile->setRemoveFile(false);
1766 comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
1767 xTempFile->getOutputStream()->closeOutput();
1768 SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
1769 uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
1770 #endif
1771 // create inner storage; opening the stream should have already verified
1772 // the password so any failure here is probably due to a bug
1773 xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
1774 PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
1775 embed::ElementModes::READWRITE, xContext, false);
1776 assert(xRet.is());
1777 // consistency check: outer and inner package must have same mimetype
1778 OUString const outerMediaType(uno::Reference<beans::XPropertySet>(pImpl->xStorage,
1779 uno::UNO_QUERY_THROW)->getPropertyValue("MediaType").get<OUString>());
1780 OUString const innerMediaType(uno::Reference<beans::XPropertySet>(xRet,
1781 uno::UNO_QUERY_THROW)->getPropertyValue("MediaType").get<OUString>());
1782 if (outerMediaType.isEmpty() || outerMediaType != innerMediaType)
1784 throw io::WrongFormatException("MediaType inconsistent in encrypted ODF package");
1786 // success:
1787 pImpl->m_bODFWholesomeEncryption = true;
1788 pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
1789 pImpl->m_xODFEncryptedOuterStorage = xStorage;
1790 pImpl->xStorage = xRet;
1792 return xRet;
1795 uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
1797 if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage )
1798 return pImpl->xStorage;
1800 uno::Sequence< uno::Any > aArgs( 2 );
1801 auto pArgs = aArgs.getArray();
1803 // the medium should be retrieved before temporary file creation
1804 // to let the MediaDescriptor be filled with the streams
1805 GetMedium_Impl();
1807 if ( bCreateTempFile )
1808 CreateTempFile( false );
1810 GetMedium_Impl();
1812 if ( GetErrorIgnoreWarning() )
1813 return pImpl->xStorage;
1815 const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
1816 if ( pRepairItem && pRepairItem->GetValue() )
1818 // the storage should be created for repairing mode
1819 CreateTempFile( false );
1820 GetMedium_Impl();
1822 Reference< css::ucb::XProgressHandler > xProgressHandler;
1823 Reference< css::task::XStatusIndicator > xStatusIndicator;
1825 const SfxUnoAnyItem* pxProgressItem = GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL, false);
1826 if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) )
1827 xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) );
1829 uno::Sequence< beans::PropertyValue > aAddProps{
1830 comphelper::makePropertyValue("RepairPackage", true),
1831 comphelper::makePropertyValue("StatusIndicator", xProgressHandler)
1834 // the first arguments will be filled later
1835 aArgs.realloc( 3 );
1836 pArgs = aArgs.getArray();
1837 pArgs[2] <<= aAddProps;
1840 if ( pImpl->xStream.is() )
1842 // since the storage is based on temporary stream we open it always read-write
1843 pArgs[0] <<= pImpl->xStream;
1844 pArgs[1] <<= embed::ElementModes::READWRITE;
1845 pImpl->bStorageBasedOnInStream = true;
1846 if (pImpl->m_bDisableFileSync)
1848 // Forward NoFileSync to the storage factory.
1849 aArgs.realloc(3); // ??? this may re-write the data added above for pRepairItem
1850 pArgs = aArgs.getArray();
1851 uno::Sequence<beans::PropertyValue> aProperties(
1852 comphelper::InitPropertySequence({ { "NoFileSync", uno::Any(true) } }));
1853 pArgs[2] <<= aProperties;
1856 else if ( pImpl->xInputStream.is() )
1858 // since the storage is based on temporary stream we open it always read-write
1859 pArgs[0] <<= pImpl->xInputStream;
1860 pArgs[1] <<= embed::ElementModes::READ;
1861 pImpl->bStorageBasedOnInStream = true;
1863 else
1865 CloseStreams_Impl();
1866 pArgs[0] <<= pImpl->m_aName;
1867 pArgs[1] <<= embed::ElementModes::READ;
1868 pImpl->bStorageBasedOnInStream = false;
1873 pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
1874 uno::UNO_QUERY );
1876 catch( const uno::Exception& )
1878 // impossibility to create the storage is no error
1881 pImpl->nLastStorageError = GetErrorIgnoreWarning();
1882 if( pImpl->nLastStorageError != ERRCODE_NONE )
1884 pImpl->xStorage = nullptr;
1885 if ( pImpl->m_pInStream )
1886 pImpl->m_pInStream->Seek(0);
1887 return uno::Reference< embed::XStorage >();
1890 pImpl->m_bTriedStorage = true;
1892 if (pImpl->xStorage.is())
1894 pImpl->m_bODFWholesomeEncryption = false;
1895 if (SetEncryptionDataToStorage_Impl())
1899 TryEncryptedInnerPackage(pImpl->xStorage);
1901 catch (Exception const&)
1903 TOOLS_WARN_EXCEPTION("sfx.doc", "exception from TryEncryptedInnerPackage: ");
1904 SetError(ERRCODE_IO_GENERAL);
1909 if (GetErrorCode()) // decryption failed?
1911 pImpl->xStorage.clear();
1914 // TODO/LATER: Get versionlist on demand
1915 if ( pImpl->xStorage.is() )
1917 GetVersionList();
1920 const SfxInt16Item* pVersion = SfxItemSet::GetItem<SfxInt16Item>(pImpl->m_pSet.get(), SID_VERSION, false);
1922 bool bResetStorage = false;
1923 if ( pVersion && pVersion->GetValue() )
1925 // Read all available versions
1926 if ( pImpl->aVersions.hasElements() )
1928 // Search for the version fits the comment
1929 // The versions are numbered starting with 1, versions with
1930 // negative versions numbers are counted backwards from the
1931 // current version
1932 short nVersion = pVersion->GetValue();
1933 if ( nVersion<0 )
1934 nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion;
1935 else // nVersion > 0; pVersion->GetValue() != 0 was the condition to this block
1936 nVersion--;
1938 const util::RevisionTag& rTag = pImpl->aVersions[nVersion];
1940 // Open SubStorage for all versions
1941 uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( "Versions",
1942 embed::ElementModes::READ );
1944 DBG_ASSERT( xSub.is(), "Version list, but no Versions!" );
1946 // There the version is stored as packed Stream
1947 uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ );
1948 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr ));
1949 if ( pStream && pStream->GetError() == ERRCODE_NONE )
1951 // Unpack Stream in TempDir
1952 const OUString aTmpName = ::utl::CreateTempURL();
1953 SvFileStream aTmpStream( aTmpName, SFX_STREAM_READWRITE );
1955 pStream->ReadStream( aTmpStream );
1956 pStream.reset();
1957 aTmpStream.Close();
1959 // Open data as Storage
1960 pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
1961 pImpl->xStorage = comphelper::OStorageHelper::GetStorageFromURL( aTmpName, embed::ElementModes::READ );
1962 pImpl->bStorageBasedOnInStream = false;
1963 OUString aTemp;
1964 osl::FileBase::getSystemPathFromFileURL( aTmpName, aTemp );
1965 SetPhysicalName_Impl( aTemp );
1967 pImpl->bIsTemp = true;
1968 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1969 // TODO/MBA
1970 pImpl->aVersions.realloc(0);
1972 else
1973 bResetStorage = true;
1976 else
1977 bResetStorage = true;
1980 if ( bResetStorage )
1982 pImpl->xStorage.clear();
1983 pImpl->m_xODFDecryptedInnerPackageStream.clear();
1984 pImpl->m_xODFEncryptedOuterStorage.clear();
1985 if ( pImpl->m_pInStream )
1986 pImpl->m_pInStream->Seek( 0 );
1989 pImpl->bIsStorage = pImpl->xStorage.is();
1990 return pImpl->xStorage;
1993 uno::Reference<embed::XStorage> SfxMedium::GetScriptingStorageToSign_Impl()
1995 // this was set when it was initially loaded
1996 if (pImpl->m_bODFWholesomeEncryption)
1998 // (partial) scripting signature can only be in inner storage!
1999 // Note: a "PackageFormat" storage like pImpl->xStorage doesn't work
2000 // (even if it's not encrypted) because it hides the "META-INF" dir.
2001 // This "ZipFormat" storage is used only read-only; a writable one is
2002 // created manually in SignContents_Impl().
2003 if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
2005 GetStorage(false);
2006 // don't care about xStorage here because Zip is readonly
2007 SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?");
2008 if (pImpl->m_xODFDecryptedInnerPackageStream.is())
2010 const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
2011 const bool bRepairPackage = pRepairItem && pRepairItem->GetValue();
2012 pImpl->m_xODFDecryptedInnerZipStorage =
2013 ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
2014 ZIP_STORAGE_FORMAT_STRING,
2015 pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), {},
2016 bRepairPackage);
2019 return pImpl->m_xODFDecryptedInnerZipStorage;
2021 else
2023 return GetZipStorageToSign_Impl(true);
2027 // note: currently nobody who calls this with "false" writes into an ODF
2028 // storage that is returned here, that is only for OOXML
2029 uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
2031 if ( !GetErrorIgnoreWarning() && !pImpl->m_xZipStorage.is() )
2033 GetMedium_Impl();
2037 const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
2038 const bool bRepairPackage = pRepairItem && pRepairItem->GetValue();
2039 // we can not sign document if there is no stream
2040 // should it be possible at all?
2041 if ( !bReadOnly && pImpl->xStream.is() )
2043 pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
2044 ZIP_STORAGE_FORMAT_STRING, pImpl->xStream, css::embed::ElementModes::READWRITE,
2045 {}, bRepairPackage);
2047 else if ( pImpl->xInputStream.is() )
2049 pImpl->m_xZipStorage
2050 = ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
2051 ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream, {}, bRepairPackage);
2054 catch( const uno::Exception& )
2056 SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
2059 if ( GetErrorIgnoreWarning() ) // do not remove warnings
2060 ResetError();
2063 return pImpl->m_xZipStorage;
2067 void SfxMedium::CloseZipStorage_Impl()
2069 if ( pImpl->m_xZipStorage.is() )
2071 try {
2072 pImpl->m_xZipStorage->dispose();
2073 } catch( const uno::Exception& )
2076 pImpl->m_xZipStorage.clear();
2078 pImpl->m_xODFDecryptedInnerZipStorage.clear();
2081 void SfxMedium::CloseStorage()
2083 if ( pImpl->xStorage.is() )
2085 uno::Reference < lang::XComponent > xComp = pImpl->xStorage;
2086 // in the salvage mode the medium does not own the storage
2087 if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode )
2089 try {
2090 xComp->dispose();
2091 } catch( const uno::Exception& )
2093 SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" );
2097 pImpl->xStorage.clear();
2098 pImpl->m_xODFDecryptedInnerPackageStream.clear();
2099 // pImpl->m_xODFDecryptedInnerZipStorage.clear();
2100 pImpl->m_xODFEncryptedOuterStorage.clear();
2101 pImpl->bStorageBasedOnInStream = false;
2104 pImpl->m_bTriedStorage = false;
2105 pImpl->bIsStorage = false;
2108 void SfxMedium::CanDisposeStorage_Impl( bool bDisposeStorage )
2110 pImpl->bDisposeStorage = bDisposeStorage;
2113 bool SfxMedium::WillDisposeStorageOnClose_Impl()
2115 return pImpl->bDisposeStorage;
2118 StreamMode SfxMedium::GetOpenMode() const
2120 return pImpl->m_nStorOpenMode;
2123 void SfxMedium::SetOpenMode( StreamMode nStorOpen,
2124 bool bDontClose )
2126 if ( pImpl->m_nStorOpenMode != nStorOpen )
2128 pImpl->m_nStorOpenMode = nStorOpen;
2130 if( !bDontClose )
2132 if ( pImpl->xStorage.is() )
2133 CloseStorage();
2135 CloseStreams_Impl();
2141 bool SfxMedium::UseBackupToRestore_Impl( ::ucbhelper::Content& aOriginalContent,
2142 const Reference< css::ucb::XCommandEnvironment >& xComEnv )
2146 ::ucbhelper::Content aTransactCont( pImpl->m_aBackupURL, xComEnv, comphelper::getProcessComponentContext() );
2148 Reference< XInputStream > aOrigInput = aTransactCont.openStream();
2149 aOriginalContent.writeStream( aOrigInput, true );
2150 return true;
2152 catch( const Exception& )
2154 // in case of failure here the backup file should not be removed
2155 // TODO/LATER: a message should be used to let user know about the backup
2156 pImpl->m_bRemoveBackup = false;
2157 // TODO/LATER: needs a specific error code
2158 pImpl->m_eError = ERRCODE_IO_GENERAL;
2161 return false;
2165 bool SfxMedium::StorageCommit_Impl()
2167 bool bResult = false;
2168 Reference< css::ucb::XCommandEnvironment > xDummyEnv;
2169 ::ucbhelper::Content aOriginalContent;
2171 if ( pImpl->xStorage.is() )
2173 if ( !GetErrorIgnoreWarning() )
2175 uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY );
2176 if ( xTrans.is() )
2180 xTrans->commit();
2181 CloseZipStorage_Impl();
2182 bResult = true;
2184 catch ( const embed::UseBackupException& aBackupExc )
2186 // since the temporary file is created always now, the scenario is close to be impossible
2187 if ( !pImpl->pTempFile )
2189 OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" );
2190 if ( !pImpl->m_aBackupURL.isEmpty()
2191 && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
2192 xDummyEnv, comphelper::getProcessComponentContext(),
2193 aOriginalContent ) )
2195 // use backup to restore the file
2196 // the storage has already disconnected from original location
2197 CloseAndReleaseStreams_Impl();
2198 if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) )
2200 // connect the medium to the temporary file of the storage
2201 pImpl->aContent = ::ucbhelper::Content();
2202 pImpl->m_aName = aBackupExc.TemporaryFileURL;
2203 OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" );
2208 if (!GetErrorIgnoreWarning())
2209 SetError(ERRCODE_IO_GENERAL);
2211 catch ( const uno::Exception& )
2213 //TODO/LATER: improve error handling
2214 SetError(ERRCODE_IO_GENERAL);
2220 return bResult;
2224 void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource,
2225 const INetURLObject& aDest,
2226 const Reference< css::ucb::XCommandEnvironment >& xComEnv )
2228 Reference< css::ucb::XCommandEnvironment > xDummyEnv;
2229 ::ucbhelper::Content aOriginalContent;
2233 aOriginalContent = ::ucbhelper::Content( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
2235 catch ( const css::ucb::CommandAbortedException& )
2237 pImpl->m_eError = ERRCODE_ABORT;
2239 catch ( const css::ucb::CommandFailedException& )
2241 pImpl->m_eError = ERRCODE_ABORT;
2243 catch (const css::ucb::ContentCreationException& ex)
2245 pImpl->m_eError = ERRCODE_IO_GENERAL;
2246 if (
2247 (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) ||
2248 (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
2251 pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
2254 catch (const css::uno::Exception&)
2256 pImpl->m_eError = ERRCODE_IO_GENERAL;
2259 if( pImpl->m_eError && !pImpl->m_eError.IsWarning() )
2260 return;
2262 if ( pImpl->xStorage.is() )
2263 CloseStorage();
2265 CloseStreams_Impl();
2267 ::ucbhelper::Content aTempCont;
2268 if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aTempCont ) )
2270 bool bTransactStarted = false;
2271 const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2272 bool bOverWrite = !pOverWrite || pOverWrite->GetValue();
2273 bool bResult = false;
2277 // tdf#60237 - if the OverWrite property of the MediaDescriptor is set to false,
2278 // try to write the file before trying to rename or copy it
2279 if (!(bOverWrite
2280 && ::utl::UCBContentHelper::IsDocument(
2281 aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE))))
2283 Reference< XInputStream > aTempInput = aTempCont.openStream();
2284 aOriginalContent.writeStream( aTempInput, bOverWrite );
2285 bResult = true;
2286 } else {
2287 OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2288 OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2290 sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL);
2291 if (IsFileMovable(aDest)
2292 && osl::File::replace(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
2294 if (nAttributes)
2295 // Adjust attributes, source might be created with
2296 // the osl_File_OpenFlag_Private flag.
2297 osl::File::setAttributes(aDestMainURL, nAttributes);
2298 bResult = true;
2300 else
2302 if( pImpl->m_aBackupURL.isEmpty() )
2303 DoInternalBackup_Impl( aOriginalContent );
2305 if( !pImpl->m_aBackupURL.isEmpty() )
2307 Reference< XInputStream > aTempInput = aTempCont.openStream();
2308 bTransactStarted = true;
2309 aOriginalContent.setPropertyValue( "Size", uno::Any( sal_Int64(0) ) );
2310 aOriginalContent.writeStream( aTempInput, bOverWrite );
2311 bResult = true;
2313 else
2315 pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
2320 catch ( const css::ucb::CommandAbortedException& )
2322 pImpl->m_eError = ERRCODE_ABORT;
2324 catch ( const css::ucb::CommandFailedException& )
2326 pImpl->m_eError = ERRCODE_ABORT;
2328 catch ( const css::ucb::InteractiveIOException& r )
2330 if ( r.Code == IOErrorCode_ACCESS_DENIED )
2331 pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
2332 else if ( r.Code == IOErrorCode_NOT_EXISTING )
2333 pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2334 else if ( r.Code == IOErrorCode_CANT_READ )
2335 pImpl->m_eError = ERRCODE_IO_CANTREAD;
2336 else
2337 pImpl->m_eError = ERRCODE_IO_GENERAL;
2339 // tdf#60237 - if the file is already present, raise the appropriate error
2340 catch (const css::ucb::NameClashException& )
2342 pImpl->m_eError = ERRCODE_IO_ALREADYEXISTS;
2344 catch ( const css::uno::Exception& )
2346 pImpl->m_eError = ERRCODE_IO_GENERAL;
2349 if ( bResult )
2351 if ( pImpl->pTempFile )
2353 pImpl->pTempFile->EnableKillingFile();
2354 pImpl->pTempFile.reset();
2357 else if ( bTransactStarted && pImpl->m_eError != ERRCODE_ABORT )
2359 UseBackupToRestore_Impl( aOriginalContent, xDummyEnv );
2362 else
2363 pImpl->m_eError = ERRCODE_IO_CANTREAD;
2367 bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet )
2369 if ( GetErrorIgnoreWarning() )
2370 return false;
2372 // if the document had no password it should be stored without password
2373 // if the document had password it should be stored with the same password
2374 // otherwise the stream copying can not be done
2375 const SfxStringItem* pNewPassItem = aTargetSet.GetItem(SID_PASSWORD, false);
2376 const SfxStringItem* pOldPassItem = GetItemSet().GetItem(SID_PASSWORD, false);
2377 if ( ( !pNewPassItem && !pOldPassItem )
2378 || ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
2380 // the filter must be the same
2381 const SfxStringItem* pNewFilterItem = aTargetSet.GetItem(SID_FILTER_NAME, false);
2382 const SfxStringItem* pOldFilterItem = GetItemSet().GetItem(SID_FILTER_NAME, false);
2383 if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() )
2385 // get the input stream and copy it
2386 // in case of success return true
2387 uno::Reference< io::XInputStream > xInStream = GetInputStream();
2389 ResetError();
2390 if ( xInStream.is() )
2394 uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY );
2395 sal_Int64 nPos = 0;
2396 if ( xSeek.is() )
2398 nPos = xSeek->getPosition();
2399 xSeek->seek( 0 );
2402 uno::Reference < css::ucb::XCommandEnvironment > xEnv;
2403 ::ucbhelper::Content aTargetContent( aURL, xEnv, comphelper::getProcessComponentContext() );
2405 InsertCommandArgument aInsertArg;
2406 aInsertArg.Data = xInStream;
2407 const SfxBoolItem* pOverWrite = aTargetSet.GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2408 if ( pOverWrite && !pOverWrite->GetValue() ) // argument says: never overwrite
2409 aInsertArg.ReplaceExisting = false;
2410 else
2411 aInsertArg.ReplaceExisting = true; // default is overwrite existing files
2413 Any aCmdArg;
2414 aCmdArg <<= aInsertArg;
2415 aTargetContent.executeCommand( "insert",
2416 aCmdArg );
2418 if ( xSeek.is() )
2419 xSeek->seek( nPos );
2421 return true;
2423 catch( const uno::Exception& )
2429 return false;
2433 void SfxMedium::Transfer_Impl()
2435 // The transfer is required only in two cases: either if there is a temporary file or if there is a salvage item
2436 OUString aNameURL;
2437 if ( pImpl->pTempFile )
2438 aNameURL = pImpl->pTempFile->GetURL();
2439 else if ( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode )
2441 // makes sense only in case logic name is set
2442 if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aNameURL )
2443 != osl::FileBase::E_None )
2444 SAL_WARN( "sfx.doc", "The medium name is not convertible!" );
2447 if ( aNameURL.isEmpty() || ( pImpl->m_eError && !pImpl->m_eError.IsWarning() ) )
2448 return;
2450 SAL_INFO( "sfx.doc", "SfxMedium::Transfer_Impl, copying to target" );
2452 Reference < css::ucb::XCommandEnvironment > xEnv;
2453 Reference< XOutputStream > rOutStream;
2455 // in case an output stream is provided from outside and the URL is correct
2456 // commit to the stream
2457 if (pImpl->m_aLogicName.startsWith("private:stream"))
2459 // TODO/LATER: support storing to SID_STREAM
2460 const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
2461 if( pOutStreamItem && ( pOutStreamItem->GetValue() >>= rOutStream ) )
2463 if ( pImpl->xStorage.is() )
2464 CloseStorage();
2466 CloseStreams_Impl();
2468 INetURLObject aSource( aNameURL );
2469 ::ucbhelper::Content aTempCont;
2470 if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aTempCont ) )
2474 sal_Int32 nRead;
2475 sal_Int32 nBufferSize = 32767;
2476 Sequence < sal_Int8 > aSequence ( nBufferSize );
2477 Reference< XInputStream > aTempInput = aTempCont.openStream();
2481 nRead = aTempInput->readBytes ( aSequence, nBufferSize );
2482 if ( nRead < nBufferSize )
2484 Sequence < sal_Int8 > aTempBuf ( aSequence.getConstArray(), nRead );
2485 rOutStream->writeBytes ( aTempBuf );
2487 else
2488 rOutStream->writeBytes ( aSequence );
2490 while ( nRead == nBufferSize );
2492 // remove temporary file
2493 if ( pImpl->pTempFile )
2495 pImpl->pTempFile->EnableKillingFile();
2496 pImpl->pTempFile.reset();
2499 catch( const Exception& )
2503 else
2505 SAL_WARN( "sfx.doc", "Illegal Output stream parameter!" );
2506 SetError(ERRCODE_IO_GENERAL);
2509 // free the reference
2510 if ( pImpl->m_pSet )
2511 pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
2513 return;
2516 GetContent();
2517 if ( !pImpl->aContent.get().is() )
2519 pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2520 return;
2523 INetURLObject aDest( GetURLObject() );
2525 // source is the temp file written so far
2526 INetURLObject aSource( aNameURL );
2528 // a special case, an interaction handler should be used for
2529 // authentication in case it is available
2530 Reference< css::ucb::XCommandEnvironment > xComEnv;
2531 bool bForceInteractionHandler = GetURLObject().isAnyKnownWebDAVScheme();
2532 Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(bForceInteractionHandler);
2533 if (xInteractionHandler.is())
2534 xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler,
2535 Reference< css::ucb::XProgressHandler >() );
2537 OUString aDestURL( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2539 if ( comphelper::isFileUrl( aDestURL ) || !aDest.removeSegment() )
2541 TransactedTransferForFS_Impl( aSource, aDest, xComEnv );
2543 if (!pImpl->m_bDisableFileSync)
2545 // Hideous - no clean way to do this, so we re-open the file just to fsync it
2546 osl::File aFile( aDestURL );
2547 if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None )
2549 aFile.sync();
2550 SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" );
2551 aFile.close();
2555 else
2557 // create content for the parent folder and call transfer on that content with the source content
2558 // and the destination file name as parameters
2559 ::ucbhelper::Content aSourceContent;
2560 ::ucbhelper::Content aTransferContent;
2562 ::ucbhelper::Content aDestContent;
2563 (void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent );
2564 // For checkin, we need the object URL, not the parent folder:
2565 if ( !IsInCheckIn( ) )
2567 // Get the parent URL from the XChild if possible: why would the URL necessarily have
2568 // a hierarchical path? It's not always the case for CMIS.
2569 Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY );
2570 OUString sParentUrl;
2571 if ( xChild.is( ) )
2573 Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY );
2574 if ( xParent.is( ) )
2576 sParentUrl = xParent->getIdentifier( )->getContentIdentifier();
2580 if ( sParentUrl.isEmpty() )
2581 aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2582 // adjust to above aDest.removeSegment()
2583 else
2584 aDestURL = sParentUrl;
2587 // LongName wasn't defined anywhere, only used here... get the Title instead
2588 // as it's less probably empty
2589 OUString aFileName;
2590 OUString sObjectId;
2593 Any aAny = aDestContent.getPropertyValue("Title");
2594 aAny >>= aFileName;
2595 aAny = aDestContent.getPropertyValue("ObjectId");
2596 aAny >>= sObjectId;
2598 catch (uno::Exception const&)
2600 SAL_INFO("sfx.doc", "exception while getting Title or ObjectId");
2602 if ( aFileName.isEmpty() )
2603 aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2607 aTransferContent = ::ucbhelper::Content( aDestURL, xComEnv, comphelper::getProcessComponentContext() );
2609 catch (const css::ucb::ContentCreationException& ex)
2611 pImpl->m_eError = ERRCODE_IO_GENERAL;
2612 if (
2613 (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) ||
2614 (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
2617 pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
2620 catch (const css::uno::Exception&)
2622 pImpl->m_eError = ERRCODE_IO_GENERAL;
2625 if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() )
2627 // free resources, otherwise the transfer may fail
2628 if ( pImpl->xStorage.is() )
2629 CloseStorage();
2631 CloseStreams_Impl();
2633 (void)::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent );
2635 // check for external parameters that may customize the handling of NameClash situations
2636 const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2637 sal_Int32 nNameClash;
2638 if ( pOverWrite && !pOverWrite->GetValue() )
2639 // argument says: never overwrite
2640 nNameClash = NameClash::ERROR;
2641 else
2642 // default is overwrite existing files
2643 nNameClash = NameClash::OVERWRITE;
2647 OUString aMimeType = pImpl->getFilterMimeType();
2648 ::ucbhelper::InsertOperation eOperation = ::ucbhelper::InsertOperation::Copy;
2649 bool bMajor = false;
2650 OUString sComment;
2651 if ( IsInCheckIn( ) )
2653 eOperation = ::ucbhelper::InsertOperation::Checkin;
2654 const SfxBoolItem* pMajor = GetItemSet().GetItem<SfxBoolItem>(SID_DOCINFO_MAJOR, false);
2655 bMajor = pMajor && pMajor->GetValue( );
2656 const SfxStringItem* pComments = GetItemSet().GetItem(SID_DOCINFO_COMMENTS, false);
2657 if ( pComments )
2658 sComment = pComments->GetValue( );
2660 OUString sResultURL;
2661 aTransferContent.transferContent(
2662 aSourceContent, eOperation,
2663 aFileName, nNameClash, aMimeType, bMajor, sComment,
2664 &sResultURL, sObjectId );
2666 if ( !sResultURL.isEmpty( ) ) // Likely to happen only for checkin
2667 SwitchDocumentToFile( sResultURL );
2670 if ( GetURLObject().isAnyKnownWebDAVScheme() &&
2671 eOperation == ::ucbhelper::InsertOperation::Copy )
2673 // tdf#95272 try to re-issue a lock command when a new file is created.
2674 // This may be needed because some WebDAV servers fail to implement the
2675 // 'LOCK on unallocated reference', see issue comment:
2676 // <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8>
2677 // and specification at:
2678 // <http://tools.ietf.org/html/rfc4918#section-7.3>
2679 // If the WebDAV resource is already locked by this LO instance, nothing will
2680 // happen, e.g. the LOCK method will not be sent to the server.
2681 ::ucbhelper::Content aLockContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
2682 aLockContent.lock();
2685 catch ( css::uno::Exception & )
2687 TOOLS_WARN_EXCEPTION( "sfx.doc", "LOCK not working while re-issuing it" );
2690 catch ( const css::ucb::CommandAbortedException& )
2692 pImpl->m_eError = ERRCODE_ABORT;
2694 catch ( const css::ucb::CommandFailedException& )
2696 pImpl->m_eError = ERRCODE_ABORT;
2698 catch ( const css::ucb::InteractiveIOException& r )
2700 if ( r.Code == IOErrorCode_ACCESS_DENIED )
2701 pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
2702 else if ( r.Code == IOErrorCode_NOT_EXISTING )
2703 pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2704 else if ( r.Code == IOErrorCode_CANT_READ )
2705 pImpl->m_eError = ERRCODE_IO_CANTREAD;
2706 else
2707 pImpl->m_eError = ERRCODE_IO_GENERAL;
2709 catch ( const css::uno::Exception& )
2711 pImpl->m_eError = ERRCODE_IO_GENERAL;
2714 // do not switch from temporary file in case of nonfile protocol
2718 if ( ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) && !pImpl->pTempFile )
2720 // without a TempFile the physical and logical name should be the same after successful transfer
2721 if (osl::FileBase::getSystemPathFromFileURL(
2722 GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName )
2723 != osl::FileBase::E_None)
2725 pImpl->m_aName.clear();
2727 pImpl->m_bSalvageMode = false;
2732 void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent,
2733 std::u16string_view aPrefix,
2734 std::u16string_view aExtension,
2735 const OUString& aDestDir )
2737 if ( !pImpl->m_aBackupURL.isEmpty() )
2738 return; // the backup was done already
2740 ::utl::TempFileNamed aTransactTemp( aPrefix, true, aExtension, &aDestDir );
2742 INetURLObject aBackObj( aTransactTemp.GetURL() );
2743 OUString aBackupName = aBackObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2745 Reference < css::ucb::XCommandEnvironment > xDummyEnv;
2746 ::ucbhelper::Content aBackupCont;
2747 if( ::ucbhelper::Content::create( aDestDir, xDummyEnv, comphelper::getProcessComponentContext(), aBackupCont ) )
2751 OUString sMimeType = pImpl->getFilterMimeType();
2752 aBackupCont.transferContent( aOriginalContent,
2753 ::ucbhelper::InsertOperation::Copy,
2754 aBackupName,
2755 NameClash::OVERWRITE,
2756 sMimeType );
2757 pImpl->m_aBackupURL = aBackObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2758 pImpl->m_bRemoveBackup = true;
2760 catch( const Exception& )
2764 if ( pImpl->m_aBackupURL.isEmpty() )
2765 aTransactTemp.EnableKillingFile();
2769 void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent )
2771 if ( !pImpl->m_aBackupURL.isEmpty() )
2772 return; // the backup was done already
2774 OUString aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT,
2775 true,
2776 INetURLObject::DecodeMechanism::NONE );
2778 sal_Int32 nPrefixLen = aFileName.lastIndexOf( '.' );
2779 OUString aPrefix = ( nPrefixLen == -1 ) ? aFileName : aFileName.copy( 0, nPrefixLen );
2780 OUString aExtension = ( nPrefixLen == -1 ) ? OUString() : aFileName.copy( nPrefixLen );
2781 OUString aBakDir = SvtPathOptions().GetBackupPath();
2783 // create content for the parent folder ( = backup folder )
2784 ::ucbhelper::Content aContent;
2785 Reference < css::ucb::XCommandEnvironment > xEnv;
2786 if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
2787 DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aBakDir );
2789 if ( !pImpl->m_aBackupURL.isEmpty() )
2790 return;
2792 // the copying to the backup catalog failed ( for example because
2793 // of using an encrypted partition as target catalog )
2794 // since the user did not specify to make backup explicitly
2795 // office should try to make backup in another place,
2796 // target catalog does not look bad for this case ( and looks
2797 // to be the only way for encrypted partitions )
2799 INetURLObject aDest = GetURLObject();
2800 if ( aDest.removeSegment() )
2801 DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2805 void SfxMedium::DoBackup_Impl(bool bForceUsingBackupPath)
2807 // source file name is the logical name of this medium
2808 INetURLObject aSource( GetURLObject() );
2810 // there is nothing to backup in case source file does not exist
2811 if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
2812 return;
2814 bool bSuccess = false;
2815 bool bOnErrorRetryUsingBackupPath = false;
2817 // get path for backups
2818 OUString aBakDir;
2819 if (!bForceUsingBackupPath
2820 && officecfg::Office::Common::Save::Document::BackupIntoDocumentFolder::get())
2822 aBakDir = aSource.GetPartBeforeLastName();
2823 bOnErrorRetryUsingBackupPath = true;
2825 else
2826 aBakDir = SvtPathOptions().GetBackupPath();
2827 if( !aBakDir.isEmpty() )
2829 // create content for the parent folder ( = backup folder )
2830 ::ucbhelper::Content aContent;
2831 Reference < css::ucb::XCommandEnvironment > xEnv;
2832 if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
2834 // save as ".bak" file
2835 INetURLObject aDest( aBakDir );
2836 aDest.insertName( aSource.getName() );
2837 const OUString sExt
2838 = aSource.hasExtension() ? aSource.getExtension() + ".bak" : OUString("bak");
2839 aDest.setExtension(sExt);
2840 OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2842 // create a content for the source file
2843 ::ucbhelper::Content aSourceContent;
2844 if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
2848 // do the transfer ( copy source file to backup dir )
2849 OUString sMimeType = pImpl->getFilterMimeType();
2850 aContent.transferContent( aSourceContent,
2851 ::ucbhelper::InsertOperation::Copy,
2852 aFileName,
2853 NameClash::OVERWRITE,
2854 sMimeType );
2855 pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2856 pImpl->m_bRemoveBackup = false;
2857 bSuccess = true;
2859 catch ( const css::uno::Exception& )
2866 if ( !bSuccess )
2868 // in case a webdav server prevents file creation, or a partition is full, or whatever...
2869 if (bOnErrorRetryUsingBackupPath)
2870 return DoBackup_Impl(/*bForceUsingBackupPath=*/true);
2872 pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
2877 void SfxMedium::ClearBackup_Impl()
2879 if( pImpl->m_bRemoveBackup )
2881 // currently a document is always stored in a new medium,
2882 // thus if a backup can not be removed the backup URL should not be cleaned
2883 if ( !pImpl->m_aBackupURL.isEmpty() )
2885 if ( ::utl::UCBContentHelper::Kill( pImpl->m_aBackupURL ) )
2887 pImpl->m_bRemoveBackup = false;
2888 pImpl->m_aBackupURL.clear();
2890 else
2893 SAL_WARN( "sfx.doc", "Couldn't remove backup file!");
2897 else
2898 pImpl->m_aBackupURL.clear();
2902 void SfxMedium::GetLockingStream_Impl()
2904 if ( GetURLObject().GetProtocol() != INetProtocol::File
2905 || pImpl->m_xLockingStream.is() )
2906 return;
2908 const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
2909 if ( pWriteStreamItem )
2910 pWriteStreamItem->GetValue() >>= pImpl->m_xLockingStream;
2912 if ( pImpl->m_xLockingStream.is() )
2913 return;
2915 // open the original document
2916 uno::Sequence< beans::PropertyValue > xProps;
2917 TransformItems( SID_OPENDOC, GetItemSet(), xProps );
2918 utl::MediaDescriptor aMedium( xProps );
2920 aMedium.addInputStreamOwnLock();
2922 uno::Reference< io::XInputStream > xInputStream;
2923 aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->m_xLockingStream;
2924 aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream;
2926 if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() )
2928 // the medium is still based on the original file, it makes sense to initialize the streams
2929 if ( pImpl->m_xLockingStream.is() )
2930 pImpl->xStream = pImpl->m_xLockingStream;
2932 if ( xInputStream.is() )
2933 pImpl->xInputStream = xInputStream;
2935 if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
2936 pImpl->xInputStream = pImpl->xStream->getInputStream();
2941 void SfxMedium::GetMedium_Impl()
2943 if ( pImpl->m_pInStream
2944 && (!pImpl->bIsTemp || pImpl->xInputStream.is() || pImpl->m_xInputStreamToLoadFrom.is() || pImpl->xStream.is() || pImpl->m_xLockingStream.is() ) )
2945 return;
2947 pImpl->bDownloadDone = false;
2948 Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler();
2950 //TODO/MBA: need support for SID_STREAM
2951 const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
2952 const SfxUnoAnyItem* pInStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INPUTSTREAM, false);
2953 if ( pWriteStreamItem )
2955 pWriteStreamItem->GetValue() >>= pImpl->xStream;
2957 if ( pInStreamItem )
2958 pInStreamItem->GetValue() >>= pImpl->xInputStream;
2960 if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
2961 pImpl->xInputStream = pImpl->xStream->getInputStream();
2963 else if ( pInStreamItem )
2965 pInStreamItem->GetValue() >>= pImpl->xInputStream;
2967 else
2969 uno::Sequence < beans::PropertyValue > xProps;
2970 OUString aFileName;
2971 if (!pImpl->m_aName.isEmpty())
2973 if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aFileName )
2974 != osl::FileBase::E_None )
2976 SAL_WARN( "sfx.doc", "Physical name not convertible!");
2979 else
2980 aFileName = GetName();
2982 // in case the temporary file exists the streams should be initialized from it,
2983 // but the original MediaDescriptor should not be changed
2984 bool bFromTempFile = ( pImpl->pTempFile != nullptr );
2986 if ( !bFromTempFile )
2988 GetItemSet().Put( SfxStringItem( SID_FILE_NAME, aFileName ) );
2989 if( !(pImpl->m_nStorOpenMode & StreamMode::WRITE) )
2990 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
2991 if (xInteractionHandler.is())
2992 GetItemSet().Put( SfxUnoAnyItem( SID_INTERACTIONHANDLER, Any(xInteractionHandler) ) );
2995 if ( pImpl->m_xInputStreamToLoadFrom.is() )
2997 pImpl->xInputStream = pImpl->m_xInputStreamToLoadFrom;
2998 if (pImpl->m_bInputStreamIsReadOnly)
2999 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
3001 else
3003 TransformItems( SID_OPENDOC, GetItemSet(), xProps );
3004 utl::MediaDescriptor aMedium( xProps );
3006 if ( pImpl->m_xLockingStream.is() && !bFromTempFile )
3008 // the medium is not based on the temporary file, so the original stream can be used
3009 pImpl->xStream = pImpl->m_xLockingStream;
3011 else
3013 if ( bFromTempFile )
3015 aMedium[utl::MediaDescriptor::PROP_URL] <<= aFileName;
3016 aMedium.erase( utl::MediaDescriptor::PROP_READONLY );
3017 aMedium.addInputStream();
3019 else if ( GetURLObject().GetProtocol() == INetProtocol::File )
3021 // use the special locking approach only for file URLs
3022 aMedium.addInputStreamOwnLock();
3024 else
3026 // add a check for protocol, if it's http or https or provide webdav then add
3027 // the interaction handler to be used by the authentication dialog
3028 if ( GetURLObject().isAnyKnownWebDAVScheme() )
3030 aMedium[utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER] <<= GetInteractionHandler( true );
3032 aMedium.addInputStream();
3034 // the ReadOnly property set in aMedium is ignored
3035 // the check is done in LockOrigFileOnDemand() for file and non-file URLs
3037 //TODO/MBA: what happens if property is not there?!
3038 aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->xStream;
3039 aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= pImpl->xInputStream;
3042 GetContent();
3043 if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
3044 pImpl->xInputStream = pImpl->xStream->getInputStream();
3047 if ( !bFromTempFile )
3049 //TODO/MBA: need support for SID_STREAM
3050 if ( pImpl->xStream.is() )
3051 GetItemSet().Put( SfxUnoAnyItem( SID_STREAM, Any( pImpl->xStream ) ) );
3053 GetItemSet().Put( SfxUnoAnyItem( SID_INPUTSTREAM, Any( pImpl->xInputStream ) ) );
3057 //TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor
3058 if ( !GetErrorIgnoreWarning() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
3059 SetError(ERRCODE_IO_ACCESSDENIED);
3061 if ( !GetErrorIgnoreWarning() && !pImpl->m_pInStream )
3063 if ( pImpl->xStream.is() )
3064 pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xStream );
3065 else if ( pImpl->xInputStream.is() )
3066 pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xInputStream );
3069 pImpl->bDownloadDone = true;
3070 pImpl->aDoneLink.ClearPendingCall();
3071 ErrCodeMsg nError = GetErrorIgnoreWarning();
3072 pImpl->aDoneLink.Call( reinterpret_cast<void*>(sal_uInt32(nError.GetCode())) );
3075 bool SfxMedium::IsRemote() const
3077 return pImpl->m_bRemote;
3080 void SfxMedium::SetUpdatePickList(bool bVal)
3082 pImpl->bUpdatePickList = bVal;
3085 bool SfxMedium::IsUpdatePickList() const
3087 return pImpl->bUpdatePickList;
3090 void SfxMedium::SetLongName(const OUString &rName)
3092 pImpl->m_aLongName = rName;
3095 const OUString& SfxMedium::GetLongName() const
3097 return pImpl->m_aLongName;
3100 void SfxMedium::SetDoneLink( const Link<void*,void>& rLink )
3102 pImpl->aDoneLink = rLink;
3105 void SfxMedium::Download( const Link<void*,void>& aLink )
3107 SetDoneLink( aLink );
3108 GetInStream();
3109 if ( pImpl->m_pInStream && !aLink.IsSet() )
3111 while( !pImpl->bDownloadDone && !Application::IsQuit())
3112 Application::Yield();
3118 Sets m_aLogicName to a valid URL and if available sets
3119 the physical name m_aName to the file name.
3121 void SfxMedium::Init_Impl()
3123 Reference< XOutputStream > rOutStream;
3125 // TODO/LATER: handle lifetime of storages
3126 pImpl->bDisposeStorage = false;
3128 const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
3129 if ( pSalvageItem && pSalvageItem->GetValue().isEmpty() )
3131 pSalvageItem = nullptr;
3132 pImpl->m_pSet->ClearItem( SID_DOC_SALVAGE );
3135 if (!pImpl->m_aLogicName.isEmpty())
3137 INetURLObject aUrl( pImpl->m_aLogicName );
3138 INetProtocol eProt = aUrl.GetProtocol();
3139 if ( eProt == INetProtocol::NotValid )
3141 SAL_WARN( "sfx.doc", "URL <" << pImpl->m_aLogicName << "> with unknown protocol" );
3143 else
3145 if ( aUrl.HasMark() )
3147 std::unique_lock<std::recursive_mutex> chkEditLock;
3148 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3149 chkEditLock = std::unique_lock<std::recursive_mutex>(
3150 *(pImpl->m_pCheckEditableWorkerMutex));
3151 pImpl->m_aLogicName = aUrl.GetURLNoMark( INetURLObject::DecodeMechanism::NONE );
3152 if (chkEditLock.owns_lock())
3153 chkEditLock.unlock();
3154 GetItemSet().Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() ) );
3157 // try to convert the URL into a physical name - but never change a physical name
3158 // physical name may be set if the logical name is changed after construction
3159 if ( pImpl->m_aName.isEmpty() )
3160 osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName );
3161 else
3163 DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" );
3168 if ( pSalvageItem )
3170 std::unique_lock<std::recursive_mutex> chkEditLock;
3171 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3172 chkEditLock
3173 = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3174 pImpl->m_aLogicName = pSalvageItem->GetValue();
3175 pImpl->m_pURLObj.reset();
3176 if (chkEditLock.owns_lock())
3177 chkEditLock.unlock();
3178 pImpl->m_bSalvageMode = true;
3181 // in case output stream is by mistake here
3182 // clear the reference
3183 const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
3184 if( pOutStreamItem
3185 && ( !( pOutStreamItem->GetValue() >>= rOutStream )
3186 || !pImpl->m_aLogicName.startsWith("private:stream")) )
3188 pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
3189 SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" );
3192 if (!pImpl->m_aLogicName.isEmpty())
3194 // if the logic name is set it should be set in MediaDescriptor as well
3195 const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3196 if ( !pFileNameItem )
3198 // let the ItemSet be created if necessary
3199 GetItemSet().Put(
3200 SfxStringItem(
3201 SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
3205 SetIsRemote_Impl();
3207 osl::DirectoryItem item;
3208 if (osl::DirectoryItem::get(GetName(), item) == osl::FileBase::E_None) {
3209 osl::FileStatus stat(osl_FileStatus_Mask_Attributes);
3210 if (item.getFileStatus(stat) == osl::FileBase::E_None
3211 && stat.isValid(osl_FileStatus_Mask_Attributes))
3213 if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0)
3215 pImpl->m_bOriginallyReadOnly = true;
3222 SfxMedium::SfxMedium() : pImpl(new SfxMedium_Impl)
3224 Init_Impl();
3228 void SfxMedium::UseInteractionHandler( bool bUse )
3230 pImpl->bAllowDefaultIntHdl = bUse;
3234 css::uno::Reference< css::task::XInteractionHandler >
3235 SfxMedium::GetInteractionHandler( bool bGetAlways )
3237 // if interaction isn't allowed explicitly ... return empty reference!
3238 if ( !bGetAlways && !pImpl->bUseInteractionHandler )
3239 return css::uno::Reference< css::task::XInteractionHandler >();
3241 // search a possible existing handler inside cached item set
3242 if ( pImpl->m_pSet )
3244 css::uno::Reference< css::task::XInteractionHandler > xHandler;
3245 const SfxUnoAnyItem* pHandler = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INTERACTIONHANDLER, false);
3246 if ( pHandler && (pHandler->GetValue() >>= xHandler) && xHandler.is() )
3247 return xHandler;
3250 // if default interaction isn't allowed explicitly ... return empty reference!
3251 if ( !bGetAlways && !pImpl->bAllowDefaultIntHdl )
3252 return css::uno::Reference< css::task::XInteractionHandler >();
3254 // otherwise return cached default handler ... if it exist.
3255 if ( pImpl->xInteraction.is() )
3256 return pImpl->xInteraction;
3258 // create default handler and cache it!
3259 Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
3260 pImpl->xInteraction.set(
3261 task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW );
3262 return pImpl->xInteraction;
3265 void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter )
3267 pImpl->m_pFilter = pFilter;
3270 const std::shared_ptr<const SfxFilter>& SfxMedium::GetFilter() const
3272 return pImpl->m_pFilter;
3275 sal_uInt32 SfxMedium::CreatePasswordToModifyHash( std::u16string_view aPasswd, bool bWriter )
3277 sal_uInt32 nHash = 0;
3279 if ( !aPasswd.empty() )
3281 if ( bWriter )
3283 nHash = ::comphelper::DocPasswordHelper::GetWordHashAsUINT32( aPasswd );
3285 else
3287 rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
3288 nHash = ::comphelper::DocPasswordHelper::GetXLHashAsUINT16( aPasswd, nEncoding );
3292 return nHash;
3296 void SfxMedium::Close(bool bInDestruction)
3298 if ( pImpl->xStorage.is() )
3300 CloseStorage();
3303 CloseStreams_Impl(bInDestruction);
3305 UnlockFile( false );
3308 void SfxMedium::CloseAndRelease()
3310 if ( pImpl->xStorage.is() )
3312 CloseStorage();
3315 CloseAndReleaseStreams_Impl();
3317 UnlockFile( true );
3320 void SfxMedium::DisableUnlockWebDAV( bool bDisableUnlockWebDAV )
3322 pImpl->m_bDisableUnlockWebDAV = bDisableUnlockWebDAV;
3325 void SfxMedium::DisableFileSync(bool bDisableFileSync)
3327 pImpl->m_bDisableFileSync = bDisableFileSync;
3330 void SfxMedium::UnlockFile( bool bReleaseLockStream )
3332 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
3333 (void) bReleaseLockStream;
3334 #else
3335 // check if webdav
3336 if ( GetURLObject().isAnyKnownWebDAVScheme() )
3338 // do nothing if WebDAV locking if disabled
3339 // (shouldn't happen because we already skipped locking,
3340 // see LockOrigFileOnDemand, but just in case ...)
3341 if (!IsWebDAVLockingUsed())
3342 return;
3344 if ( pImpl->m_bLocked )
3346 // an interaction handler should be used for authentication, if needed
3347 try {
3348 uno::Reference< css::task::XInteractionHandler > xHandler = GetInteractionHandler( true );
3349 uno::Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( xHandler,
3350 Reference< css::ucb::XProgressHandler >() );
3351 ucbhelper::Content aContentToUnlock( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext());
3352 pImpl->m_bLocked = false;
3353 //check if WebDAV unlock was explicitly disabled
3354 if ( !pImpl->m_bDisableUnlockWebDAV )
3355 aContentToUnlock.unlock();
3357 catch ( uno::Exception& )
3359 TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
3362 return;
3365 if ( pImpl->m_xLockingStream.is() )
3367 if ( bReleaseLockStream )
3371 uno::Reference< io::XInputStream > xInStream = pImpl->m_xLockingStream->getInputStream();
3372 uno::Reference< io::XOutputStream > xOutStream = pImpl->m_xLockingStream->getOutputStream();
3373 if ( xInStream.is() )
3374 xInStream->closeInput();
3375 if ( xOutStream.is() )
3376 xOutStream->closeOutput();
3378 catch( const uno::Exception& )
3382 pImpl->m_xLockingStream.clear();
3385 if ( !pImpl->m_bLocked )
3386 return;
3390 ::svt::DocumentLockFile aLockFile(pImpl->m_aLogicName);
3394 pImpl->m_bLocked = false;
3395 // TODO/LATER: A warning could be shown in case the file is not the own one
3396 aLockFile.RemoveFile();
3398 catch (const io::WrongFormatException&)
3400 // erase the empty or corrupt file
3401 aLockFile.RemoveFileDirectly();
3404 catch( const uno::Exception& )
3407 if(!pImpl->m_bMSOLockFileCreated)
3408 return;
3412 ::svt::MSODocumentLockFile aMSOLockFile(pImpl->m_aLogicName);
3416 pImpl->m_bLocked = false;
3417 // TODO/LATER: A warning could be shown in case the file is not the own one
3418 aMSOLockFile.RemoveFile();
3420 catch (const io::WrongFormatException&)
3422 // erase the empty or corrupt file
3423 aMSOLockFile.RemoveFileDirectly();
3426 catch( const uno::Exception& )
3428 pImpl->m_bMSOLockFileCreated = false;
3429 #endif
3432 void SfxMedium::CloseAndReleaseStreams_Impl()
3434 CloseZipStorage_Impl();
3436 uno::Reference< io::XInputStream > xInToClose = pImpl->xInputStream;
3437 uno::Reference< io::XOutputStream > xOutToClose;
3438 if ( pImpl->xStream.is() )
3440 xOutToClose = pImpl->xStream->getOutputStream();
3442 // if the locking stream is closed here the related member should be cleaned
3443 if ( pImpl->xStream == pImpl->m_xLockingStream )
3444 pImpl->m_xLockingStream.clear();
3447 // The probably existing SvStream wrappers should be closed first
3448 CloseStreams_Impl();
3450 // in case of salvage mode the storage is based on the streams
3451 if ( pImpl->m_bSalvageMode )
3452 return;
3456 if ( xInToClose.is() )
3457 xInToClose->closeInput();
3458 if ( xOutToClose.is() )
3459 xOutToClose->closeOutput();
3461 catch ( const uno::Exception& )
3467 void SfxMedium::CloseStreams_Impl(bool bInDestruction)
3469 CloseInStream_Impl(bInDestruction);
3470 CloseOutStream_Impl();
3472 if ( pImpl->m_pSet )
3473 pImpl->m_pSet->ClearItem( SID_CONTENT );
3475 pImpl->aContent = ::ucbhelper::Content();
3479 void SfxMedium::SetIsRemote_Impl()
3481 INetURLObject aObj( GetName() );
3482 switch( aObj.GetProtocol() )
3484 case INetProtocol::Ftp:
3485 case INetProtocol::Http:
3486 case INetProtocol::Https:
3487 pImpl->m_bRemote = true;
3488 break;
3489 default:
3490 pImpl->m_bRemote = GetName().startsWith("private:msgid");
3491 break;
3494 // As files that are written to the remote transmission must also be able
3495 // to be read.
3496 if (pImpl->m_bRemote)
3497 pImpl->m_nStorOpenMode |= StreamMode::READ;
3501 void SfxMedium::SetName( const OUString& aNameP, bool bSetOrigURL )
3503 if (pImpl->aOrigURL.isEmpty())
3504 pImpl->aOrigURL = pImpl->m_aLogicName;
3505 if( bSetOrigURL )
3506 pImpl->aOrigURL = aNameP;
3507 std::unique_lock<std::recursive_mutex> chkEditLock;
3508 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3509 chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3510 pImpl->m_aLogicName = aNameP;
3511 pImpl->m_pURLObj.reset();
3512 if (chkEditLock.owns_lock())
3513 chkEditLock.unlock();
3514 pImpl->aContent = ::ucbhelper::Content();
3515 Init_Impl();
3519 const OUString& SfxMedium::GetOrigURL() const
3521 return pImpl->aOrigURL.isEmpty() ? pImpl->m_aLogicName : pImpl->aOrigURL;
3525 void SfxMedium::SetPhysicalName_Impl( const OUString& rNameP )
3527 if ( rNameP != pImpl->m_aName )
3529 pImpl->pTempFile.reset();
3531 if ( !pImpl->m_aName.isEmpty() || !rNameP.isEmpty() )
3532 pImpl->aContent = ::ucbhelper::Content();
3534 pImpl->m_aName = rNameP;
3535 pImpl->m_bTriedStorage = false;
3536 pImpl->bIsStorage = false;
3540 void SfxMedium::ReOpen()
3542 bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
3543 pImpl->bUseInteractionHandler = false;
3544 GetMedium_Impl();
3545 pImpl->bUseInteractionHandler = bUseInteractionHandler;
3548 void SfxMedium::CompleteReOpen()
3550 // do not use temporary file for reopen and in case of success throw the temporary file away
3551 bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
3552 pImpl->bUseInteractionHandler = false;
3554 std::unique_ptr<MediumTempFile> pTmpFile;
3555 if ( pImpl->pTempFile )
3557 pTmpFile = std::move(pImpl->pTempFile);
3558 pImpl->m_aName.clear();
3561 GetMedium_Impl();
3563 if ( GetErrorIgnoreWarning() )
3565 if ( pImpl->pTempFile )
3567 pImpl->pTempFile->EnableKillingFile();
3568 pImpl->pTempFile.reset();
3570 pImpl->pTempFile = std::move( pTmpFile );
3571 if ( pImpl->pTempFile )
3572 pImpl->m_aName = pImpl->pTempFile->GetFileName();
3574 else if (pTmpFile)
3576 pTmpFile->EnableKillingFile();
3577 pTmpFile.reset();
3580 pImpl->bUseInteractionHandler = bUseInteractionHandler;
3583 SfxMedium::SfxMedium(const OUString &rName, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
3584 pImpl(new SfxMedium_Impl)
3586 pImpl->m_pSet = pInSet;
3587 pImpl->m_pFilter = std::move(pFilter);
3588 pImpl->m_aLogicName = rName;
3589 pImpl->m_nStorOpenMode = nOpenMode;
3590 Init_Impl();
3593 SfxMedium::SfxMedium(const OUString &rName, const OUString &rReferer, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
3594 pImpl(new SfxMedium_Impl)
3596 pImpl->m_pSet = pInSet;
3597 SfxItemSet& s = GetItemSet();
3598 if (s.GetItem(SID_REFERER) == nullptr) {
3599 s.Put(SfxStringItem(SID_REFERER, rReferer));
3601 pImpl->m_pFilter = std::move(pFilter);
3602 pImpl->m_aLogicName = rName;
3603 pImpl->m_nStorOpenMode = nOpenMode;
3604 Init_Impl();
3607 SfxMedium::SfxMedium( const uno::Sequence<beans::PropertyValue>& aArgs ) :
3608 pImpl(new SfxMedium_Impl)
3610 SfxAllItemSet *pParams = new SfxAllItemSet( SfxGetpApp()->GetPool() );
3611 pImpl->m_pSet.reset( pParams );
3612 TransformParameters( SID_OPENDOC, aArgs, *pParams );
3613 SetArgs(aArgs);
3615 OUString aFilterProvider, aFilterName;
3617 const SfxStringItem* pItem = nullptr;
3618 if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_PROVIDER)))
3619 aFilterProvider = pItem->GetValue();
3621 if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_NAME)))
3622 aFilterName = pItem->GetValue();
3625 if (aFilterProvider.isEmpty())
3627 // This is a conventional filter type.
3628 pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( aFilterName );
3630 else
3632 // This filter is from an external provider such as orcus.
3633 pImpl->m_pCustomFilter = std::make_shared<SfxFilter>(aFilterProvider, aFilterName);
3634 pImpl->m_pFilter = pImpl->m_pCustomFilter;
3637 const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
3638 if( pSalvageItem )
3640 // QUESTION: there is some treatment of Salvage in Init_Impl; align!
3641 if ( !pSalvageItem->GetValue().isEmpty() )
3643 // if a URL is provided in SalvageItem that means that the FileName refers to a temporary file
3644 // that must be copied here
3646 const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3647 if (!pFileNameItem) throw uno::RuntimeException();
3648 OUString aNewTempFileURL = SfxMedium::CreateTempCopyWithExt( pFileNameItem->GetValue() );
3649 if ( !aNewTempFileURL.isEmpty() )
3651 pImpl->m_pSet->Put( SfxStringItem( SID_FILE_NAME, aNewTempFileURL ) );
3652 pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
3653 pImpl->m_pSet->ClearItem( SID_STREAM );
3654 pImpl->m_pSet->ClearItem( SID_CONTENT );
3656 else
3658 SAL_WARN( "sfx.doc", "Can not create a new temporary file for crash recovery!" );
3663 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
3664 if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
3665 pImpl->m_bOriginallyLoadedReadOnly = true;
3667 const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3668 if (!pFileNameItem) throw uno::RuntimeException();
3669 pImpl->m_aLogicName = pFileNameItem->GetValue();
3670 pImpl->m_nStorOpenMode = pImpl->m_bOriginallyLoadedReadOnly
3671 ? SFX_STREAM_READONLY : SFX_STREAM_READWRITE;
3672 Init_Impl();
3675 void SfxMedium::SetArgs(const uno::Sequence<beans::PropertyValue>& rArgs)
3677 static constexpr OUStringLiteral sStream(u"Stream");
3678 static constexpr OUStringLiteral sInputStream(u"InputStream");
3679 comphelper::SequenceAsHashMap aArgsMap(rArgs);
3680 aArgsMap.erase(sStream);
3681 aArgsMap.erase(sInputStream);
3682 pImpl->m_aArgs = aArgsMap.getAsConstPropertyValueList();
3685 const uno::Sequence<beans::PropertyValue> & SfxMedium::GetArgs() const { return pImpl->m_aArgs; }
3687 SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const std::shared_ptr<SfxItemSet>& p ) :
3688 pImpl(new SfxMedium_Impl)
3690 OUString aType = SfxFilter::GetTypeFromStorage(rStor);
3691 pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( aType );
3692 DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
3694 Init_Impl();
3695 pImpl->xStorage = rStor;
3696 pImpl->bDisposeStorage = false;
3698 // always take BaseURL first, could be overwritten by ItemSet
3699 GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
3700 if ( p )
3701 GetItemSet().Put( *p );
3705 SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const OUString &rTypeName, const std::shared_ptr<SfxItemSet>& p ) :
3706 pImpl(new SfxMedium_Impl)
3708 pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( rTypeName );
3709 DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
3711 Init_Impl();
3712 pImpl->xStorage = rStor;
3713 pImpl->bDisposeStorage = false;
3715 // always take BaseURL first, could be overwritten by ItemSet
3716 GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
3717 if ( p )
3718 GetItemSet().Put( *p );
3721 // NOTE: should only be called on main thread
3722 SfxMedium::~SfxMedium()
3724 CancelCheckEditableEntry();
3726 // if there is a requirement to clean the backup this is the last possibility to do it
3727 ClearBackup_Impl();
3729 Close(/*bInDestruction*/true);
3731 if( !pImpl->bIsTemp || pImpl->m_aName.isEmpty() )
3732 return;
3734 OUString aTemp;
3735 if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aTemp )
3736 != osl::FileBase::E_None )
3738 SAL_WARN( "sfx.doc", "Physical name not convertible!");
3741 if ( !::utl::UCBContentHelper::Kill( aTemp ) )
3743 SAL_WARN( "sfx.doc", "Couldn't remove temporary file!");
3747 const OUString& SfxMedium::GetName() const
3749 return pImpl->m_aLogicName;
3752 const INetURLObject& SfxMedium::GetURLObject() const
3754 std::unique_lock<std::recursive_mutex> chkEditLock;
3755 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3756 chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3758 if (!pImpl->m_pURLObj)
3760 pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) );
3761 pImpl->m_pURLObj->SetMark(u"");
3764 return *pImpl->m_pURLObj;
3767 void SfxMedium::SetExpired_Impl( const DateTime& rDateTime )
3769 pImpl->aExpireTime = rDateTime;
3773 bool SfxMedium::IsExpired() const
3775 return pImpl->aExpireTime.IsValidAndGregorian() && pImpl->aExpireTime < DateTime( DateTime::SYSTEM );
3779 SfxFrame* SfxMedium::GetLoadTargetFrame() const
3781 return pImpl->wLoadTargetFrame;
3784 void SfxMedium::setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream>& xInputStream, bool bIsReadOnly )
3786 pImpl->m_xInputStreamToLoadFrom = xInputStream;
3787 pImpl->m_bInputStreamIsReadOnly = bIsReadOnly;
3790 void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame )
3792 pImpl->wLoadTargetFrame = pFrame;
3795 void SfxMedium::SetStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
3797 pImpl->xStorage = xStorage;
3798 pImpl->m_bODFWholesomeEncryption = false;
3801 void SfxMedium::SetInnerStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
3803 pImpl->xStorage = xStorage;
3804 pImpl->m_bODFWholesomeEncryption = true;
3807 SfxItemSet& SfxMedium::GetItemSet() const
3809 if (!pImpl->m_pSet)
3810 pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() );
3811 return *pImpl->m_pSet;
3815 SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl()
3817 if( !pImpl->xAttributes.is() )
3819 pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator );
3821 if ( GetContent().is() )
3825 Any aAny = pImpl->aContent.getPropertyValue("MediaType");
3826 OUString aContentType;
3827 aAny >>= aContentType;
3829 pImpl->xAttributes->Append( SvKeyValue( "content-type", aContentType ) );
3831 catch ( const css::uno::Exception& )
3837 return pImpl->xAttributes.get();
3840 css::uno::Reference< css::io::XInputStream > const & SfxMedium::GetInputStream()
3842 if ( !pImpl->xInputStream.is() )
3843 GetMedium_Impl();
3844 return pImpl->xInputStream;
3847 const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload )
3849 // if the medium has no name, then this medium should represent a new document and can have no version info
3850 if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.hasElements() &&
3851 ( !pImpl->m_aName.isEmpty() || !pImpl->m_aLogicName.isEmpty() ) && GetStorage().is() )
3853 uno::Reference < document::XDocumentRevisionListPersistence > xReader =
3854 document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3857 pImpl->aVersions = xReader->load( GetStorage() );
3859 catch ( const uno::Exception& )
3864 if ( !pImpl->m_bVersionsAlreadyLoaded )
3865 pImpl->m_bVersionsAlreadyLoaded = true;
3867 return pImpl->aVersions;
3870 uno::Sequence < util::RevisionTag > SfxMedium::GetVersionList( const uno::Reference < embed::XStorage >& xStorage )
3872 uno::Reference < document::XDocumentRevisionListPersistence > xReader =
3873 document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3876 return xReader->load( xStorage );
3878 catch ( const uno::Exception& )
3882 return uno::Sequence < util::RevisionTag >();
3885 void SfxMedium::AddVersion_Impl( util::RevisionTag& rRevision )
3887 if ( !GetStorage().is() )
3888 return;
3890 // To determine a unique name for the stream
3891 std::vector<sal_uInt32> aLongs;
3892 sal_Int32 nLength = pImpl->aVersions.getLength();
3893 for ( const auto& rVersion : std::as_const(pImpl->aVersions) )
3895 sal_uInt32 nVer = static_cast<sal_uInt32>( o3tl::toInt32(rVersion.Identifier.subView(7)));
3896 size_t n;
3897 for ( n=0; n<aLongs.size(); ++n )
3898 if ( nVer<aLongs[n] )
3899 break;
3901 aLongs.insert( aLongs.begin()+n, nVer );
3904 std::vector<sal_uInt32>::size_type nKey;
3905 for ( nKey=0; nKey<aLongs.size(); ++nKey )
3906 if ( aLongs[nKey] > nKey+1 )
3907 break;
3909 OUString aRevName = "Version" + OUString::number( nKey + 1 );
3910 pImpl->aVersions.realloc( nLength+1 );
3911 rRevision.Identifier = aRevName;
3912 pImpl->aVersions.getArray()[nLength] = rRevision;
3915 void SfxMedium::RemoveVersion_Impl( const OUString& rName )
3917 if ( !pImpl->aVersions.hasElements() )
3918 return;
3920 auto pVersion = std::find_if(std::cbegin(pImpl->aVersions), std::cend(pImpl->aVersions),
3921 [&rName](const auto& rVersion) { return rVersion.Identifier == rName; });
3922 if (pVersion != std::cend(pImpl->aVersions))
3924 auto nIndex = static_cast<sal_Int32>(std::distance(std::cbegin(pImpl->aVersions), pVersion));
3925 comphelper::removeElementAt(pImpl->aVersions, nIndex);
3929 bool SfxMedium::TransferVersionList_Impl( SfxMedium const & rMedium )
3931 if ( rMedium.pImpl->aVersions.hasElements() )
3933 pImpl->aVersions = rMedium.pImpl->aVersions;
3934 return true;
3937 return false;
3940 void SfxMedium::SaveVersionList_Impl()
3942 if ( !GetStorage().is() )
3943 return;
3945 if ( !pImpl->aVersions.hasElements() )
3946 return;
3948 uno::Reference < document::XDocumentRevisionListPersistence > xWriter =
3949 document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3952 xWriter->store( GetStorage(), pImpl->aVersions );
3954 catch ( const uno::Exception& )
3959 bool SfxMedium::IsReadOnly() const
3961 // a) ReadOnly filter can't produce read/write contents!
3962 bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY);
3964 // b) if filter allow read/write contents .. check open mode of the storage
3965 if (!bReadOnly)
3966 bReadOnly = !( GetOpenMode() & StreamMode::WRITE );
3968 // c) the API can force the readonly state!
3969 if (!bReadOnly)
3971 const SfxBoolItem* pItem = GetItemSet().GetItem(SID_DOC_READONLY, false);
3972 if (pItem)
3973 bReadOnly = pItem->GetValue();
3976 return bReadOnly;
3979 bool SfxMedium::IsOriginallyReadOnly() const
3981 return pImpl->m_bOriginallyReadOnly;
3984 void SfxMedium::SetOriginallyReadOnly(bool val)
3986 pImpl->m_bOriginallyReadOnly = val;
3989 bool SfxMedium::IsOriginallyLoadedReadOnly() const
3991 return pImpl->m_bOriginallyLoadedReadOnly;
3994 bool SfxMedium::SetWritableForUserOnly( const OUString& aURL )
3996 // UCB does not allow to allow write access only for the user,
3997 // use osl API
3998 bool bResult = false;
4000 ::osl::DirectoryItem aDirItem;
4001 if ( ::osl::DirectoryItem::get( aURL, aDirItem ) == ::osl::FileBase::E_None )
4003 ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Attributes );
4004 if ( aDirItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None
4005 && aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) )
4007 sal_uInt64 nAttributes = aFileStatus.getAttributes();
4009 nAttributes &= ~(osl_File_Attribute_OwnWrite |
4010 osl_File_Attribute_GrpWrite |
4011 osl_File_Attribute_OthWrite |
4012 osl_File_Attribute_ReadOnly);
4013 nAttributes |= (osl_File_Attribute_OwnWrite |
4014 osl_File_Attribute_OwnRead);
4016 bResult = ( osl::File::setAttributes( aURL, nAttributes ) == ::osl::FileBase::E_None );
4020 return bResult;
4023 namespace
4025 /// Get the parent directory of a temporary file for output purposes.
4026 OUString GetLogicBase(const INetURLObject& rURL, std::unique_ptr<SfxMedium_Impl> const & pImpl)
4028 OUString aLogicBase;
4030 #if HAVE_FEATURE_MACOSX_SANDBOX
4031 // In a sandboxed environment we don't want to attempt to create temporary files in the same
4032 // directory where the user has selected an output file to be stored. The sandboxed process has
4033 // permission only to create the specifically named output file in that directory.
4034 (void) rURL;
4035 (void) pImpl;
4036 #else
4038 if (!pImpl->m_bHasEmbeddedObjects // Embedded objects would mean a special base, ignore that.
4039 && rURL.GetProtocol() == INetProtocol::File && !pImpl->m_pInStream)
4041 // Try to create the temp file in the same directory when storing.
4042 INetURLObject aURL(rURL);
4043 aURL.removeSegment();
4044 aLogicBase = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset);
4047 #endif // !HAVE_FEATURE_MACOSX_SANDBOX
4049 return aLogicBase;
4053 void SfxMedium::CreateTempFile( bool bReplace )
4055 if ( pImpl->pTempFile )
4057 if ( !bReplace )
4058 return;
4060 pImpl->pTempFile.reset();
4061 pImpl->m_aName.clear();
4064 OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
4065 pImpl->pTempFile.reset(new MediumTempFile(&aLogicBase));
4066 pImpl->pTempFile->EnableKillingFile();
4067 pImpl->m_aName = pImpl->pTempFile->GetFileName();
4068 OUString aTmpURL = pImpl->pTempFile->GetURL();
4069 if ( pImpl->m_aName.isEmpty() || aTmpURL.isEmpty() )
4071 SetError(ERRCODE_IO_CANTWRITE);
4072 return;
4075 if ( !(pImpl->m_nStorOpenMode & StreamMode::TRUNC) )
4077 bool bTransferSuccess = false;
4079 if ( GetContent().is()
4080 && GetURLObject().GetProtocol() == INetProtocol::File
4081 && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
4083 // if there is already such a document, we should copy it
4084 // if it is a file system use OS copy process
4087 uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
4088 INetURLObject aTmpURLObj( aTmpURL );
4089 OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT,
4090 true,
4091 INetURLObject::DecodeMechanism::WithCharset );
4092 if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() )
4094 ::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4095 OUString sMimeType = pImpl->getFilterMimeType();
4096 aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType );
4097 SetWritableForUserOnly( aTmpURL );
4098 bTransferSuccess = true;
4101 catch( const uno::Exception& )
4104 if ( bTransferSuccess )
4106 CloseOutStream();
4107 CloseInStream();
4111 if ( !bTransferSuccess && pImpl->m_pInStream )
4113 // the case when there is no URL-access available or this is a remote protocol
4114 // but there is an input stream
4115 GetOutStream();
4116 if ( pImpl->m_pOutStream )
4118 std::unique_ptr<char[]> pBuf(new char [8192]);
4119 ErrCode nErr = ERRCODE_NONE;
4121 pImpl->m_pInStream->Seek(0);
4122 pImpl->m_pOutStream->Seek(0);
4124 while( !pImpl->m_pInStream->eof() && nErr == ERRCODE_NONE )
4126 sal_uInt32 nRead = pImpl->m_pInStream->ReadBytes(pBuf.get(), 8192);
4127 nErr = pImpl->m_pInStream->GetError();
4128 pImpl->m_pOutStream->WriteBytes( pBuf.get(), nRead );
4131 bTransferSuccess = true;
4132 CloseInStream();
4134 CloseOutStream_Impl();
4136 else
4138 // Quite strange design, but currently it is expected that in this case no transfer happens
4139 // TODO/LATER: get rid of this inconsistent part of the call design
4140 bTransferSuccess = true;
4141 CloseInStream();
4144 if ( !bTransferSuccess )
4146 SetError(ERRCODE_IO_CANTWRITE);
4147 return;
4151 CloseStorage();
4155 void SfxMedium::CreateTempFileNoCopy()
4157 // this call always replaces the existing temporary file
4158 pImpl->pTempFile.reset();
4160 OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
4161 pImpl->pTempFile.reset(new MediumTempFile(&aLogicBase));
4162 pImpl->pTempFile->EnableKillingFile();
4163 pImpl->m_aName = pImpl->pTempFile->GetFileName();
4164 if ( pImpl->m_aName.isEmpty() )
4166 SetError(ERRCODE_IO_CANTWRITE);
4167 return;
4170 CloseOutStream_Impl();
4171 CloseStorage();
4174 bool SfxMedium::SignDocumentContentUsingCertificate(
4175 const css::uno::Reference<css::frame::XModel>& xModel, bool bHasValidDocumentSignature,
4176 const Reference<XCertificate>& xCertificate)
4178 bool bChanges = false;
4180 if (IsOpen() || GetErrorIgnoreWarning())
4182 SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
4183 return bChanges;
4186 // The component should know if there was a valid document signature, since
4187 // it should show a warning in this case
4188 OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
4189 uno::Reference< security::XDocumentDigitalSignatures > xSigner(
4190 security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
4191 comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
4192 auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get());
4193 if (!xModelSigner)
4195 return bChanges;
4198 uno::Reference< embed::XStorage > xWriteableZipStor;
4200 // we can reuse the temporary file if there is one already
4201 CreateTempFile( false );
4202 GetMedium_Impl();
4206 if ( !pImpl->xStream.is() )
4207 throw uno::RuntimeException();
4209 bool bODF = GetFilter()->IsOwnFormat();
4212 xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
4214 catch (const io::IOException&)
4216 if (bODF)
4218 TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
4222 if ( !xWriteableZipStor.is() && bODF )
4223 throw uno::RuntimeException();
4225 uno::Reference< embed::XStorage > xMetaInf;
4226 if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF"))
4228 xMetaInf = xWriteableZipStor->openStorageElement(
4229 "META-INF",
4230 embed::ElementModes::READWRITE );
4231 if ( !xMetaInf.is() )
4232 throw uno::RuntimeException();
4236 if (xMetaInf.is())
4238 // ODF.
4239 uno::Reference< io::XStream > xStream;
4240 if (GetFilter() && GetFilter()->IsOwnFormat())
4241 xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
4243 bool bSuccess = xModelSigner->SignModelWithCertificate(
4244 xModel, xCertificate, GetZipStorageToSign_Impl(), xStream);
4246 if (bSuccess)
4248 uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4249 xTransact->commit();
4250 xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4251 xTransact->commit();
4253 // the temporary file has been written, commit it to the original file
4254 Commit();
4255 bChanges = true;
4258 else if (xWriteableZipStor.is())
4260 // OOXML.
4261 uno::Reference<io::XStream> xStream;
4263 // We need read-write to be able to add the signature relation.
4264 bool bSuccess = xModelSigner->SignModelWithCertificate(
4265 xModel, xCertificate, GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
4267 if (bSuccess)
4269 uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
4270 xTransact->commit();
4272 // the temporary file has been written, commit it to the original file
4273 Commit();
4274 bChanges = true;
4277 else
4279 // Something not ZIP based: e.g. PDF.
4280 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
4281 uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
4282 if (xModelSigner->SignModelWithCertificate(
4283 xModel, xCertificate, uno::Reference<embed::XStorage>(), xStream))
4284 bChanges = true;
4288 catch ( const uno::Exception& )
4290 TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
4293 CloseAndRelease();
4295 ResetError();
4297 return bChanges;
4300 // note: this is the only function creating scripting signature
4301 bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
4302 bool bSignScriptingContent,
4303 bool bHasValidDocumentSignature,
4304 const OUString& aSignatureLineId,
4305 const Reference<XCertificate>& xCert,
4306 const Reference<XGraphic>& xValidGraphic,
4307 const Reference<XGraphic>& xInvalidGraphic,
4308 const OUString& aComment)
4310 bool bChanges = false;
4312 if (IsOpen() || GetErrorIgnoreWarning())
4314 SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
4315 return bChanges;
4318 // The component should know if there was a valid document signature, since
4319 // it should show a warning in this case
4320 OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
4321 uno::Reference< security::XDocumentDigitalSignatures > xSigner(
4322 security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
4323 comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
4324 if (pDialogParent)
4325 xSigner->setParentWindow(pDialogParent->GetXWindow());
4327 uno::Reference< embed::XStorage > xWriteableZipStor;
4329 // we can reuse the temporary file if there is one already
4330 CreateTempFile( false );
4331 GetMedium_Impl();
4335 if ( !pImpl->xStream.is() )
4336 throw uno::RuntimeException();
4338 bool bODF = GetFilter()->IsOwnFormat();
4341 if (pImpl->m_bODFWholesomeEncryption && bSignScriptingContent)
4343 assert(pImpl->xStorage); // GetStorage was called above
4344 assert(pImpl->m_xODFDecryptedInnerPackageStream);
4345 xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
4346 ZIP_STORAGE_FORMAT_STRING, pImpl->m_xODFDecryptedInnerPackageStream);
4348 else
4350 xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
4351 ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
4354 catch (const io::IOException&)
4356 if (bODF)
4358 TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
4362 if ( !xWriteableZipStor.is() && bODF )
4363 throw uno::RuntimeException();
4365 uno::Reference< embed::XStorage > xMetaInf;
4366 if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF"))
4368 xMetaInf = xWriteableZipStor->openStorageElement(
4369 "META-INF",
4370 embed::ElementModes::READWRITE );
4371 if ( !xMetaInf.is() )
4372 throw uno::RuntimeException();
4375 if ( bSignScriptingContent )
4377 // If the signature has already the document signature it will be removed
4378 // after the scripting signature is inserted.
4379 uno::Reference< io::XStream > xStream(
4380 xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(),
4381 embed::ElementModes::READWRITE ),
4382 uno::UNO_SET_THROW );
4384 // note: the storage passed here must be independent from the
4385 // xWriteableZipStor because a writable storage can't have 2
4386 // instances of sub-storage for the same directory open, but with
4387 // independent storages it somehow works
4388 if (xSigner->signScriptingContent(GetScriptingStorageToSign_Impl(), xStream))
4390 // remove the document signature if any
4391 OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
4392 if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) )
4393 xMetaInf->removeElement( aDocSigName );
4395 uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4396 xTransact->commit();
4397 xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4398 xTransact->commit();
4400 if (pImpl->m_bODFWholesomeEncryption)
4401 { // manually copy the inner package to the outer one
4402 uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
4403 uno::Reference<io::XStream> const xEncryptedPackage =
4404 pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
4405 "encrypted-package",
4406 embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
4407 comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
4408 xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
4409 xTransact->commit(); // Commit() below won't do this
4412 assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
4413 || !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified());
4414 // the temporary file has been written, commit it to the original file
4415 Commit();
4416 bChanges = true;
4419 else
4421 if (xMetaInf.is())
4423 // ODF.
4424 uno::Reference< io::XStream > xStream;
4425 if (GetFilter() && GetFilter()->IsOwnFormat())
4426 xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
4428 bool bSuccess = false;
4429 if (xCert.is())
4430 bSuccess = xSigner->signSignatureLine(
4431 GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
4432 xValidGraphic, xInvalidGraphic, aComment);
4433 else
4434 bSuccess = xSigner->signDocumentContent(GetZipStorageToSign_Impl(),
4435 xStream);
4437 if (bSuccess)
4439 uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4440 xTransact->commit();
4441 xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4442 xTransact->commit();
4444 // the temporary file has been written, commit it to the original file
4445 Commit();
4446 bChanges = true;
4449 else if (xWriteableZipStor.is())
4451 // OOXML.
4452 uno::Reference<io::XStream> xStream;
4454 bool bSuccess = false;
4455 if (xCert.is())
4457 bSuccess = xSigner->signSignatureLine(
4458 GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId,
4459 xCert, xValidGraphic, xInvalidGraphic, aComment);
4461 else
4463 // We need read-write to be able to add the signature relation.
4464 bSuccess =xSigner->signDocumentContent(
4465 GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
4468 if (bSuccess)
4470 uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
4471 xTransact->commit();
4473 // the temporary file has been written, commit it to the original file
4474 Commit();
4475 bChanges = true;
4478 else
4480 // Something not ZIP based: e.g. PDF.
4481 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
4482 uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
4483 if (xSigner->signDocumentContent(uno::Reference<embed::XStorage>(), xStream))
4484 bChanges = true;
4488 catch ( const uno::Exception& )
4490 TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
4493 CloseAndRelease();
4495 ResetError();
4497 return bChanges;
4501 SignatureState SfxMedium::GetCachedSignatureState_Impl() const
4503 return pImpl->m_nSignatureState;
4507 void SfxMedium::SetCachedSignatureState_Impl( SignatureState nState )
4509 pImpl->m_nSignatureState = nState;
4512 void SfxMedium::SetHasEmbeddedObjects(bool bHasEmbeddedObjects)
4514 pImpl->m_bHasEmbeddedObjects = bHasEmbeddedObjects;
4517 bool SfxMedium::HasStorage_Impl() const
4519 return pImpl->xStorage.is();
4522 bool SfxMedium::IsOpen() const
4524 return pImpl->m_pInStream || pImpl->m_pOutStream || pImpl->xStorage.is();
4527 OUString SfxMedium::CreateTempCopyWithExt( std::u16string_view aURL )
4529 OUString aResult;
4531 if ( !aURL.empty() )
4533 size_t nPrefixLen = aURL.rfind( '.' );
4534 std::u16string_view aExt = ( nPrefixLen == std::u16string_view::npos ) ? std::u16string_view() : aURL.substr( nPrefixLen );
4536 OUString aNewTempFileURL = ::utl::CreateTempURL( u"", true, aExt );
4537 if ( !aNewTempFileURL.isEmpty() )
4539 INetURLObject aSource( aURL );
4540 INetURLObject aDest( aNewTempFileURL );
4541 OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT,
4542 true,
4543 INetURLObject::DecodeMechanism::WithCharset );
4544 if ( !aFileName.isEmpty() && aDest.removeSegment() )
4548 uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
4549 ::ucbhelper::Content aTargetContent( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4550 ::ucbhelper::Content aSourceContent( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4551 aTargetContent.transferContent( aSourceContent,
4552 ::ucbhelper::InsertOperation::Copy,
4553 aFileName,
4554 NameClash::OVERWRITE );
4555 aResult = aNewTempFileURL;
4557 catch( const uno::Exception& )
4563 return aResult;
4566 bool SfxMedium::CallApproveHandler(const uno::Reference< task::XInteractionHandler >& xHandler, const uno::Any& rRequest, bool bAllowAbort)
4568 bool bResult = false;
4570 if ( xHandler.is() )
4574 uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( bAllowAbort ? 2 : 1 );
4575 auto pContinuations = aContinuations.getArray();
4577 ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove );
4578 pContinuations[ 0 ] = pApprove.get();
4580 if ( bAllowAbort )
4582 ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort( new ::comphelper::OInteractionAbort );
4583 pContinuations[ 1 ] = pAbort.get();
4586 xHandler->handle(::framework::InteractionRequest::CreateRequest(rRequest, aContinuations));
4587 bResult = pApprove->wasSelected();
4589 catch( const Exception& )
4594 return bResult;
4597 OUString SfxMedium::SwitchDocumentToTempFile()
4599 // the method returns empty string in case of failure
4600 OUString aResult;
4601 OUString aOrigURL = pImpl->m_aLogicName;
4603 if ( !aOrigURL.isEmpty() )
4605 sal_Int32 nPrefixLen = aOrigURL.lastIndexOf( '.' );
4606 std::u16string_view aExt = (nPrefixLen == -1)
4607 ? std::u16string_view()
4608 : aOrigURL.subView(nPrefixLen);
4609 OUString aNewURL = ::utl::CreateTempURL( u"", true, aExt );
4611 // TODO/LATER: In future the aLogicName should be set to shared folder URL
4612 // and a temporary file should be created. Transport_Impl should be impossible then.
4613 if ( !aNewURL.isEmpty() )
4615 uno::Reference< embed::XStorage > xStorage = GetStorage();
4616 uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
4618 if ( xOptStorage.is() )
4620 // TODO/LATER: reuse the pImpl->pTempFile if it already exists
4621 CanDisposeStorage_Impl( false );
4622 Close();
4623 SetPhysicalName_Impl( OUString() );
4624 SetName( aNewURL );
4626 // remove the readonly state
4627 bool bWasReadonly = false;
4628 pImpl->m_nStorOpenMode = SFX_STREAM_READWRITE;
4629 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
4630 if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
4631 bWasReadonly = true;
4632 GetItemSet().ClearItem( SID_DOC_READONLY );
4634 GetMedium_Impl();
4635 LockOrigFileOnDemand( false, false );
4636 CreateTempFile();
4637 GetMedium_Impl();
4639 if ( pImpl->xStream.is() )
4643 xOptStorage->writeAndAttachToStream( pImpl->xStream );
4644 pImpl->xStorage = xStorage;
4645 aResult = aNewURL;
4647 catch( const uno::Exception& )
4651 if (bWasReadonly)
4653 // set the readonly state back
4654 pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
4655 GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
4658 if ( aResult.isEmpty() )
4660 Close();
4661 SetPhysicalName_Impl( OUString() );
4662 SetName( aOrigURL );
4663 GetMedium_Impl();
4664 pImpl->xStorage = xStorage;
4670 return aResult;
4673 bool SfxMedium::SwitchDocumentToFile( const OUString& aURL )
4675 // the method is only for storage based documents
4676 bool bResult = false;
4677 OUString aOrigURL = pImpl->m_aLogicName;
4679 if ( !aURL.isEmpty() && !aOrigURL.isEmpty() )
4681 uno::Reference< embed::XStorage > xStorage = GetStorage();
4682 uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
4684 // TODO/LATER: reuse the pImpl->pTempFile if it already exists
4685 CanDisposeStorage_Impl( false );
4686 Close();
4687 SetPhysicalName_Impl( OUString() );
4688 SetName( aURL );
4690 // open the temporary file based document
4691 GetMedium_Impl();
4692 LockOrigFileOnDemand( false, false );
4693 CreateTempFile();
4694 GetMedium_Impl();
4696 if ( pImpl->xStream.is() )
4700 uno::Reference< io::XTruncate > xTruncate( pImpl->xStream, uno::UNO_QUERY_THROW );
4701 xTruncate->truncate();
4702 if ( xOptStorage.is() )
4703 xOptStorage->writeAndAttachToStream( pImpl->xStream );
4704 pImpl->xStorage = xStorage;
4705 bResult = true;
4707 catch( const uno::Exception& )
4711 if ( !bResult )
4713 Close();
4714 SetPhysicalName_Impl( OUString() );
4715 SetName( aOrigURL );
4716 GetMedium_Impl();
4717 pImpl->xStorage = xStorage;
4721 return bResult;
4724 void SfxMedium::SetInCheckIn( bool bInCheckIn )
4726 pImpl->m_bInCheckIn = bInCheckIn;
4729 bool SfxMedium::IsInCheckIn( ) const
4731 return pImpl->m_bInCheckIn;
4734 // should only be called on main thread
4735 const std::shared_ptr<std::recursive_mutex>& SfxMedium::GetCheckEditableMutex() const
4737 return pImpl->m_pCheckEditableWorkerMutex;
4740 // should only be called while holding pImpl->m_pCheckEditableWorkerMutex
4741 void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent)
4743 pImpl->m_pReloadEvent = pEvent;
4746 // should only be called while holding pImpl->m_pCheckEditableWorkerMutex
4747 ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const
4749 return pImpl->m_pReloadEvent;
4752 // should only be called on main thread
4753 void SfxMedium::AddToCheckEditableWorkerList()
4755 if (!pImpl->m_bNotifyWhenEditable)
4756 return;
4758 CancelCheckEditableEntry();
4760 if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
4762 pImpl->m_pCheckEditableWorkerMutex = std::make_shared<std::recursive_mutex>();
4763 if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
4764 return;
4767 pImpl->m_pIsDestructed = std::make_shared<bool>(false);
4768 if (pImpl->m_pIsDestructed == nullptr)
4769 return;
4771 std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
4772 if (g_newReadOnlyDocs.find(this) == g_newReadOnlyDocs.end())
4774 bool bAddNewEntry = false;
4775 if (!g_bChkReadOnlyTaskRunning)
4777 std::shared_ptr<comphelper::ThreadTaskTag> pTag
4778 = comphelper::ThreadPool::createThreadTaskTag();
4779 if (pTag != nullptr)
4781 g_bChkReadOnlyTaskRunning = true;
4782 bAddNewEntry = true;
4783 comphelper::ThreadPool::getSharedOptimalPool().pushTask(
4784 std::make_unique<CheckReadOnlyTask>(pTag));
4787 else
4788 bAddNewEntry = true;
4790 if (bAddNewEntry)
4792 std::shared_ptr<ReadOnlyMediumEntry> newEntry = std::make_shared<ReadOnlyMediumEntry>(
4793 pImpl->m_pCheckEditableWorkerMutex, pImpl->m_pIsDestructed);
4795 if (newEntry != nullptr)
4797 g_newReadOnlyDocs[this] = newEntry;
4803 // should only be called on main thread
4804 void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent)
4806 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
4808 std::unique_lock<std::recursive_mutex> lock(*(pImpl->m_pCheckEditableWorkerMutex));
4810 if (pImpl->m_pReloadEvent != nullptr)
4812 if (bRemoveEvent)
4813 Application::RemoveUserEvent(pImpl->m_pReloadEvent);
4814 // make sure destructor doesn't use a freed reference
4815 // and reset the event so we can check again
4816 pImpl->m_pReloadEvent = nullptr;
4819 if (pImpl->m_pIsDestructed != nullptr)
4821 *(pImpl->m_pIsDestructed) = true;
4822 pImpl->m_pIsDestructed = nullptr;
4827 /** callback function, which is triggered by worker thread after successfully checking if the file
4828 is editable. Sent from <Application::PostUserEvent(..)>
4829 Note: This method has to be run in the main thread.
4831 IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void)
4833 SfxMedium* pMed = static_cast<SfxMedium*>(p);
4834 if (pMed == nullptr)
4835 return;
4837 pMed->CancelCheckEditableEntry(false);
4839 uno::Reference<task::XInteractionHandler> xHandler = pMed->GetInteractionHandler();
4840 if (xHandler.is())
4842 OUString aDocumentURL
4843 = pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
4844 ::rtl::Reference<::ucbhelper::InteractionRequest> xInteractionRequestImpl
4845 = new ::ucbhelper::InteractionRequest(uno::Any(document::ReloadEditableRequest(
4846 OUString(), uno::Reference<uno::XInterface>(), aDocumentURL)));
4847 if (xInteractionRequestImpl != nullptr)
4849 uno::Sequence<uno::Reference<task::XInteractionContinuation>> aContinuations{
4850 new ::ucbhelper::InteractionAbort(xInteractionRequestImpl.get()),
4851 new ::ucbhelper::InteractionApprove(xInteractionRequestImpl.get())
4853 xInteractionRequestImpl->setContinuations(aContinuations);
4854 xHandler->handle(xInteractionRequestImpl);
4855 ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
4856 = xInteractionRequestImpl->getSelection();
4857 if (uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
4859 for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame;
4860 pFrame = SfxViewFrame::GetNext(*pFrame))
4862 if (pFrame->GetObjectShell()->GetMedium() == pMed)
4864 // special case to ensure view isn't set to read-only in
4865 // SfxViewFrame::ExecReload_Impl after reloading
4866 pMed->SetOriginallyReadOnly(false);
4867 pFrame->GetDispatcher()->Execute(SID_RELOAD);
4868 break;
4876 bool SfxMedium::CheckCanGetLockfile() const
4878 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
4879 bool bCanReload = true;
4880 #else
4881 bool bCanReload = false;
4882 ::svt::DocumentLockFile aLockFile(GetName());
4883 LockFileEntry aData;
4884 osl::DirectoryItem rItem;
4885 auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem);
4886 if (nError1 == osl::FileBase::E_None)
4890 aData = aLockFile.GetLockData();
4892 catch (const io::WrongFormatException&)
4894 // we get empty or corrupt data
4895 return false;
4897 catch (const uno::Exception&)
4899 // locked from other app
4900 return false;
4902 LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
4903 bool bOwnLock
4904 = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
4905 if (bOwnLock
4906 && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
4907 && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
4909 // this is own lock from the same installation, it could remain because of crash
4910 bCanReload = true;
4913 else if (nError1 == osl::FileBase::E_NOENT) // file doesn't exist
4917 aLockFile.CreateOwnLockFile();
4920 // TODO/LATER: A warning could be shown in case the file is not the own one
4921 aLockFile.RemoveFile();
4923 catch (const io::WrongFormatException&)
4927 // erase the empty or corrupt file
4928 aLockFile.RemoveFileDirectly();
4930 catch (const uno::Exception&)
4934 bCanReload = true;
4936 catch (const uno::Exception&)
4940 #endif
4941 return bCanReload;
4944 // worker thread method, should only be one thread globally
4945 void CheckReadOnlyTask::doWork()
4947 if (m_xListener == nullptr)
4948 return;
4950 while (true)
4952 std::unique_lock<std::mutex> termLock(m_xListener->mMutex);
4953 if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60),
4954 [this] { return m_xListener->bIsTerminated; }))
4955 // signalled, spurious wakeups should not be possible
4956 return;
4958 // must have timed-out
4959 termLock.unlock();
4960 std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
4961 for (auto it = g_newReadOnlyDocs.begin(); it != g_newReadOnlyDocs.end(); )
4963 auto [pMed, roEntry] = *it;
4964 g_existingReadOnlyDocs[pMed] = roEntry;
4965 it = g_newReadOnlyDocs.erase(it);
4967 if (g_existingReadOnlyDocs.size() == 0)
4969 g_bChkReadOnlyTaskRunning = false;
4970 return;
4972 globalLock.unlock();
4974 auto checkForErase = [](SfxMedium* pMed, const std::shared_ptr<ReadOnlyMediumEntry>& roEntry) -> bool
4976 if (pMed == nullptr || roEntry == nullptr || roEntry->_pMutex == nullptr
4977 || roEntry->_pIsDestructed == nullptr)
4978 return true;
4980 std::unique_lock<std::recursive_mutex> medLock(*(roEntry->_pMutex));
4981 if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != nullptr)
4982 return true;
4984 osl::File aFile(
4985 pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
4986 if (aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None)
4987 return false;
4989 if (!pMed->CheckCanGetLockfile())
4990 return false;
4992 if (aFile.close() != osl::FileBase::E_None)
4993 return true;
4995 // we can load, ask user
4996 ImplSVEvent* pEvent = Application::PostUserEvent(
4997 LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed);
4998 pMed->SetWorkerReloadEvent(pEvent);
4999 return true;
5002 for (auto it = g_existingReadOnlyDocs.begin(); it != g_existingReadOnlyDocs.end(); )
5004 if (checkForErase(it->first, it->second))
5005 it = g_existingReadOnlyDocs.erase(it);
5006 else
5007 ++it;
5012 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */