xref: /core/sfx2/source/doc/docfile.cxx (revision ca5c9591)
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.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <config_features.h>
21 
22 #ifdef UNX
23 #include <sys/stat.h>
24 #endif
25 
26 #include <sfx2/docfile.hxx>
27 #include <sfx2/signaturestate.hxx>
28 
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/fileurl.hxx>
80 #include <comphelper/processfactory.hxx>
81 #include <comphelper/propertyvalue.hxx>
82 #include <comphelper/interaction.hxx>
83 #include <comphelper/sequence.hxx>
84 #include <comphelper/simplefileaccessinteraction.hxx>
85 #include <comphelper/string.hxx>
86 #include <framework/interaction.hxx>
87 #include <utility>
88 #include <svl/stritem.hxx>
89 #include <svl/eitem.hxx>
90 #include <svtools/sfxecode.hxx>
91 #include <svl/itemset.hxx>
92 #include <svl/intitem.hxx>
93 #include <svtools/svparser.hxx>
94 #include <sal/log.hxx>
95 
96 #include <unotools/streamwrap.hxx>
97 
98 #include <osl/file.hxx>
99 
100 #include <comphelper/storagehelper.hxx>
101 #include <unotools/mediadescriptor.hxx>
102 #include <comphelper/docpasswordhelper.hxx>
103 #include <tools/datetime.hxx>
104 #include <unotools/pathoptions.hxx>
105 #include <svtools/asynclink.hxx>
106 #include <ucbhelper/commandenvironment.hxx>
107 #include <unotools/ucbstreamhelper.hxx>
108 #include <unotools/ucbhelper.hxx>
109 #include <unotools/progresshandlerwrap.hxx>
110 #include <ucbhelper/content.hxx>
111 #include <ucbhelper/interactionrequest.hxx>
112 #include <sot/storage.hxx>
113 #include <svl/documentlockfile.hxx>
114 #include <svl/msodocumentlockfile.hxx>
115 #include <com/sun/star/document/DocumentRevisionListPersistence.hpp>
116 
117 #include <sfx2/app.hxx>
118 #include <sfx2/frame.hxx>
119 #include <sfx2/dispatch.hxx>
120 #include <sfx2/fcontnr.hxx>
121 #include <sfx2/docfilt.hxx>
122 #include <sfx2/sfxsids.hrc>
123 #include <sfx2/sfxuno.hxx>
124 #include <openflag.hxx>
125 #include <officecfg/Office/Common.hxx>
126 #include <comphelper/propertysequence.hxx>
127 #include <vcl/weld.hxx>
128 #include <vcl/svapp.hxx>
129 #include <comphelper/diagnose_ex.hxx>
130 #include <sfx2/digitalsignatures.hxx>
131 #include <sfx2/viewfrm.hxx>
132 #include <comphelper/threadpool.hxx>
133 #include <o3tl/string_view.hxx>
134 #include <condition_variable>
135 
136 #include <com/sun/star/io/WrongFormatException.hpp>
137 
138 #include <memory>
139 
140 using namespace ::com::sun::star;
141 using namespace ::com::sun::star::graphic;
142 using namespace ::com::sun::star::uno;
143 using namespace ::com::sun::star::ucb;
144 using namespace ::com::sun::star::beans;
145 using namespace ::com::sun::star::io;
146 using namespace ::com::sun::star::security;
147 
148 namespace
149 {
150 
151 struct ReadOnlyMediumEntry
152 {
ReadOnlyMediumEntry__anon6928f1030111::ReadOnlyMediumEntry153     ReadOnlyMediumEntry(std::shared_ptr<std::recursive_mutex> pMutex,
154                         std::shared_ptr<bool> pIsDestructed)
155         : _pMutex(std::move(pMutex))
156         , _pIsDestructed(std::move(pIsDestructed))
157     {
158     }
159     std::shared_ptr<std::recursive_mutex> _pMutex;
160     std::shared_ptr<bool> _pIsDestructed;
161 };
162 
163 }
164 
165 static std::mutex g_chkReadOnlyGlobalMutex;
166 static bool g_bChkReadOnlyTaskRunning = false;
167 static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_newReadOnlyDocs;
168 static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_existingReadOnlyDocs;
169 
170 namespace {
171 
172 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
173 
IsSystemFileLockingUsed()174 bool IsSystemFileLockingUsed()
175 {
176 #if HAVE_FEATURE_MACOSX_SANDBOX
177     return true;
178 #else
179     return officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get();
180 #endif
181 }
182 
183 
IsOOoLockFileUsed()184 bool IsOOoLockFileUsed()
185 {
186 #if HAVE_FEATURE_MACOSX_SANDBOX
187     return false;
188 #else
189     return officecfg::Office::Common::Misc::UseDocumentOOoLockFile::get();
190 #endif
191 }
192 
IsLockingUsed()193 bool IsLockingUsed()
194 {
195     return officecfg::Office::Common::Misc::UseLocking::get();
196 }
197 
198 #endif
199 
200 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
IsWebDAVLockingUsed()201 bool IsWebDAVLockingUsed()
202 {
203     return officecfg::Office::Common::Misc::UseWebDAVFileLocking::get();
204 }
205 #endif
206 
207 /// Gets default attributes of a file:// URL.
GetDefaultFileAttributes(const OUString & rURL)208 sal_uInt64 GetDefaultFileAttributes(const OUString& rURL)
209 {
210     sal_uInt64 nRet = 0;
211 
212     if (!comphelper::isFileUrl(rURL))
213         return nRet;
214 
215     // Make sure the file exists (and create it if not).
216     osl::File aFile(rURL);
217     osl::File::RC nRes = aFile.open(osl_File_OpenFlag_Create);
218     if (nRes != osl::File::E_None && nRes != osl::File::E_EXIST)
219         return nRet;
220 
221     aFile.close();
222 
223     osl::DirectoryItem aItem;
224     if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None)
225         return nRet;
226 
227     osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
228     if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None)
229         return nRet;
230 
231     nRet = aStatus.getAttributes();
232     return nRet;
233 }
234 
235 /// Determines if rURL is safe to move or not.
IsFileMovable(const INetURLObject & rURL)236 bool IsFileMovable(const INetURLObject& rURL)
237 {
238 #ifdef MACOSX
239     (void)rURL;
240     // Hide extension macOS-specific file property would be lost.
241     return false;
242 #else
243 
244     if (rURL.GetProtocol() != INetProtocol::File)
245         // Not a file:// URL.
246         return false;
247 
248 #ifdef UNX
249     OUString sPath = rURL.getFSysPath(FSysStyle::Unix);
250     if (sPath.isEmpty())
251         return false;
252 
253     struct stat buf;
254     if (lstat(sPath.toUtf8().getStr(), &buf) != 0)
255         return false;
256 
257     // Hardlink or symlink: osl::File::move() doesn't play with these nicely.
258     if (buf.st_nlink > 1 || S_ISLNK(buf.st_mode))
259         return false;
260 #elif defined _WIN32
261     if (tools::IsMappedWebDAVPath(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE)))
262         return false;
263 #endif
264 
265     return true;
266 #endif
267 }
268 
269 class CheckReadOnlyTaskTerminateListener
270     : public ::cppu::WeakImplHelper<css::frame::XTerminateListener>
271 {
272 public:
273     // XEventListener
274     void SAL_CALL disposing(const css::lang::EventObject& Source) override;
275 
276     // XTerminateListener
277     void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) override;
278     void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) override;
279 
280     bool bIsTerminated = false;
281     std::condition_variable mCond;
282     std::mutex mMutex;
283 };
284 
285 class CheckReadOnlyTask : public comphelper::ThreadTask
286 {
287 public:
288     CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag);
289     ~CheckReadOnlyTask();
290 
291     virtual void doWork() override;
292 
293 private:
294     rtl::Reference<CheckReadOnlyTaskTerminateListener> m_xListener;
295 };
296 
297 } // anonymous namespace
298 
CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag> & pTag)299 CheckReadOnlyTask::CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag)
300     : ThreadTask(pTag)
301     , m_xListener(new CheckReadOnlyTaskTerminateListener)
302 {
303     Reference<css::frame::XDesktop> xDesktop
304         = css::frame::Desktop::create(comphelper::getProcessComponentContext());
305     if (xDesktop.is() && m_xListener != nullptr)
306     {
307         xDesktop->addTerminateListener(m_xListener);
308     }
309 }
310 
~CheckReadOnlyTask()311 CheckReadOnlyTask::~CheckReadOnlyTask()
312 {
313     Reference<css::frame::XDesktop> xDesktop
314         = css::frame::Desktop::create(comphelper::getProcessComponentContext());
315     if (xDesktop.is() && m_xListener != nullptr)
316     {
317         std::unique_lock<std::mutex> lock(m_xListener->mMutex);
318         if (!m_xListener->bIsTerminated)
319         {
320             lock.unlock();
321             xDesktop->removeTerminateListener(m_xListener);
322         }
323     }
324 }
325 
326 namespace
327 {
328 void SAL_CALL
disposing(const css::lang::EventObject &)329 CheckReadOnlyTaskTerminateListener::disposing(const css::lang::EventObject& /*Source*/)
330 {
331 }
332 
333 void SAL_CALL
queryTermination(const css::lang::EventObject &)334 CheckReadOnlyTaskTerminateListener::queryTermination(const css::lang::EventObject& /*aEvent*/)
335 {
336 }
337 
338 void SAL_CALL
notifyTermination(const css::lang::EventObject &)339 CheckReadOnlyTaskTerminateListener::notifyTermination(const css::lang::EventObject& /*aEvent*/)
340 {
341     std::unique_lock<std::mutex> lock(mMutex);
342     bIsTerminated = true;
343     lock.unlock();
344     mCond.notify_one();
345 }
346 }
347 
348 class SfxMedium_Impl
349 {
350 public:
351     StreamMode m_nStorOpenMode;
352     ErrCodeMsg m_eError;
353     ErrCodeMsg m_eWarningError;
354 
355     ::ucbhelper::Content aContent;
356     bool bUpdatePickList:1;
357     bool bIsTemp:1;
358     bool bDownloadDone:1;
359     bool bIsStorage:1;
360     bool bUseInteractionHandler:1;
361     bool bAllowDefaultIntHdl:1;
362     bool bDisposeStorage:1;
363     bool bStorageBasedOnInStream:1;
364     bool m_bSalvageMode:1;
365     bool m_bVersionsAlreadyLoaded:1;
366     bool m_bLocked:1;
367     bool m_bMSOLockFileCreated : 1;
368     bool m_bDisableUnlockWebDAV:1;
369     bool m_bGotDateTime:1;
370     bool m_bRemoveBackup:1;
371     bool m_bOriginallyReadOnly:1;
372     bool m_bOriginallyLoadedReadOnly:1;
373     bool m_bTriedStorage:1;
374     bool m_bRemote:1;
375     bool m_bInputStreamIsReadOnly:1;
376     bool m_bInCheckIn:1;
377     bool m_bDisableFileSync = false;
378     bool m_bNotifyWhenEditable = false;
379     /// if true, xStorage is an inner package and not directly from xStream
380     bool m_bODFWholesomeEncryption = false;
381 
382     OUString m_aName;
383     OUString m_aLogicName;
384     OUString m_aLongName;
385 
386     mutable std::shared_ptr<SfxItemSet> m_pSet;
387     mutable std::unique_ptr<INetURLObject> m_pURLObj;
388 
389     std::shared_ptr<const SfxFilter> m_pFilter;
390     std::shared_ptr<const SfxFilter> m_pCustomFilter;
391 
392     std::shared_ptr<std::recursive_mutex> m_pCheckEditableWorkerMutex;
393     std::shared_ptr<bool> m_pIsDestructed;
394     ImplSVEvent* m_pReloadEvent;
395 
396     std::unique_ptr<SvStream> m_pInStream;
397     std::unique_ptr<SvStream> m_pOutStream;
398 
399     OUString    aOrigURL;
400     DateTime         aExpireTime;
401     SfxFrameWeakRef  wLoadTargetFrame;
402     SvKeyValueIteratorRef xAttributes;
403 
404     svtools::AsynchronLink  aDoneLink;
405 
406     uno::Sequence < util::RevisionTag > aVersions;
407 
408     std::unique_ptr<::utl::TempFileNamed> pTempFile;
409 
410     uno::Reference<embed::XStorage> xStorage;
411     uno::Reference<embed::XStorage> m_xZipStorage;
412     uno::Reference<io::XInputStream> m_xInputStreamToLoadFrom;
413     uno::Reference<io::XInputStream> xInputStream;
414     uno::Reference<io::XStream> xStream;
415     uno::Reference<io::XStream> m_xLockingStream;
416     uno::Reference<task::XInteractionHandler> xInteraction;
417     uno::Reference<io::XStream> m_xODFDecryptedInnerPackageStream;
418     uno::Reference<embed::XStorage> m_xODFEncryptedOuterStorage;
419     uno::Reference<embed::XStorage> m_xODFDecryptedInnerZipStorage;
420 
421     ErrCodeMsg  nLastStorageError;
422 
423     OUString m_aBackupURL;
424 
425     // the following member is changed and makes sense only during saving
426     // TODO/LATER: in future the signature state should be controlled by the medium not by the document
427     //             in this case the member will hold this information
428     SignatureState             m_nSignatureState;
429 
430     bool m_bHasEmbeddedObjects = false;
431 
432     util::DateTime m_aDateTime;
433 
434     uno::Sequence<beans::PropertyValue> m_aArgs;
435 
436     explicit SfxMedium_Impl();
437     ~SfxMedium_Impl();
438     SfxMedium_Impl(const SfxMedium_Impl&) = delete;
439     SfxMedium_Impl& operator=(const SfxMedium_Impl&) = delete;
440 
getFilterMimeType() const441     OUString getFilterMimeType() const
442         { return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); }
443 };
444 
SfxMedium_Impl()445 SfxMedium_Impl::SfxMedium_Impl() :
446     m_nStorOpenMode(SFX_STREAM_READWRITE),
447     m_eError(ERRCODE_NONE),
448     m_eWarningError(ERRCODE_NONE),
449     bUpdatePickList(true),
450     bIsTemp( false ),
451     bDownloadDone( true ),
452     bIsStorage( false ),
453     bUseInteractionHandler( true ),
454     bAllowDefaultIntHdl( false ),
455     bDisposeStorage( false ),
456     bStorageBasedOnInStream( false ),
457     m_bSalvageMode( false ),
458     m_bVersionsAlreadyLoaded( false ),
459     m_bLocked( false ),
460     m_bMSOLockFileCreated( false ),
461     m_bDisableUnlockWebDAV( false ),
462     m_bGotDateTime( false ),
463     m_bRemoveBackup( false ),
464     m_bOriginallyReadOnly(false),
465     m_bOriginallyLoadedReadOnly(false),
466     m_bTriedStorage(false),
467     m_bRemote(false),
468     m_bInputStreamIsReadOnly(false),
469     m_bInCheckIn(false),
470     m_pReloadEvent(nullptr),
471     aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ),
472     nLastStorageError( ERRCODE_NONE ),
473     m_nSignatureState( SignatureState::NOSIGNATURES )
474 {
475 }
476 
477 
~SfxMedium_Impl()478 SfxMedium_Impl::~SfxMedium_Impl()
479 {
480     aDoneLink.ClearPendingCall();
481 
482     pTempFile.reset();
483     m_pSet.reset();
484     std::unique_lock<std::recursive_mutex> chkEditLock;
485     if (m_pCheckEditableWorkerMutex != nullptr)
486         chkEditLock = std::unique_lock<std::recursive_mutex>(*m_pCheckEditableWorkerMutex);
487     m_pURLObj.reset();
488 }
489 
ResetError()490 void SfxMedium::ResetError()
491 {
492     pImpl->m_eError = ERRCODE_NONE;
493     if( pImpl->m_pInStream )
494         pImpl->m_pInStream->ResetError();
495     if( pImpl->m_pOutStream )
496         pImpl->m_pOutStream->ResetError();
497 }
498 
GetWarningError() const499 ErrCodeMsg const & SfxMedium::GetWarningError() const
500 {
501     return pImpl->m_eWarningError;
502 }
503 
GetLastStorageCreationState() const504 ErrCodeMsg const & SfxMedium::GetLastStorageCreationState() const
505 {
506     return pImpl->nLastStorageError;
507 }
508 
SetError(ErrCodeMsg nError)509 void SfxMedium::SetError(ErrCodeMsg nError)
510 {
511     if (pImpl->m_eError == ERRCODE_NONE || (pImpl->m_eError.IsWarning() && nError.IsError()))
512         pImpl->m_eError = nError;
513 }
514 
SetWarningError(const ErrCodeMsg & nWarningError)515 void SfxMedium::SetWarningError(const ErrCodeMsg& nWarningError)
516 {
517     pImpl->m_eWarningError = nWarningError;
518 }
519 
GetErrorCode() const520 ErrCodeMsg SfxMedium::GetErrorCode() const
521 {
522     ErrCodeMsg lError = pImpl->m_eError;
523     if(!lError && pImpl->m_pInStream)
524         lError = pImpl->m_pInStream->GetErrorCode();
525     if(!lError && pImpl->m_pOutStream)
526         lError = pImpl->m_pOutStream->GetErrorCode();
527     return lError;
528 }
529 
CheckFileDate(const util::DateTime & aInitDate)530 void SfxMedium::CheckFileDate( const util::DateTime& aInitDate )
531 {
532     GetInitFileDate( true );
533     if ( pImpl->m_aDateTime.Seconds == aInitDate.Seconds
534       && pImpl->m_aDateTime.Minutes == aInitDate.Minutes
535       && pImpl->m_aDateTime.Hours == aInitDate.Hours
536       && pImpl->m_aDateTime.Day == aInitDate.Day
537       && pImpl->m_aDateTime.Month == aInitDate.Month
538       && pImpl->m_aDateTime.Year == aInitDate.Year )
539         return;
540 
541     uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
542 
543     if ( !xHandler.is() )
544         return;
545 
546     try
547     {
548         ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
549             document::ChangedByOthersRequest() ) );
550         uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
551             new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() ),
552             new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() )
553         };
554         xInteractionRequestImpl->setContinuations( aContinuations );
555 
556         xHandler->handle( xInteractionRequestImpl );
557 
558         ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
559         if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
560         {
561             SetError(ERRCODE_ABORT);
562         }
563     }
564     catch ( const uno::Exception& )
565     {}
566 }
567 
DocNeedsFileDateCheck() const568 bool SfxMedium::DocNeedsFileDateCheck() const
569 {
570     return ( !IsReadOnly() && ( GetURLObject().GetProtocol() == INetProtocol::File ||
571                                 GetURLObject().isAnyKnownWebDAVScheme() ) );
572 }
573 
GetInitFileDate(bool bIgnoreOldValue)574 util::DateTime const & SfxMedium::GetInitFileDate( bool bIgnoreOldValue )
575 {
576     if ( ( bIgnoreOldValue || !pImpl->m_bGotDateTime ) && !pImpl->m_aLogicName.isEmpty() )
577     {
578         try
579         {
580             // add a default css::ucb::XCommandEnvironment
581             // in order to have the WebDAV UCP provider manage http/https authentication correctly
582             ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
583                                            utl::UCBContentHelper::getDefaultCommandEnvironment(),
584                                            comphelper::getProcessComponentContext() );
585 
586             aContent.getPropertyValue(u"DateModified"_ustr) >>= pImpl->m_aDateTime;
587             pImpl->m_bGotDateTime = true;
588         }
589         catch ( const css::uno::Exception& )
590         {
591         }
592     }
593 
594     return pImpl->m_aDateTime;
595 }
596 
597 
GetContent() const598 Reference < XContent > SfxMedium::GetContent() const
599 {
600     if ( !pImpl->aContent.get().is() )
601     {
602         Reference < css::ucb::XContent > xContent;
603 
604         // tdf#95144 add a default css::ucb::XCommandEnvironment
605         // in order to have the WebDAV UCP provider manage https protocol certificates correctly
606         css:: uno::Reference< task::XInteractionHandler > xIH(
607                 css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) );
608 
609         css::uno::Reference< css::ucb::XProgressHandler > xProgress;
610         rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
611 
612         const SfxUnoAnyItem* pItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_CONTENT, false);
613         if ( pItem )
614             pItem->GetValue() >>= xContent;
615 
616         if ( xContent.is() )
617         {
618             try
619             {
620                 pImpl->aContent = ::ucbhelper::Content( xContent, pCommandEnv, comphelper::getProcessComponentContext() );
621             }
622             catch ( const Exception& )
623             {
624             }
625         }
626         else
627         {
628             // TODO: SAL_WARN( "sfx.doc", "SfxMedium::GetContent()\nCreate Content? This code exists as fallback only. Please clarify, why it's used.");
629             OUString aURL;
630             if ( !pImpl->m_aName.isEmpty() )
631                 osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL );
632             else if ( !pImpl->m_aLogicName.isEmpty() )
633                 aURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
634             if (!aURL.isEmpty() )
635                 (void)::ucbhelper::Content::create( aURL, pCommandEnv, comphelper::getProcessComponentContext(), pImpl->aContent );
636         }
637     }
638 
639     return pImpl->aContent.get();
640 }
641 
GetBaseURL(bool bForSaving)642 OUString SfxMedium::GetBaseURL( bool bForSaving )
643 {
644     if (bForSaving)
645     {
646         bool bIsRemote = IsRemote();
647         if ((bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get())
648             || (!bIsRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()))
649             return OUString();
650     }
651 
652     if (const SfxStringItem* pBaseURLItem = GetItemSet().GetItem<SfxStringItem>(SID_DOC_BASEURL))
653         return pBaseURLItem->GetValue();
654 
655     OUString aBaseURL;
656     if (!comphelper::IsFuzzing() && GetContent().is())
657     {
658         try
659         {
660             Any aAny = pImpl->aContent.getPropertyValue(u"BaseURI"_ustr);
661             aAny >>= aBaseURL;
662         }
663         catch ( const css::uno::Exception& )
664         {
665         }
666 
667         if ( aBaseURL.isEmpty() )
668             aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
669     }
670     return aBaseURL;
671 }
672 
IsSkipImages() const673 bool SfxMedium::IsSkipImages() const
674 {
675     const SfxStringItem* pSkipImagesItem = GetItemSet().GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS);
676     return pSkipImagesItem && pSkipImagesItem->GetValue() == "SkipImages";
677 }
678 
GetInStream()679 SvStream* SfxMedium::GetInStream()
680 {
681     if ( pImpl->m_pInStream )
682         return pImpl->m_pInStream.get();
683 
684     if ( pImpl->pTempFile )
685     {
686         pImpl->m_pInStream.reset( new SvFileStream(pImpl->m_aName, pImpl->m_nStorOpenMode) );
687 
688         pImpl->m_eError = pImpl->m_pInStream->GetError();
689 
690         if (!pImpl->m_eError && (pImpl->m_nStorOpenMode & StreamMode::WRITE)
691                     && ! pImpl->m_pInStream->IsWritable() )
692         {
693             pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
694             pImpl->m_pInStream.reset();
695         }
696         else
697             return pImpl->m_pInStream.get();
698     }
699 
700     GetMedium_Impl();
701 
702     if ( GetErrorIgnoreWarning() )
703         return nullptr;
704 
705     return pImpl->m_pInStream.get();
706 }
707 
708 
CloseInStream()709 void SfxMedium::CloseInStream()
710 {
711     CloseInStream_Impl();
712 }
713 
CloseInStream_Impl(bool bInDestruction)714 void SfxMedium::CloseInStream_Impl(bool bInDestruction)
715 {
716     // if there is a storage based on the InStream, we have to
717     // close the storage, too, because otherwise the storage
718     // would use an invalid ( deleted ) stream.
719     if ( pImpl->m_pInStream && pImpl->xStorage.is() )
720     {
721         if ( pImpl->bStorageBasedOnInStream )
722             CloseStorage();
723     }
724 
725     if ( pImpl->m_pInStream && !GetContent().is() && !bInDestruction )
726     {
727         CreateTempFile();
728         return;
729     }
730 
731     pImpl->m_pInStream.reset();
732     if ( pImpl->m_pSet )
733         pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
734 
735     CloseZipStorage_Impl();
736     pImpl->xInputStream.clear();
737 
738     if ( !pImpl->m_pOutStream )
739     {
740         // output part of the stream is not used so the whole stream can be closed
741         // TODO/LATER: is it correct?
742         pImpl->xStream.clear();
743         if ( pImpl->m_pSet )
744             pImpl->m_pSet->ClearItem( SID_STREAM );
745     }
746 }
747 
748 
GetOutStream()749 SvStream* SfxMedium::GetOutStream()
750 {
751     if ( !pImpl->m_pOutStream )
752     {
753         // Create a temp. file if there is none because we always
754         // need one.
755         CreateTempFile( false );
756 
757         if ( pImpl->pTempFile )
758         {
759             // On windows we try to re-use XOutStream from xStream if that exists;
760             // because opening new SvFileStream in this situation may fail with ERROR_SHARING_VIOLATION
761             // TODO: this is a horrible hack that should probably be removed,
762             // somebody needs to investigate this more thoroughly...
763             if (getenv("SFX_MEDIUM_REUSE_STREAM") && pImpl->xStream.is())
764             {
765                 assert(pImpl->xStream->getOutputStream().is()); // need that...
766                 pImpl->m_pOutStream = utl::UcbStreamHelper::CreateStream(
767                         pImpl->xStream, false);
768             }
769             else
770             {
771             // On Unix don't try to re-use XOutStream from xStream if that exists;
772             // it causes fdo#59022 (fails opening files via SMB on Linux)
773                 pImpl->m_pOutStream.reset( new SvFileStream(
774                             pImpl->m_aName, StreamMode::STD_READWRITE) );
775             }
776             CloseStorage();
777         }
778     }
779 
780     return pImpl->m_pOutStream.get();
781 }
782 
783 
CloseOutStream()784 void SfxMedium::CloseOutStream()
785 {
786     CloseOutStream_Impl();
787 }
788 
CloseOutStream_Impl()789 void SfxMedium::CloseOutStream_Impl()
790 {
791     if ( pImpl->m_pOutStream )
792     {
793         // if there is a storage based on the OutStream, we have to
794         // close the storage, too, because otherwise the storage
795         // would use an invalid ( deleted ) stream.
796         //TODO/MBA: how to deal with this?!
797         //maybe we need a new flag when the storage was created from the outstream
798         if ( pImpl->xStorage.is() )
799         {
800                 CloseStorage();
801         }
802 
803         pImpl->m_pOutStream.reset();
804     }
805 
806     if ( !pImpl->m_pInStream )
807     {
808         // input part of the stream is not used so the whole stream can be closed
809         // TODO/LATER: is it correct?
810         pImpl->xStream.clear();
811         if ( pImpl->m_pSet )
812             pImpl->m_pSet->ClearItem( SID_STREAM );
813     }
814 }
815 
816 
GetPhysicalName() const817 const OUString& SfxMedium::GetPhysicalName() const
818 {
819     if ( pImpl->m_aName.isEmpty() && !pImpl->m_aLogicName.isEmpty() )
820         const_cast<SfxMedium*>(this)->CreateFileStream();
821 
822     // return the name then
823     return pImpl->m_aName;
824 }
825 
826 
CreateFileStream()827 void SfxMedium::CreateFileStream()
828 {
829     // force synchron
830     if( pImpl->m_pInStream )
831     {
832         SvLockBytes* pBytes = pImpl->m_pInStream->GetLockBytes();
833         if( pBytes )
834             pBytes->SetSynchronMode();
835     }
836 
837     GetInStream();
838     if( pImpl->m_pInStream )
839     {
840         CreateTempFile( false );
841         pImpl->bIsTemp = true;
842         CloseInStream_Impl();
843     }
844 }
845 
846 
Commit()847 bool SfxMedium::Commit()
848 {
849     if( pImpl->xStorage.is() )
850         StorageCommit_Impl();
851     else if( pImpl->m_pOutStream  )
852         pImpl->m_pOutStream->FlushBuffer();
853     else if( pImpl->m_pInStream  )
854         pImpl->m_pInStream->FlushBuffer();
855 
856     if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
857     {
858         // does something only in case there is a temporary file ( means aName points to different location than aLogicName )
859         Transfer_Impl();
860     }
861 
862     bool bResult = ( GetErrorIgnoreWarning() == ERRCODE_NONE );
863 
864     if ( bResult && DocNeedsFileDateCheck() )
865         GetInitFileDate( true );
866 
867     // remove truncation mode from the flags
868     pImpl->m_nStorOpenMode &= ~StreamMode::TRUNC;
869     return bResult;
870 }
871 
872 
IsStorage()873 bool SfxMedium::IsStorage()
874 {
875     if ( pImpl->xStorage.is() )
876         return true;
877 
878     if ( pImpl->m_bTriedStorage )
879         return pImpl->bIsStorage;
880 
881     if ( pImpl->pTempFile )
882     {
883         OUString aURL;
884         if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL )
885              != osl::FileBase::E_None )
886         {
887             SAL_WARN( "sfx.doc", "Physical name '" << pImpl->m_aName << "' not convertible to file URL");
888         }
889         pImpl->bIsStorage = SotStorage::IsStorageFile( aURL ) && !SotStorage::IsOLEStorage( aURL);
890         if ( !pImpl->bIsStorage )
891             pImpl->m_bTriedStorage = true;
892     }
893     else if ( GetInStream() )
894     {
895         pImpl->bIsStorage = SotStorage::IsStorageFile( pImpl->m_pInStream.get() ) && !SotStorage::IsOLEStorage( pImpl->m_pInStream.get() );
896         if ( !pImpl->m_pInStream->GetError() && !pImpl->bIsStorage )
897             pImpl->m_bTriedStorage = true;
898     }
899 
900     return pImpl->bIsStorage;
901 }
902 
903 
IsPreview_Impl() const904 bool SfxMedium::IsPreview_Impl() const
905 {
906     bool bPreview = false;
907     const SfxBoolItem* pPreview = GetItemSet().GetItem(SID_PREVIEW, false);
908     if ( pPreview )
909         bPreview = pPreview->GetValue();
910     else
911     {
912         const SfxStringItem* pFlags = GetItemSet().GetItem(SID_OPTIONS, false);
913         if ( pFlags )
914         {
915             OUString aFileFlags = pFlags->GetValue();
916             aFileFlags = aFileFlags.toAsciiUpperCase();
917             if ( -1 != aFileFlags.indexOf( 'B' ) )
918                 bPreview = true;
919         }
920     }
921 
922     return bPreview;
923 }
924 
925 
StorageBackup_Impl()926 void SfxMedium::StorageBackup_Impl()
927 {
928     ::ucbhelper::Content aOriginalContent;
929     Reference< css::ucb::XCommandEnvironment > xDummyEnv;
930 
931     bool bBasedOnOriginalFile =
932         !pImpl->pTempFile
933         && ( pImpl->m_aLogicName.isEmpty() || !pImpl->m_bSalvageMode )
934         && !GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ).isEmpty()
935         && GetURLObject().GetProtocol() == INetProtocol::File
936         && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
937 
938     if ( bBasedOnOriginalFile && pImpl->m_aBackupURL.isEmpty()
939       && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aOriginalContent ) )
940     {
941         DoInternalBackup_Impl( aOriginalContent );
942         if( pImpl->m_aBackupURL.isEmpty() )
943             SetError(ERRCODE_SFX_CANTCREATEBACKUP);
944     }
945 }
946 
947 
GetBackup_Impl()948 OUString const & SfxMedium::GetBackup_Impl()
949 {
950     if ( pImpl->m_aBackupURL.isEmpty() )
951         StorageBackup_Impl();
952 
953     return pImpl->m_aBackupURL;
954 }
955 
956 
GetOutputStorage()957 uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()
958 {
959     if ( GetErrorIgnoreWarning() )
960         return uno::Reference< embed::XStorage >();
961 
962     // if the medium was constructed with a Storage: use this one, not a temp. storage
963     // if a temporary storage already exists: use it
964     if (pImpl->xStorage.is()
965         && (pImpl->m_bODFWholesomeEncryption || pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile))
966     {
967         return pImpl->xStorage;
968     }
969 
970     // if necessary close stream that was used for reading
971     if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
972         CloseInStream();
973 
974     DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" );
975 
976     // TODO/LATER: The current solution is to store the document temporary and then copy it to the target location;
977     // in future it should be stored directly and then copied to the temporary location, since in this case no
978     // file attributes have to be preserved and system copying mechanics could be used instead of streaming.
979     CreateTempFileNoCopy();
980 
981     return GetStorage();
982 }
983 
984 
SetEncryptionDataToStorage_Impl()985 bool SfxMedium::SetEncryptionDataToStorage_Impl()
986 {
987     // in case media-descriptor contains password it should be used on opening
988     if ( !pImpl->xStorage.is() || !pImpl->m_pSet )
989         return false;
990 
991     uno::Sequence< beans::NamedValue > aEncryptionData;
992     if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
993         return false;
994 
995     // replace the password with encryption data
996     pImpl->m_pSet->ClearItem( SID_PASSWORD );
997     pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) );
998 
999     try
1000     {
1001         ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData );
1002     }
1003     catch( const uno::Exception& )
1004     {
1005         SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
1006         SetError(ERRCODE_IO_GENERAL);
1007         return false;
1008     }
1009     return true;
1010 }
1011 
1012 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1013 
1014 // FIXME: Hmm actually lock files should be used for sftp: documents
1015 // even if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT. Only the use of lock
1016 // files for *local* documents is unnecessary in that case. But
1017 // actually, the checks for sftp: here are just wishful thinking; I
1018 // don't this there is any support for actually editing documents
1019 // behind sftp: URLs anyway.
1020 
1021 // Sure, there could perhaps be a 3rd-party extension that brings UCB
1022 // the potential to handle files behind sftp:. But there could also be
1023 // an extension that handles some arbitrary foobar: scheme *and* it
1024 // could be that lock files would be the correct thing to use for
1025 // foobar: documents, too. But the hardcoded test below won't know
1026 // that. Clearly the knowledge whether lock files should be used or
1027 // not for some URL scheme belongs in UCB, not here.
1028 
1029 namespace
1030 {
1031 
tryMSOwnerFiles(std::u16string_view sDocURL)1032 OUString tryMSOwnerFiles(std::u16string_view sDocURL)
1033 {
1034     svt::MSODocumentLockFile aMSOLockFile(sDocURL);
1035     LockFileEntry aData;
1036     try
1037     {
1038         aData = aMSOLockFile.GetLockData();
1039     }
1040     catch( const uno::Exception& )
1041     {
1042         return OUString();
1043     }
1044 
1045     OUString sUserData = aData[LockFileComponent::OOOUSERNAME];
1046 
1047     if (!sUserData.isEmpty())
1048         sUserData += " (MS Office)"; // Mention the used office suite
1049 
1050     return sUserData;
1051 }
1052 
tryForeignLockfiles(std::u16string_view sDocURL)1053 OUString tryForeignLockfiles(std::u16string_view sDocURL)
1054 {
1055     OUString sUserData = tryMSOwnerFiles(sDocURL);
1056     // here we can test for empty result, and add other known applications' lockfile testing
1057     return sUserData.trim();
1058 }
1059 }
1060 
ShowLockedDocumentDialog(const LockFileEntry & aData,bool bIsLoading,bool bOwnLock,bool bHandleSysLocked)1061 SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog(const LockFileEntry& aData,
1062                                                               bool bIsLoading, bool bOwnLock,
1063                                                               bool bHandleSysLocked)
1064 {
1065     ShowLockResult nResult = ShowLockResult::NoLock;
1066 
1067     // tdf#92817: Simple check for empty lock file that needs to be deleted, when system locking is enabled
1068     if( aData[LockFileComponent::OOOUSERNAME].isEmpty() && aData[LockFileComponent::SYSUSERNAME].isEmpty() && !bHandleSysLocked )
1069         bOwnLock=true;
1070 
1071     // show the interaction regarding the document opening
1072     uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
1073 
1074     if ( xHandler.is() && ( bIsLoading || !bHandleSysLocked || bOwnLock ) )
1075     {
1076         OUString aDocumentURL
1077             = GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
1078         OUString aInfo;
1079         ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl;
1080 
1081         sal_Int32 nContinuations = 3;
1082 
1083         if ( bOwnLock )
1084         {
1085             aInfo = aData[LockFileComponent::EDITTIME];
1086 
1087             xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
1088                 document::OwnLockOnDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo, !bIsLoading ) ) );
1089         }
1090         else
1091         {
1092             // Use a fourth continuation in case there's no filesystem lock:
1093             // "Ignore lock file and open/replace the document"
1094             if (!bHandleSysLocked)
1095                 nContinuations = 4;
1096 
1097             if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() )
1098                 aInfo = aData[LockFileComponent::OOOUSERNAME];
1099             else
1100                 aInfo = aData[LockFileComponent::SYSUSERNAME];
1101 
1102             if (aInfo.isEmpty() && !GetURLObject().isAnyKnownWebDAVScheme())
1103                 // Try to get name of user who has locked the file using other applications
1104                 aInfo = tryForeignLockfiles(
1105                     GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
1106 
1107             if ( !aInfo.isEmpty() && !aData[LockFileComponent::EDITTIME].isEmpty() )
1108                 aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )";
1109 
1110             if (!bIsLoading) // so, !bHandleSysLocked
1111             {
1112                 xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any(
1113                     document::LockedOnSavingRequest(OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo)));
1114                 // Currently, only the last "Retry" continuation (meaning ignore the lock and try overwriting) can be returned.
1115             }
1116             else /*logically therefore bIsLoading is set */
1117             {
1118                 xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
1119                     document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) );
1120             }
1121         }
1122 
1123         uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations);
1124         auto pContinuations = aContinuations.getArray();
1125         pContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
1126         pContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
1127         pContinuations[2] = new ::ucbhelper::InteractionDisapprove( xInteractionRequestImpl.get() );
1128         if (nContinuations > 3)
1129         {
1130             // We use InteractionRetry to reflect that user wants to
1131             // ignore the (stale?) alien lock file and open/overwrite the document
1132             pContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get());
1133         }
1134         xInteractionRequestImpl->setContinuations( aContinuations );
1135 
1136         xHandler->handle( xInteractionRequestImpl );
1137 
1138         bool bOpenReadOnly = false;
1139         ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
1140         if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
1141         {
1142             SetError(ERRCODE_ABORT);
1143         }
1144         else if ( uno::Reference< task::XInteractionDisapprove >( xSelected.get(), uno::UNO_QUERY ).is() )
1145         {
1146             // own lock on loading, user has selected to ignore the lock
1147             // own lock on saving, user has selected to ignore the lock
1148             // alien lock on loading, user has selected to edit a copy of document
1149             // TODO/LATER: alien lock on saving, user has selected to do SaveAs to different location
1150             if ( !bOwnLock ) // bIsLoading implied from outermost condition
1151             {
1152                 // means that a copy of the document should be opened
1153                 GetItemSet().Put( SfxBoolItem( SID_TEMPLATE, true ) );
1154             }
1155             else
1156                 nResult = ShowLockResult::Succeeded;
1157         }
1158         else if (uno::Reference< task::XInteractionRetry >(xSelected.get(), uno::UNO_QUERY).is())
1159         {
1160             // User decided to ignore the alien (stale?) lock file without filesystem lock
1161             nResult = ShowLockResult::Succeeded;
1162         }
1163         else if (uno::Reference< task::XInteractionApprove >( xSelected.get(), uno::UNO_QUERY ).is())
1164         {
1165             bOpenReadOnly = true;
1166         }
1167         else // user selected "Notify"
1168         {
1169             pImpl->m_bNotifyWhenEditable = true;
1170             AddToCheckEditableWorkerList();
1171             bOpenReadOnly = true;
1172         }
1173 
1174         if (bOpenReadOnly)
1175         {
1176             // own lock on loading, user has selected to open readonly
1177             // own lock on saving, user has selected to open readonly
1178             // alien lock on loading, user has selected to retry saving
1179             // TODO/LATER: alien lock on saving, user has selected to retry saving
1180 
1181             if (bIsLoading)
1182                 GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
1183             else
1184                 nResult = ShowLockResult::Try;
1185         }
1186     }
1187     else
1188     {
1189         if ( bIsLoading )
1190         {
1191             // if no interaction handler is provided the default answer is open readonly
1192             // that usually happens in case the document is loaded per API
1193             // so the document must be opened readonly for backward compatibility
1194             GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1195         }
1196         else
1197             SetError(ERRCODE_IO_ACCESSDENIED);
1198 
1199     }
1200 
1201     return nResult;
1202 }
1203 
ShowLockFileProblemDialog(MessageDlg nWhichDlg)1204 bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg)
1205 {
1206     // system file locking is not active, ask user whether he wants to open the document without any locking
1207     uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
1208 
1209     if (xHandler.is())
1210     {
1211         ::rtl::Reference< ::ucbhelper::InteractionRequest > xIgnoreRequestImpl;
1212 
1213         switch (nWhichDlg)
1214         {
1215             case MessageDlg::LockFileIgnore:
1216                 xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileIgnoreRequest() ));
1217                 break;
1218             case MessageDlg::LockFileCorrupt:
1219                 xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileCorruptRequest() ));
1220                 break;
1221         }
1222 
1223         uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
1224             new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get()),
1225             new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get())
1226         };
1227         xIgnoreRequestImpl->setContinuations(aContinuations);
1228 
1229         xHandler->handle(xIgnoreRequestImpl);
1230 
1231         ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xIgnoreRequestImpl->getSelection();
1232         bool bReadOnly = true;
1233 
1234         if (uno::Reference<task::XInteractionAbort>(xSelected.get(), uno::UNO_QUERY).is())
1235         {
1236             SetError(ERRCODE_ABORT);
1237             bReadOnly = false;
1238         }
1239         else if (!uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
1240         {
1241             // user selected "Notify"
1242             pImpl->m_bNotifyWhenEditable = true;
1243             AddToCheckEditableWorkerList();
1244         }
1245 
1246         if (bReadOnly)
1247             GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
1248 
1249         return bReadOnly;
1250     }
1251 
1252     return false;
1253 }
1254 
1255 namespace
1256 {
isSuitableProtocolForLocking(const OUString & rLogicName)1257     bool isSuitableProtocolForLocking(const OUString & rLogicName)
1258     {
1259         INetURLObject aUrl( rLogicName );
1260         INetProtocol eProt = aUrl.GetProtocol();
1261 #if !HAVE_FEATURE_MACOSX_SANDBOX
1262         if (eProt == INetProtocol::File) {
1263             return true;
1264         }
1265 #endif
1266         return eProt == INetProtocol::Smb || eProt == INetProtocol::Sftp;
1267     }
1268 }
1269 
1270 namespace
1271 {
1272 
1273 // for LOCK request, suppress dialog on 403, typically indicates read-only
1274 // document and there's a 2nd dialog prompting to open a copy anyway
1275 class LockInteractionHandler : public ::cppu::WeakImplHelper<task::XInteractionHandler>
1276 {
1277 private:
1278     uno::Reference<task::XInteractionHandler> m_xHandler;
1279 
1280 public:
LockInteractionHandler(uno::Reference<task::XInteractionHandler> const & xHandler)1281     explicit LockInteractionHandler(uno::Reference<task::XInteractionHandler> const& xHandler)
1282         : m_xHandler(xHandler)
1283     {
1284     }
1285 
handle(uno::Reference<task::XInteractionRequest> const & xRequest)1286     virtual void SAL_CALL handle(uno::Reference<task::XInteractionRequest> const& xRequest) override
1287     {
1288         ucb::InteractiveNetworkWriteException readException;
1289         ucb::InteractiveNetworkReadException writeException;
1290         if ((xRequest->getRequest() >>= readException)
1291             || (xRequest->getRequest() >>= writeException))
1292         {
1293             return; // 403 gets reported as one of these; ignore to avoid dialog
1294         }
1295         m_xHandler->handle(xRequest);
1296     }
1297 };
1298 
1299 } // namespace
1300 
1301 #endif // HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1302 
1303 // sets SID_DOC_READONLY if the document cannot be opened for editing
1304 // if user cancel the loading the ERROR_ABORT is set
LockOrigFileOnDemand(bool bLoading,bool bNoUI,bool bTryIgnoreLockFile,LockFileEntry * pLockData)1305 SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bNoUI,
1306                                                           bool bTryIgnoreLockFile,
1307                                                           LockFileEntry* pLockData)
1308 {
1309 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
1310     (void) bLoading;
1311     (void) bNoUI;
1312     (void) bTryIgnoreLockFile;
1313     (void) pLockData;
1314     return LockFileResult::Succeeded;
1315 #else
1316     LockFileResult eResult = LockFileResult::Failed;
1317 
1318     // check if path scheme is http:// or https://
1319     // may be this is better if used always, in Android and iOS as well?
1320     // if this code should be always there, remember to move the relevant code in UnlockFile method as well !
1321 
1322     if ( GetURLObject().isAnyKnownWebDAVScheme() )
1323     {
1324         // do nothing if WebDAV locking is disabled
1325         if (!IsWebDAVLockingUsed())
1326             return LockFileResult::Succeeded;
1327 
1328         {
1329             bool bResult = pImpl->m_bLocked;
1330             bool bIsTemplate = false;
1331             // so, this is webdav stuff...
1332             if ( !bResult )
1333             {
1334                 // no read-write access is necessary on loading if the document is explicitly opened as copy
1335                 const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
1336                 bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
1337             }
1338 
1339             if ( !bIsTemplate && !bResult && !IsReadOnly() )
1340             {
1341                 ShowLockResult bUIStatus = ShowLockResult::NoLock;
1342                 do
1343                 {
1344                     if( !bResult )
1345                     {
1346                         uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true );
1347                         // Dialog with error is superfluous:
1348                         // on loading, will result in read-only with infobar.
1349                         // bNoUI case for Reload failing, will open dialog later.
1350                         if (bLoading || bNoUI)
1351                         {
1352                             xCHandler = new LockInteractionHandler(xCHandler);
1353                         }
1354                         Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment(
1355                             xCHandler, Reference< css::ucb::XProgressHandler >() );
1356 
1357                         ucbhelper::Content aContentToLock(
1358                             GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
1359                             xComEnv, comphelper::getProcessComponentContext() );
1360 
1361                         try
1362                         {
1363                             aContentToLock.lock();
1364                             bResult = true;
1365                         }
1366                         catch ( ucb::InteractiveLockingLockedException& )
1367                         {
1368                             // received when the resource is already locked
1369                             if (!bNoUI || pLockData)
1370                             {
1371                                 // get the lock owner, using a special ucb.webdav property
1372                                 // the owner property retrieved here is  what the other principal send the server
1373                                 // when activating the lock.
1374                                 // See http://tools.ietf.org/html/rfc4918#section-14.17 for details
1375                                 LockFileEntry aLockData;
1376                                 aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user";
1377                                 // This solution works right when the LO user name and the WebDAV user
1378                                 // name are the same.
1379                                 // A better thing to do would be to obtain the 'real' WebDAV user name,
1380                                 // but that's not possible from a WebDAV UCP provider client.
1381                                 LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
1382                                 // use the current LO user name as the system name
1383                                 aLockData[LockFileComponent::SYSUSERNAME]
1384                                     = aOwnData[LockFileComponent::SYSUSERNAME];
1385 
1386                                 uno::Sequence<css::ucb::Lock> aLocks;
1387                                 // getting the property, send a PROPFIND to the server over the net
1388                                 if ((aContentToLock.getPropertyValue(u"DAV:lockdiscovery"_ustr) >>= aLocks) && aLocks.hasElements())
1389                                 {
1390                                     // got at least a lock, show the owner of the first lock returned
1391                                     css::ucb::Lock aLock = aLocks[0];
1392                                     OUString aOwner;
1393                                     if (aLock.Owner >>= aOwner)
1394                                     {
1395                                         // we need to display the WebDAV user name owning the lock, not the local one
1396                                         aLockData[LockFileComponent::OOOUSERNAME] = aOwner;
1397                                     }
1398                                 }
1399 
1400                                 if (!bNoUI)
1401                                 {
1402                                     bUIStatus = ShowLockedDocumentDialog(aLockData, bLoading, false,
1403                                                                          true);
1404                                 }
1405 
1406                                 if (pLockData)
1407                                 {
1408                                     std::copy(aLockData.begin(), aLockData.end(), pLockData->begin());
1409                                 }
1410                             }
1411                         }
1412                         catch( ucb::InteractiveNetworkWriteException& )
1413                         {
1414                             // This catch it's not really needed, here just for the sake of documentation on the behaviour.
1415                             // This is the most likely reason:
1416                             // - the remote site is a WebDAV with special configuration: read/only for read operations
1417                             //   and read/write for write operations, the user is not allowed to lock/write and
1418                             //   she cancelled the credentials request.
1419                             //   this is not actually an error, but the exception is sent directly from ucb, avoiding the automatic
1420                             //   management that takes part in cancelCommandExecution()
1421                             // Unfortunately there is no InteractiveNetwork*Exception available to signal this more correctly
1422                             // since it mostly happens on read/only part of webdav, this can be the most correct
1423                             // exception available
1424                         }
1425                         catch( uno::Exception& )
1426                         {
1427                             TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
1428                         }
1429                     }
1430                 } while( !bResult && bUIStatus == ShowLockResult::Try );
1431             }
1432 
1433             pImpl->m_bLocked = bResult;
1434 
1435             if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
1436             {
1437                 // the error should be set in case it is storing process
1438                 // or the document has been opened for editing explicitly
1439                 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
1440 
1441                 if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
1442                     SetError(ERRCODE_IO_ACCESSDENIED);
1443                 else
1444                     GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1445             }
1446 
1447             // when the file is locked, get the current file date
1448             if ( bResult && DocNeedsFileDateCheck() )
1449                 GetInitFileDate( true );
1450 
1451             if ( bResult )
1452                 eResult = LockFileResult::Succeeded;
1453         }
1454         return eResult;
1455     }
1456 
1457     if (!IsLockingUsed())
1458         return LockFileResult::Succeeded;
1459     if (GetURLObject().HasError())
1460         return eResult;
1461 
1462     try
1463     {
1464         if ( pImpl->m_bLocked && bLoading
1465              && GetURLObject().GetProtocol() == INetProtocol::File )
1466         {
1467             // if the document is already locked the system locking might be temporarily off after storing
1468             // check whether the system file locking should be taken again
1469             GetLockingStream_Impl();
1470         }
1471 
1472         bool bResult = pImpl->m_bLocked;
1473 
1474         if ( !bResult )
1475         {
1476             // no read-write access is necessary on loading if the document is explicitly opened as copy
1477             const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
1478             bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
1479         }
1480 
1481         if ( !bResult && !IsReadOnly() )
1482         {
1483             bool bContentReadonly = false;
1484             if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File )
1485             {
1486                 // let the original document be opened to check the possibility to open it for editing
1487                 // and to let the writable stream stay open to hold the lock on the document
1488                 GetLockingStream_Impl();
1489             }
1490 
1491             // "IsReadOnly" property does not allow to detect whether the file is readonly always
1492             // so we try always to open the file for editing
1493             // the file is readonly only in case the read-write stream can not be opened
1494             if ( bLoading && !pImpl->m_xLockingStream.is() )
1495             {
1496                 try
1497                 {
1498                     // MediaDescriptor does this check also, the duplication should be avoided in future
1499                     Reference< css::ucb::XCommandEnvironment > xDummyEnv;
1500                     ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
1501                     aContent.getPropertyValue(u"IsReadOnly"_ustr) >>= bContentReadonly;
1502                 }
1503                 catch( const uno::Exception& ) {}
1504             }
1505 
1506             // do further checks only if the file not readonly in fs
1507             if ( !bContentReadonly )
1508             {
1509                 // the special file locking should be used only for suitable URLs
1510                 if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) )
1511                 {
1512 
1513                     // in case of storing the document should request the output before locking
1514                     if ( bLoading )
1515                     {
1516                         // let the stream be opened to check the system file locking
1517                         GetMedium_Impl();
1518                         if (GetErrorIgnoreWarning() != ERRCODE_NONE) {
1519                             return eResult;
1520                         }
1521                     }
1522 
1523                     ShowLockResult bUIStatus = ShowLockResult::NoLock;
1524 
1525                     // check whether system file locking has been used, the default value is false
1526                     bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed();
1527 
1528                     // TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem
1529                     // if system lock is used the writeable stream should be available
1530                     bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream );
1531 
1532                     // The file is attempted to get locked for the duration of lockfile creation on save
1533                     std::unique_ptr<osl::File> pFileLock;
1534                     if (!bLoading && bUseSystemLock && pImpl->pTempFile)
1535                     {
1536                         INetURLObject aDest(GetURLObject());
1537                         OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE));
1538 
1539                         if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment())
1540                         {
1541                             pFileLock = std::make_unique<osl::File>(aDestURL);
1542                             auto rc = pFileLock->open(osl_File_OpenFlag_Write);
1543                             if (rc == osl::FileBase::E_ACCES)
1544                                 bHandleSysLocked = true;
1545                         }
1546                     }
1547 
1548                     do
1549                     {
1550                         try
1551                         {
1552                             ::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName );
1553 
1554                             std::unique_ptr<svt::MSODocumentLockFile> pMSOLockFile;
1555                             if (officecfg::Office::Common::Filter::Microsoft::Import::CreateMSOLockFiles::get()  && svt::MSODocumentLockFile::IsMSOSupportedFileFormat(pImpl->m_aLogicName))
1556                             {
1557                                 pMSOLockFile.reset(new svt::MSODocumentLockFile(pImpl->m_aLogicName));
1558                                 pImpl->m_bMSOLockFileCreated = true;
1559                             }
1560 
1561                             bool  bIoErr = false;
1562 
1563                             if (!bHandleSysLocked)
1564                             {
1565                                 try
1566                                 {
1567                                     bResult = aLockFile.CreateOwnLockFile();
1568                                     if(pMSOLockFile)
1569                                         bResult &= pMSOLockFile->CreateOwnLockFile();
1570                                 }
1571                                 catch (const uno::Exception&)
1572                                 {
1573                                     if (tools::IsMappedWebDAVPath(GetURLObject().GetMainURL(
1574                                             INetURLObject::DecodeMechanism::NONE)))
1575                                     {
1576                                         // This is a path that redirects to a WebDAV resource;
1577                                         // so failure creating lockfile is not an error here.
1578                                         bResult = true;
1579                                     }
1580                                     else if (bLoading && !bNoUI)
1581                                     {
1582                                         bIoErr = true;
1583                                         ShowLockFileProblemDialog(MessageDlg::LockFileIgnore);
1584                                         bResult = true;   // always delete the defect lock-file
1585                                     }
1586                                 }
1587 
1588                                 // in case OOo locking is turned off the lock file is still written if possible
1589                                 // but it is ignored while deciding whether the document should be opened for editing or not
1590                                 if (!bResult && !IsOOoLockFileUsed() && !bIoErr)
1591                                 {
1592                                     bResult = true;
1593                                     // take the ownership over the lock file
1594                                     aLockFile.OverwriteOwnLockFile();
1595 
1596                                     if(pMSOLockFile)
1597                                         pMSOLockFile->OverwriteOwnLockFile();
1598                                 }
1599                             }
1600 
1601                             if ( !bResult )
1602                             {
1603                                 LockFileEntry aData;
1604                                 try
1605                                 {
1606                                     aData = aLockFile.GetLockData();
1607                                 }
1608                                 catch (const io::WrongFormatException&)
1609                                 {
1610                                     // we get empty or corrupt data
1611                                     // info to the user
1612                                     if (!bIoErr && bLoading && !bNoUI )
1613                                         bResult = ShowLockFileProblemDialog(MessageDlg::LockFileCorrupt);
1614 
1615                                     // not show the Lock Document Dialog
1616                                     bIoErr = true;
1617                                 }
1618                                 catch( const uno::Exception& )
1619                                 {
1620                                     // show the Lock Document Dialog, when locked from other app
1621                                     bIoErr = !bHandleSysLocked;
1622                                 }
1623 
1624                                 bool bOwnLock = false;
1625 
1626                                 if (!bHandleSysLocked)
1627                                 {
1628                                     LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
1629                                     bOwnLock = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
1630 
1631                                     if (bOwnLock
1632                                         && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
1633                                         && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
1634                                     {
1635                                         // this is own lock from the same installation, it could remain because of crash
1636                                         bResult = true;
1637                                     }
1638                                 }
1639 
1640                                 if ( !bResult && !bIoErr)
1641                                 {
1642                                     if (!bNoUI)
1643                                         bUIStatus = ShowLockedDocumentDialog(
1644                                             aData, bLoading, bOwnLock, bHandleSysLocked);
1645                                     else if (bLoading && bTryIgnoreLockFile && !bHandleSysLocked)
1646                                         bUIStatus = ShowLockResult::Succeeded;
1647 
1648                                     if ( bUIStatus == ShowLockResult::Succeeded )
1649                                     {
1650                                         // take the ownership over the lock file
1651                                         bResult = aLockFile.OverwriteOwnLockFile();
1652 
1653                                         if(pMSOLockFile)
1654                                             pMSOLockFile->OverwriteOwnLockFile();
1655                                     }
1656                                     else if (bLoading && !bHandleSysLocked)
1657                                         eResult = LockFileResult::FailedLockFile;
1658 
1659                                     if (!bResult && pLockData)
1660                                     {
1661                                         std::copy(aData.begin(), aData.end(), pLockData->begin());
1662                                     }
1663                                 }
1664                             }
1665                         }
1666                         catch( const uno::Exception& )
1667                         {
1668                         }
1669                     } while( !bResult && bUIStatus == ShowLockResult::Try );
1670 
1671                     pImpl->m_bLocked = bResult;
1672                 }
1673                 else
1674                 {
1675                     // this is no file URL, check whether the file is readonly
1676                     bResult = !bContentReadonly;
1677                 }
1678             }
1679             else // read-only
1680             {
1681                 AddToCheckEditableWorkerList();
1682             }
1683         }
1684 
1685         if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
1686         {
1687             // the error should be set in case it is storing process
1688             // or the document has been opened for editing explicitly
1689             const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
1690 
1691             if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
1692                 SetError(ERRCODE_IO_ACCESSDENIED);
1693             else
1694                 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1695         }
1696 
1697         // when the file is locked, get the current file date
1698         if ( bResult && DocNeedsFileDateCheck() )
1699             GetInitFileDate( true );
1700 
1701         if ( bResult )
1702             eResult = LockFileResult::Succeeded;
1703     }
1704     catch( const uno::Exception& )
1705     {
1706         TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: high probability, that the content has not been created" );
1707     }
1708 
1709     return eResult;
1710 #endif
1711 }
1712 
1713 // this either returns non-null or throws exception
1714 uno::Reference<embed::XStorage>
TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const xStorage)1715 SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const xStorage)
1716 {
1717     uno::Reference<embed::XStorage> xRet;
1718     if (xStorage->hasByName(u"encrypted-package"_ustr))
1719     {
1720         uno::Reference<io::XStream> const
1721             xDecryptedInnerPackage = xStorage->openStreamElement(
1722                 u"encrypted-package"_ustr,
1723                 embed::ElementModes::READ | embed::ElementModes::NOCREATE);
1724         // either this throws due to wrong password or IO error, or returns stream
1725         assert(xDecryptedInnerPackage.is());
1726         // need a seekable stream => copy
1727         Reference<uno::XComponentContext> const xContext(::comphelper::getProcessComponentContext());
1728         uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
1729             xContext->getServiceManager()->createInstanceWithContext(
1730                 u"com.sun.star.comp.MemoryStream"_ustr, xContext),
1731             UNO_QUERY_THROW);
1732         comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
1733         xDecryptedInnerPackageStream->getOutputStream()->closeOutput();
1734 #if 0
1735         // debug: dump to temp file
1736         uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
1737         xTempFile->setRemoveFile(false);
1738         comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
1739         xTempFile->getOutputStream()->closeOutput();
1740         SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
1741         uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
1742 #endif
1743         // create inner storage; opening the stream should have already verified
1744         // the password so any failure here is probably due to a bug
1745         xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
1746             PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
1747             embed::ElementModes::READWRITE, xContext, false);
1748         assert(xRet.is());
1749         // consistency check: outer and inner package must have same mimetype
1750         OUString const outerMediaType(uno::Reference<beans::XPropertySet>(pImpl->xStorage,
1751             uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>());
1752         OUString const innerMediaType(uno::Reference<beans::XPropertySet>(xRet,
1753             uno::UNO_QUERY_THROW)->getPropertyValue(u"MediaType"_ustr).get<OUString>());
1754         if (outerMediaType.isEmpty() || outerMediaType != innerMediaType)
1755         {
1756             throw io::WrongFormatException(u"MediaType inconsistent in encrypted ODF package"_ustr);
1757         }
1758         // success:
1759         pImpl->m_bODFWholesomeEncryption = true;
1760         pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
1761         pImpl->m_xODFEncryptedOuterStorage = xStorage;
1762         pImpl->xStorage = xRet;
1763     }
1764     return xRet;
1765 }
1766 
IsRepairPackage() const1767 bool SfxMedium::IsRepairPackage() const
1768 {
1769     const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
1770     return pRepairItem && pRepairItem->GetValue();
1771 }
1772 
GetStorage(bool bCreateTempFile)1773 uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
1774 {
1775     if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage )
1776         return pImpl->xStorage;
1777 
1778     uno::Sequence< uno::Any > aArgs( 2 );
1779     auto pArgs = aArgs.getArray();
1780 
1781     // the medium should be retrieved before temporary file creation
1782     // to let the MediaDescriptor be filled with the streams
1783     GetMedium_Impl();
1784 
1785     if ( bCreateTempFile )
1786         CreateTempFile( false );
1787 
1788     GetMedium_Impl();
1789 
1790     if ( GetErrorIgnoreWarning() )
1791         return pImpl->xStorage;
1792 
1793     if (IsRepairPackage())
1794     {
1795         // the storage should be created for repairing mode
1796         CreateTempFile( false );
1797         GetMedium_Impl();
1798 
1799         Reference< css::ucb::XProgressHandler > xProgressHandler;
1800         Reference< css::task::XStatusIndicator > xStatusIndicator;
1801 
1802         const SfxUnoAnyItem* pxProgressItem = GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL, false);
1803         if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) )
1804             xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) );
1805 
1806         uno::Sequence< beans::PropertyValue > aAddProps{
1807             comphelper::makePropertyValue(u"RepairPackage"_ustr, true),
1808             comphelper::makePropertyValue(u"StatusIndicator"_ustr, xProgressHandler)
1809         };
1810 
1811         // the first arguments will be filled later
1812         aArgs.realloc( 3 );
1813         pArgs = aArgs.getArray();
1814         pArgs[2] <<= aAddProps;
1815     }
1816 
1817     if ( pImpl->xStream.is() )
1818     {
1819         // since the storage is based on temporary stream we open it always read-write
1820         pArgs[0] <<= pImpl->xStream;
1821         pArgs[1] <<= embed::ElementModes::READWRITE;
1822         pImpl->bStorageBasedOnInStream = true;
1823         if (pImpl->m_bDisableFileSync)
1824         {
1825             // Forward NoFileSync to the storage factory.
1826             aArgs.realloc(3); // ??? this may re-write the data added above for pRepairItem
1827             pArgs = aArgs.getArray();
1828             uno::Sequence<beans::PropertyValue> aProperties(
1829                 comphelper::InitPropertySequence({ { "NoFileSync", uno::Any(true) } }));
1830             pArgs[2] <<= aProperties;
1831         }
1832     }
1833     else if ( pImpl->xInputStream.is() )
1834     {
1835         // since the storage is based on temporary stream we open it always read-write
1836         pArgs[0] <<= pImpl->xInputStream;
1837         pArgs[1] <<= embed::ElementModes::READ;
1838         pImpl->bStorageBasedOnInStream = true;
1839     }
1840     else
1841     {
1842         CloseStreams_Impl();
1843         pArgs[0] <<= pImpl->m_aName;
1844         pArgs[1] <<= embed::ElementModes::READ;
1845         pImpl->bStorageBasedOnInStream = false;
1846     }
1847 
1848     try
1849     {
1850         pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
1851                             uno::UNO_QUERY );
1852     }
1853     catch( const uno::Exception& )
1854     {
1855         // impossibility to create the storage is no error
1856     }
1857 
1858     pImpl->nLastStorageError = GetErrorIgnoreWarning();
1859     if( pImpl->nLastStorageError != ERRCODE_NONE )
1860     {
1861         pImpl->xStorage = nullptr;
1862         if ( pImpl->m_pInStream )
1863             pImpl->m_pInStream->Seek(0);
1864         return uno::Reference< embed::XStorage >();
1865     }
1866 
1867     pImpl->m_bTriedStorage = true;
1868 
1869     if (pImpl->xStorage.is())
1870     {
1871         pImpl->m_bODFWholesomeEncryption = false;
1872         if (SetEncryptionDataToStorage_Impl())
1873         {
1874             try
1875             {
1876                 TryEncryptedInnerPackage(pImpl->xStorage);
1877             }
1878             catch (Exception const&)
1879             {
1880                 TOOLS_WARN_EXCEPTION("sfx.doc", "exception from TryEncryptedInnerPackage: ");
1881                 SetError(ERRCODE_IO_GENERAL);
1882             }
1883         }
1884     }
1885 
1886     if (GetErrorCode()) // decryption failed?
1887     {
1888         pImpl->xStorage.clear();
1889     }
1890 
1891     // TODO/LATER: Get versionlist on demand
1892     if ( pImpl->xStorage.is() )
1893     {
1894         GetVersionList();
1895     }
1896 
1897     const SfxInt16Item* pVersion = SfxItemSet::GetItem<SfxInt16Item>(pImpl->m_pSet.get(), SID_VERSION, false);
1898 
1899     bool bResetStorage = false;
1900     if ( pVersion && pVersion->GetValue() )
1901     {
1902         // Read all available versions
1903         if ( pImpl->aVersions.hasElements() )
1904         {
1905             // Search for the version fits the comment
1906             // The versions are numbered starting with 1, versions with
1907             // negative versions numbers are counted backwards from the
1908             // current version
1909             short nVersion = pVersion->GetValue();
1910             if ( nVersion<0 )
1911                 nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion;
1912             else // nVersion > 0; pVersion->GetValue() != 0 was the condition to this block
1913                 nVersion--;
1914 
1915             const util::RevisionTag& rTag = pImpl->aVersions[nVersion];
1916             {
1917                 // Open SubStorage for all versions
1918                 uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( u"Versions"_ustr,
1919                         embed::ElementModes::READ );
1920 
1921                 DBG_ASSERT( xSub.is(), "Version list, but no Versions!" );
1922 
1923                 // There the version is stored as packed Stream
1924                 uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ );
1925                 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr ));
1926                 if ( pStream && pStream->GetError() == ERRCODE_NONE )
1927                 {
1928                     // Unpack Stream  in TempDir
1929                     const OUString aTmpName = ::utl::CreateTempURL();
1930                     SvFileStream    aTmpStream( aTmpName, SFX_STREAM_READWRITE );
1931 
1932                     pStream->ReadStream( aTmpStream );
1933                     pStream.reset();
1934                     aTmpStream.Close();
1935 
1936                     // Open data as Storage
1937                     pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
1938                     pImpl->xStorage = comphelper::OStorageHelper::GetStorageFromURL( aTmpName, embed::ElementModes::READ );
1939                     pImpl->bStorageBasedOnInStream = false;
1940                     OUString aTemp;
1941                     osl::FileBase::getSystemPathFromFileURL( aTmpName, aTemp );
1942                     SetPhysicalName_Impl( aTemp );
1943 
1944                     pImpl->bIsTemp = true;
1945                     GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
1946                     // TODO/MBA
1947                     pImpl->aVersions.realloc(0);
1948                 }
1949                 else
1950                     bResetStorage = true;
1951             }
1952         }
1953         else
1954             bResetStorage = true;
1955     }
1956 
1957     if ( bResetStorage )
1958     {
1959         pImpl->xStorage.clear();
1960         pImpl->m_xODFDecryptedInnerPackageStream.clear();
1961         pImpl->m_xODFEncryptedOuterStorage.clear();
1962         if ( pImpl->m_pInStream )
1963             pImpl->m_pInStream->Seek( 0 );
1964     }
1965 
1966     pImpl->bIsStorage = pImpl->xStorage.is();
1967     return pImpl->xStorage;
1968 }
1969 
GetScriptingStorageToSign_Impl()1970 uno::Reference<embed::XStorage> SfxMedium::GetScriptingStorageToSign_Impl()
1971 {
1972     // this was set when it was initially loaded
1973     if (pImpl->m_bODFWholesomeEncryption)
1974     {
1975         // (partial) scripting signature can only be in inner storage!
1976         // Note: a "PackageFormat" storage like pImpl->xStorage doesn't work
1977         // (even if it's not encrypted) because it hides the "META-INF" dir.
1978         // This "ZipFormat" storage is used only read-only; a writable one is
1979         // created manually in SignContents_Impl().
1980         if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
1981         {
1982             GetStorage(false);
1983             // don't care about xStorage here because Zip is readonly
1984             SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?");
1985             if (pImpl->m_xODFDecryptedInnerPackageStream.is())
1986             {
1987                 pImpl->m_xODFDecryptedInnerZipStorage =
1988                     ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
1989                         ZIP_STORAGE_FORMAT_STRING,
1990                         pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), {},
1991                         IsRepairPackage());
1992             }
1993         }
1994         return pImpl->m_xODFDecryptedInnerZipStorage;
1995     }
1996     else
1997     {
1998         return GetZipStorageToSign_Impl(true);
1999     }
2000 }
2001 
2002 // note: currently nobody who calls this with "false" writes into an ODF
2003 // storage that is returned here, that is only for OOXML
GetZipStorageToSign_Impl(bool bReadOnly)2004 uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
2005 {
2006     if ( !GetErrorIgnoreWarning() && !pImpl->m_xZipStorage.is() )
2007     {
2008         GetMedium_Impl();
2009 
2010         try
2011         {
2012             // we can not sign document if there is no stream
2013             // should it be possible at all?
2014             if ( !bReadOnly && pImpl->xStream.is() )
2015             {
2016                 pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
2017                     ZIP_STORAGE_FORMAT_STRING, pImpl->xStream, css::embed::ElementModes::READWRITE,
2018                     {}, IsRepairPackage());
2019             }
2020             else if ( pImpl->xInputStream.is() )
2021             {
2022                 pImpl->m_xZipStorage
2023                     = ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
2024                         ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream, {}, IsRepairPackage());
2025             }
2026         }
2027         catch( const uno::Exception& )
2028         {
2029             SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
2030         }
2031 
2032         if ( GetErrorIgnoreWarning() ) // do not remove warnings
2033             ResetError();
2034     }
2035 
2036     return pImpl->m_xZipStorage;
2037 }
2038 
2039 
CloseZipStorage_Impl()2040 void SfxMedium::CloseZipStorage_Impl()
2041 {
2042     if ( pImpl->m_xZipStorage.is() )
2043     {
2044         try {
2045             pImpl->m_xZipStorage->dispose();
2046         } catch( const uno::Exception& )
2047         {}
2048 
2049         pImpl->m_xZipStorage.clear();
2050     }
2051     pImpl->m_xODFDecryptedInnerZipStorage.clear();
2052 }
2053 
CloseStorage()2054 void SfxMedium::CloseStorage()
2055 {
2056     if ( pImpl->xStorage.is() )
2057     {
2058         uno::Reference < lang::XComponent > xComp = pImpl->xStorage;
2059         // in the salvage mode the medium does not own the storage
2060         if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode )
2061         {
2062             try {
2063                 xComp->dispose();
2064             } catch( const uno::Exception& )
2065             {
2066                 SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" );
2067             }
2068         }
2069 
2070         pImpl->xStorage.clear();
2071         pImpl->m_xODFDecryptedInnerPackageStream.clear();
2072 //        pImpl->m_xODFDecryptedInnerZipStorage.clear();
2073         pImpl->m_xODFEncryptedOuterStorage.clear();
2074         pImpl->bStorageBasedOnInStream = false;
2075     }
2076 
2077     pImpl->m_bTriedStorage = false;
2078     pImpl->bIsStorage = false;
2079 }
2080 
CanDisposeStorage_Impl(bool bDisposeStorage)2081 void SfxMedium::CanDisposeStorage_Impl( bool bDisposeStorage )
2082 {
2083     pImpl->bDisposeStorage = bDisposeStorage;
2084 }
2085 
WillDisposeStorageOnClose_Impl()2086 bool SfxMedium::WillDisposeStorageOnClose_Impl()
2087 {
2088     return pImpl->bDisposeStorage;
2089 }
2090 
GetOpenMode() const2091 StreamMode SfxMedium::GetOpenMode() const
2092 {
2093     return pImpl->m_nStorOpenMode;
2094 }
2095 
SetOpenMode(StreamMode nStorOpen,bool bDontClose)2096 void SfxMedium::SetOpenMode( StreamMode nStorOpen,
2097                              bool bDontClose )
2098 {
2099     if ( pImpl->m_nStorOpenMode != nStorOpen )
2100     {
2101         pImpl->m_nStorOpenMode = nStorOpen;
2102 
2103         if( !bDontClose )
2104         {
2105             if ( pImpl->xStorage.is() )
2106                 CloseStorage();
2107 
2108             CloseStreams_Impl();
2109         }
2110     }
2111 }
2112 
2113 
UseBackupToRestore_Impl(::ucbhelper::Content & aOriginalContent,const Reference<css::ucb::XCommandEnvironment> & xComEnv)2114 bool SfxMedium::UseBackupToRestore_Impl( ::ucbhelper::Content& aOriginalContent,
2115                                             const Reference< css::ucb::XCommandEnvironment >& xComEnv )
2116 {
2117     try
2118     {
2119         ::ucbhelper::Content aTransactCont( pImpl->m_aBackupURL, xComEnv, comphelper::getProcessComponentContext() );
2120 
2121         Reference< XInputStream > aOrigInput = aTransactCont.openStream();
2122         aOriginalContent.writeStream( aOrigInput, true );
2123         return true;
2124     }
2125     catch( const Exception& )
2126     {
2127         // in case of failure here the backup file should not be removed
2128         // TODO/LATER: a message should be used to let user know about the backup
2129         pImpl->m_bRemoveBackup = false;
2130         // TODO/LATER: needs a specific error code
2131         pImpl->m_eError = ERRCODE_IO_GENERAL;
2132     }
2133 
2134     return false;
2135 }
2136 
2137 
StorageCommit_Impl()2138 bool SfxMedium::StorageCommit_Impl()
2139 {
2140     bool bResult = false;
2141     Reference< css::ucb::XCommandEnvironment > xDummyEnv;
2142     ::ucbhelper::Content aOriginalContent;
2143 
2144     if ( pImpl->xStorage.is() )
2145     {
2146         if ( !GetErrorIgnoreWarning() )
2147         {
2148             uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY );
2149             if ( xTrans.is() )
2150             {
2151                 try
2152                 {
2153                     xTrans->commit();
2154                     CloseZipStorage_Impl();
2155                     bResult = true;
2156                 }
2157                 catch ( const embed::UseBackupException& aBackupExc )
2158                 {
2159                     // since the temporary file is created always now, the scenario is close to be impossible
2160                     if ( !pImpl->pTempFile )
2161                     {
2162                         OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" );
2163                         if ( !pImpl->m_aBackupURL.isEmpty()
2164                             && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
2165                                                         xDummyEnv, comphelper::getProcessComponentContext(),
2166                                                         aOriginalContent ) )
2167                         {
2168                             // use backup to restore the file
2169                             // the storage has already disconnected from original location
2170                             CloseAndReleaseStreams_Impl();
2171                             if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) )
2172                             {
2173                                 // connect the medium to the temporary file of the storage
2174                                 pImpl->aContent = ::ucbhelper::Content();
2175                                 pImpl->m_aName = aBackupExc.TemporaryFileURL;
2176                                 OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" );
2177                             }
2178                         }
2179                     }
2180 
2181                     if (!GetErrorIgnoreWarning())
2182                         SetError(ERRCODE_IO_GENERAL);
2183                 }
2184                 catch ( const uno::Exception& )
2185                 {
2186                     //TODO/LATER: improve error handling
2187                     SetError(ERRCODE_IO_GENERAL);
2188                 }
2189             }
2190         }
2191     }
2192 
2193     return bResult;
2194 }
2195 
2196 
TransactedTransferForFS_Impl(const INetURLObject & aSource,const INetURLObject & aDest,const Reference<css::ucb::XCommandEnvironment> & xComEnv)2197 void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource,
2198                                                  const INetURLObject& aDest,
2199                                                  const Reference< css::ucb::XCommandEnvironment >& xComEnv )
2200 {
2201     Reference< css::ucb::XCommandEnvironment > xDummyEnv;
2202     ::ucbhelper::Content aOriginalContent;
2203 
2204     try
2205     {
2206         aOriginalContent = ::ucbhelper::Content( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
2207     }
2208     catch ( const css::ucb::CommandAbortedException& )
2209     {
2210         pImpl->m_eError = ERRCODE_ABORT;
2211     }
2212     catch ( const css::ucb::CommandFailedException& )
2213     {
2214         pImpl->m_eError = ERRCODE_ABORT;
2215     }
2216     catch (const css::ucb::ContentCreationException& ex)
2217     {
2218         pImpl->m_eError = ERRCODE_IO_GENERAL;
2219         if (
2220             (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER    ) ||
2221             (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
2222            )
2223         {
2224             pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
2225         }
2226     }
2227     catch (const css::uno::Exception&)
2228     {
2229        pImpl->m_eError = ERRCODE_IO_GENERAL;
2230     }
2231 
2232     if( pImpl->m_eError && !pImpl->m_eError.IsWarning() )
2233         return;
2234 
2235     if ( pImpl->xStorage.is() )
2236         CloseStorage();
2237 
2238     CloseStreams_Impl();
2239 
2240     ::ucbhelper::Content aTempCont;
2241     if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aTempCont ) )
2242     {
2243         bool bTransactStarted = false;
2244         const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2245         bool bOverWrite = !pOverWrite || pOverWrite->GetValue();
2246         bool bResult = false;
2247 
2248         try
2249         {
2250             // tdf#60237 - if the OverWrite property of the MediaDescriptor is set to false,
2251             // try to write the file before trying to rename or copy it
2252             if (!(bOverWrite
2253                   && ::utl::UCBContentHelper::IsDocument(
2254                       aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE))))
2255             {
2256                 Reference< XInputStream > aTempInput = aTempCont.openStream();
2257                 aOriginalContent.writeStream( aTempInput, bOverWrite );
2258                 bResult = true;
2259             } else {
2260                 OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2261                 OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);
2262 
2263                 sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL);
2264                 if (IsFileMovable(aDest)
2265                     && osl::File::replace(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
2266                 {
2267                     if (nAttributes)
2268                         // Adjust attributes, source might be created with
2269                         // the osl_File_OpenFlag_Private flag.
2270                         osl::File::setAttributes(aDestMainURL, nAttributes);
2271                     bResult = true;
2272                 }
2273                 else
2274                 {
2275                     if( pImpl->m_aBackupURL.isEmpty() )
2276                         DoInternalBackup_Impl( aOriginalContent );
2277 
2278                     if( !pImpl->m_aBackupURL.isEmpty() )
2279                     {
2280                         Reference< XInputStream > aTempInput = aTempCont.openStream();
2281                         bTransactStarted = true;
2282                         aOriginalContent.setPropertyValue( u"Size"_ustr, uno::Any( sal_Int64(0) ) );
2283                         aOriginalContent.writeStream( aTempInput, bOverWrite );
2284                         bResult = true;
2285                     }
2286                     else
2287                     {
2288                         pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
2289                     }
2290                 }
2291             }
2292         }
2293         catch ( const css::ucb::CommandAbortedException& )
2294         {
2295             pImpl->m_eError = ERRCODE_ABORT;
2296         }
2297         catch ( const css::ucb::CommandFailedException& )
2298         {
2299             pImpl->m_eError = ERRCODE_ABORT;
2300         }
2301         catch ( const css::ucb::InteractiveIOException& r )
2302         {
2303             if ( r.Code == IOErrorCode_ACCESS_DENIED )
2304                 pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
2305             else if ( r.Code == IOErrorCode_NOT_EXISTING )
2306                 pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2307             else if ( r.Code == IOErrorCode_CANT_READ )
2308                 pImpl->m_eError = ERRCODE_IO_CANTREAD;
2309             else
2310                 pImpl->m_eError = ERRCODE_IO_GENERAL;
2311         }
2312         // tdf#60237 - if the file is already present, raise the appropriate error
2313         catch (const css::ucb::NameClashException& )
2314         {
2315             pImpl->m_eError = ERRCODE_IO_ALREADYEXISTS;
2316         }
2317         catch ( const css::uno::Exception& )
2318         {
2319             pImpl->m_eError = ERRCODE_IO_GENERAL;
2320         }
2321 
2322         if ( bResult )
2323         {
2324             if ( pImpl->pTempFile )
2325             {
2326                 pImpl->pTempFile->EnableKillingFile();
2327                 pImpl->pTempFile.reset();
2328             }
2329         }
2330         else if ( bTransactStarted && pImpl->m_eError != ERRCODE_ABORT )
2331         {
2332             UseBackupToRestore_Impl( aOriginalContent, xDummyEnv );
2333         }
2334     }
2335     else
2336         pImpl->m_eError = ERRCODE_IO_CANTREAD;
2337 }
2338 
2339 
TryDirectTransfer(const OUString & aURL,SfxItemSet const & aTargetSet)2340 bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet )
2341 {
2342     if ( GetErrorIgnoreWarning() )
2343         return false;
2344 
2345     // if the document had no password it should be stored without password
2346     // if the document had password it should be stored with the same password
2347     // otherwise the stream copying can not be done
2348     const SfxStringItem* pNewPassItem = aTargetSet.GetItem(SID_PASSWORD, false);
2349     const SfxStringItem* pOldPassItem = GetItemSet().GetItem(SID_PASSWORD, false);
2350     if ( ( !pNewPassItem && !pOldPassItem )
2351       || ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
2352     {
2353         // the filter must be the same
2354         const SfxStringItem* pNewFilterItem = aTargetSet.GetItem(SID_FILTER_NAME, false);
2355         const SfxStringItem* pOldFilterItem = GetItemSet().GetItem(SID_FILTER_NAME, false);
2356         if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() )
2357         {
2358             // get the input stream and copy it
2359             // in case of success return true
2360             uno::Reference< io::XInputStream > xInStream = GetInputStream();
2361 
2362             ResetError();
2363             if ( xInStream.is() )
2364             {
2365                 try
2366                 {
2367                     uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY );
2368                     sal_Int64 nPos = 0;
2369                     if ( xSeek.is() )
2370                     {
2371                         nPos = xSeek->getPosition();
2372                         xSeek->seek( 0 );
2373                     }
2374 
2375                     uno::Reference < css::ucb::XCommandEnvironment > xEnv;
2376                     ::ucbhelper::Content aTargetContent( aURL, xEnv, comphelper::getProcessComponentContext() );
2377 
2378                     InsertCommandArgument aInsertArg;
2379                     aInsertArg.Data = xInStream;
2380                     const SfxBoolItem* pOverWrite = aTargetSet.GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2381                     if ( pOverWrite && !pOverWrite->GetValue() ) // argument says: never overwrite
2382                         aInsertArg.ReplaceExisting = false;
2383                     else
2384                         aInsertArg.ReplaceExisting = true; // default is overwrite existing files
2385 
2386                     Any aCmdArg;
2387                     aCmdArg <<= aInsertArg;
2388                     aTargetContent.executeCommand( u"insert"_ustr,
2389                                                     aCmdArg );
2390 
2391                     if ( xSeek.is() )
2392                         xSeek->seek( nPos );
2393 
2394                     return true;
2395                 }
2396                 catch( const uno::Exception& )
2397                 {}
2398             }
2399         }
2400     }
2401 
2402     return false;
2403 }
2404 
2405 
Transfer_Impl()2406 void SfxMedium::Transfer_Impl()
2407 {
2408     // The transfer is required only in two cases: either if there is a temporary file or if there is a salvage item
2409     OUString aNameURL;
2410     if ( pImpl->pTempFile )
2411         aNameURL = pImpl->pTempFile->GetURL();
2412     else if ( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode )
2413     {
2414         // makes sense only in case logic name is set
2415         if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aNameURL )
2416              != osl::FileBase::E_None )
2417             SAL_WARN( "sfx.doc", "The medium name is not convertible!" );
2418     }
2419 
2420     if ( aNameURL.isEmpty() || ( pImpl->m_eError && !pImpl->m_eError.IsWarning() ) )
2421         return;
2422 
2423     SAL_INFO( "sfx.doc", "SfxMedium::Transfer_Impl, copying to target" );
2424 
2425     Reference < css::ucb::XCommandEnvironment > xEnv;
2426     Reference< XOutputStream > rOutStream;
2427 
2428     // in case an output stream is provided from outside and the URL is correct
2429     // commit to the stream
2430     if (pImpl->m_aLogicName.startsWith("private:stream"))
2431     {
2432         // TODO/LATER: support storing to SID_STREAM
2433         const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
2434         if( pOutStreamItem && ( pOutStreamItem->GetValue() >>= rOutStream ) )
2435         {
2436             if ( pImpl->xStorage.is() )
2437                 CloseStorage();
2438 
2439             CloseStreams_Impl();
2440 
2441             INetURLObject aSource( aNameURL );
2442             ::ucbhelper::Content aTempCont;
2443             if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aTempCont ) )
2444             {
2445                 try
2446                 {
2447                     sal_Int32 nRead;
2448                     sal_Int32 nBufferSize = 32767;
2449                     Sequence < sal_Int8 > aSequence ( nBufferSize );
2450                     Reference< XInputStream > aTempInput = aTempCont.openStream();
2451 
2452                     do
2453                     {
2454                         nRead = aTempInput->readBytes ( aSequence, nBufferSize );
2455                         if ( nRead < nBufferSize )
2456                         {
2457                             Sequence < sal_Int8 > aTempBuf ( aSequence.getConstArray(), nRead );
2458                             rOutStream->writeBytes ( aTempBuf );
2459                         }
2460                         else
2461                             rOutStream->writeBytes ( aSequence );
2462                     }
2463                     while ( nRead == nBufferSize );
2464 
2465                     // remove temporary file
2466                     if ( pImpl->pTempFile )
2467                     {
2468                         pImpl->pTempFile->EnableKillingFile();
2469                         pImpl->pTempFile.reset();
2470                     }
2471                 }
2472                 catch( const Exception& )
2473                 {}
2474             }
2475         }
2476         else
2477         {
2478             SAL_WARN( "sfx.doc", "Illegal Output stream parameter!" );
2479             SetError(ERRCODE_IO_GENERAL);
2480         }
2481 
2482         // free the reference
2483         if ( pImpl->m_pSet )
2484             pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
2485 
2486         return;
2487     }
2488 
2489     GetContent();
2490     if ( !pImpl->aContent.get().is() )
2491     {
2492         pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2493         return;
2494     }
2495 
2496     INetURLObject aDest( GetURLObject() );
2497 
2498     // source is the temp file written so far
2499     INetURLObject aSource( aNameURL );
2500 
2501     // a special case, an interaction handler should be used for
2502     // authentication in case it is available
2503     Reference< css::ucb::XCommandEnvironment > xComEnv;
2504     bool bForceInteractionHandler = GetURLObject().isAnyKnownWebDAVScheme();
2505     Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(bForceInteractionHandler);
2506     if (xInteractionHandler.is())
2507         xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler,
2508                                                   Reference< css::ucb::XProgressHandler >() );
2509 
2510     OUString aDestURL( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2511 
2512     if ( comphelper::isFileUrl( aDestURL ) || !aDest.removeSegment() )
2513     {
2514         TransactedTransferForFS_Impl( aSource, aDest, xComEnv );
2515 
2516         if (!pImpl->m_bDisableFileSync)
2517         {
2518             // Hideous - no clean way to do this, so we re-open the file just to fsync it
2519             osl::File aFile( aDestURL );
2520             if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None )
2521             {
2522                 aFile.sync();
2523                 SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" );
2524                 aFile.close();
2525             }
2526         }
2527     }
2528     else
2529     {
2530         // create content for the parent folder and call transfer on that content with the source content
2531         // and the destination file name as parameters
2532         ::ucbhelper::Content aSourceContent;
2533         ::ucbhelper::Content aTransferContent;
2534 
2535         ::ucbhelper::Content aDestContent;
2536         (void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent );
2537         // For checkin, we need the object URL, not the parent folder:
2538         if ( !IsInCheckIn( ) )
2539         {
2540             // Get the parent URL from the XChild if possible: why would the URL necessarily have
2541             // a hierarchical path? It's not always the case for CMIS.
2542             Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY );
2543             OUString sParentUrl;
2544             if ( xChild.is( ) )
2545             {
2546                 Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY );
2547                 if ( xParent.is( ) )
2548                 {
2549                     sParentUrl = xParent->getIdentifier( )->getContentIdentifier();
2550                 }
2551             }
2552 
2553             if ( sParentUrl.isEmpty() )
2554                 aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2555                     // adjust to above aDest.removeSegment()
2556             else
2557                 aDestURL = sParentUrl;
2558         }
2559 
2560         // LongName wasn't defined anywhere, only used here... get the Title instead
2561         // as it's less probably empty
2562         OUString aFileName;
2563         OUString sObjectId;
2564         try
2565         {
2566             Any aAny = aDestContent.getPropertyValue(u"Title"_ustr);
2567             aAny >>= aFileName;
2568             aAny = aDestContent.getPropertyValue(u"ObjectId"_ustr);
2569             aAny >>= sObjectId;
2570         }
2571         catch (uno::Exception const&)
2572         {
2573             SAL_INFO("sfx.doc", "exception while getting Title or ObjectId");
2574         }
2575         if ( aFileName.isEmpty() )
2576             aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2577 
2578         try
2579         {
2580             aTransferContent = ::ucbhelper::Content( aDestURL, xComEnv, comphelper::getProcessComponentContext() );
2581         }
2582         catch (const css::ucb::ContentCreationException& ex)
2583         {
2584             pImpl->m_eError = ERRCODE_IO_GENERAL;
2585             if (
2586                 (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER    ) ||
2587                 (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
2588                )
2589             {
2590                 pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
2591             }
2592         }
2593         catch (const css::uno::Exception&)
2594         {
2595             pImpl->m_eError = ERRCODE_IO_GENERAL;
2596         }
2597 
2598         if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() )
2599         {
2600             // free resources, otherwise the transfer may fail
2601             if ( pImpl->xStorage.is() )
2602                 CloseStorage();
2603 
2604             CloseStreams_Impl();
2605 
2606             (void)::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent );
2607 
2608             // check for external parameters that may customize the handling of NameClash situations
2609             const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
2610             sal_Int32 nNameClash;
2611             if ( pOverWrite && !pOverWrite->GetValue() )
2612                 // argument says: never overwrite
2613                 nNameClash = NameClash::ERROR;
2614             else
2615                 // default is overwrite existing files
2616                 nNameClash = NameClash::OVERWRITE;
2617 
2618             try
2619             {
2620                 OUString aMimeType = pImpl->getFilterMimeType();
2621                 ::ucbhelper::InsertOperation eOperation = ::ucbhelper::InsertOperation::Copy;
2622                 bool bMajor = false;
2623                 OUString sComment;
2624                 if ( IsInCheckIn( ) )
2625                 {
2626                     eOperation = ::ucbhelper::InsertOperation::Checkin;
2627                     const SfxBoolItem* pMajor = GetItemSet().GetItem<SfxBoolItem>(SID_DOCINFO_MAJOR, false);
2628                     bMajor = pMajor && pMajor->GetValue( );
2629                     const SfxStringItem* pComments = GetItemSet().GetItem(SID_DOCINFO_COMMENTS, false);
2630                     if ( pComments )
2631                         sComment = pComments->GetValue( );
2632                 }
2633                 OUString sResultURL;
2634                 aTransferContent.transferContent(
2635                     aSourceContent, eOperation,
2636                     aFileName, nNameClash, aMimeType, bMajor, sComment,
2637                     &sResultURL, sObjectId );
2638 
2639                 if ( !sResultURL.isEmpty( ) )  // Likely to happen only for checkin
2640                     SwitchDocumentToFile( sResultURL );
2641                 try
2642                 {
2643                     if ( GetURLObject().isAnyKnownWebDAVScheme() &&
2644                          eOperation == ::ucbhelper::InsertOperation::Copy )
2645                     {
2646                         // tdf#95272 try to re-issue a lock command when a new file is created.
2647                         // This may be needed because some WebDAV servers fail to implement the
2648                         // 'LOCK on unallocated reference', see issue comment:
2649                         // <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8>
2650                         // and specification at:
2651                         // <http://tools.ietf.org/html/rfc4918#section-7.3>
2652                         // If the WebDAV resource is already locked by this LO instance, nothing will
2653                         // happen, e.g. the LOCK method will not be sent to the server.
2654                         ::ucbhelper::Content aLockContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
2655                         aLockContent.lock();
2656                     }
2657                 }
2658                 catch ( css::uno::Exception & )
2659                 {
2660                     TOOLS_WARN_EXCEPTION( "sfx.doc", "LOCK not working while re-issuing it" );
2661                 }
2662             }
2663             catch ( const css::ucb::CommandAbortedException& )
2664             {
2665                 pImpl->m_eError = ERRCODE_ABORT;
2666             }
2667             catch ( const css::ucb::CommandFailedException& )
2668             {
2669                 pImpl->m_eError = ERRCODE_ABORT;
2670             }
2671             catch ( const css::ucb::InteractiveIOException& r )
2672             {
2673                 if ( r.Code == IOErrorCode_ACCESS_DENIED )
2674                     pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
2675                 else if ( r.Code == IOErrorCode_NOT_EXISTING )
2676                     pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
2677                 else if ( r.Code == IOErrorCode_CANT_READ )
2678                     pImpl->m_eError = ERRCODE_IO_CANTREAD;
2679                 else
2680                     pImpl->m_eError = ERRCODE_IO_GENERAL;
2681             }
2682             catch ( const css::uno::Exception& )
2683             {
2684                 pImpl->m_eError = ERRCODE_IO_GENERAL;
2685             }
2686 
2687             // do not switch from temporary file in case of nonfile protocol
2688         }
2689     }
2690 
2691     if ( ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) && !pImpl->pTempFile )
2692     {
2693         // without a TempFile the physical and logical name should be the same after successful transfer
2694         if (osl::FileBase::getSystemPathFromFileURL(
2695               GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName )
2696             != osl::FileBase::E_None)
2697         {
2698             pImpl->m_aName.clear();
2699         }
2700         pImpl->m_bSalvageMode = false;
2701     }
2702 }
2703 
2704 
DoInternalBackup_Impl(const::ucbhelper::Content & aOriginalContent,std::u16string_view aPrefix,std::u16string_view aExtension,const OUString & aDestDir)2705 void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent,
2706                                        std::u16string_view aPrefix,
2707                                        std::u16string_view aExtension,
2708                                        const OUString& aDestDir )
2709 {
2710     if ( !pImpl->m_aBackupURL.isEmpty() )
2711         return; // the backup was done already
2712 
2713     ::utl::TempFileNamed aTransactTemp( aPrefix, true, aExtension, &aDestDir );
2714 
2715     INetURLObject aBackObj( aTransactTemp.GetURL() );
2716     OUString aBackupName = aBackObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2717 
2718     Reference < css::ucb::XCommandEnvironment > xDummyEnv;
2719     ::ucbhelper::Content aBackupCont;
2720     if( ::ucbhelper::Content::create( aDestDir, xDummyEnv, comphelper::getProcessComponentContext(), aBackupCont ) )
2721     {
2722         try
2723         {
2724             OUString sMimeType = pImpl->getFilterMimeType();
2725             aBackupCont.transferContent( aOriginalContent,
2726                                             ::ucbhelper::InsertOperation::Copy,
2727                                             aBackupName,
2728                                             NameClash::OVERWRITE,
2729                                             sMimeType );
2730             pImpl->m_aBackupURL = aBackObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2731             pImpl->m_bRemoveBackup = true;
2732         }
2733         catch( const Exception& )
2734         {}
2735     }
2736 
2737     if ( pImpl->m_aBackupURL.isEmpty() )
2738         aTransactTemp.EnableKillingFile();
2739 }
2740 
2741 
DoInternalBackup_Impl(const::ucbhelper::Content & aOriginalContent)2742 void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent )
2743 {
2744     if ( !pImpl->m_aBackupURL.isEmpty() )
2745         return; // the backup was done already
2746 
2747     OUString aFileName =  GetURLObject().getName( INetURLObject::LAST_SEGMENT,
2748                                                         true,
2749                                                         INetURLObject::DecodeMechanism::NONE );
2750 
2751     sal_Int32 nPrefixLen = aFileName.lastIndexOf( '.' );
2752     OUString aPrefix = ( nPrefixLen == -1 ) ? aFileName : aFileName.copy( 0, nPrefixLen );
2753     OUString aExtension = ( nPrefixLen == -1 ) ? OUString() : aFileName.copy( nPrefixLen );
2754     OUString aBakDir = SvtPathOptions().GetBackupPath();
2755 
2756     // create content for the parent folder ( = backup folder )
2757     ::ucbhelper::Content  aContent;
2758     Reference < css::ucb::XCommandEnvironment > xEnv;
2759     if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
2760         DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aBakDir );
2761 
2762     if ( !pImpl->m_aBackupURL.isEmpty() )
2763         return;
2764 
2765     // the copying to the backup catalog failed ( for example because
2766     // of using an encrypted partition as target catalog )
2767     // since the user did not specify to make backup explicitly
2768     // office should try to make backup in another place,
2769     // target catalog does not look bad for this case ( and looks
2770     // to be the only way for encrypted partitions )
2771 
2772     INetURLObject aDest = GetURLObject();
2773     if ( aDest.removeSegment() )
2774         DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2775 }
2776 
2777 
DoBackup_Impl(bool bForceUsingBackupPath)2778 void SfxMedium::DoBackup_Impl(bool bForceUsingBackupPath)
2779 {
2780     // source file name is the logical name of this medium
2781     INetURLObject aSource( GetURLObject() );
2782 
2783     // there is nothing to backup in case source file does not exist
2784     if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
2785         return;
2786 
2787     bool        bSuccess = false;
2788     bool bOnErrorRetryUsingBackupPath = false;
2789 
2790     // get path for backups
2791     OUString aBakDir;
2792     if (!bForceUsingBackupPath
2793         && officecfg::Office::Common::Save::Document::BackupIntoDocumentFolder::get())
2794     {
2795         aBakDir = aSource.GetPartBeforeLastName();
2796         bOnErrorRetryUsingBackupPath = true;
2797     }
2798     else
2799         aBakDir = SvtPathOptions().GetBackupPath();
2800     if( !aBakDir.isEmpty() )
2801     {
2802         // create content for the parent folder ( = backup folder )
2803         ::ucbhelper::Content  aContent;
2804         Reference < css::ucb::XCommandEnvironment > xEnv;
2805         if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
2806         {
2807             // save as ".bak" file
2808             INetURLObject aDest( aBakDir );
2809             aDest.insertName( aSource.getName() );
2810             const OUString sExt
2811                 = aSource.hasExtension() ? aSource.getExtension() + ".bak" : u"bak"_ustr;
2812             aDest.setExtension(sExt);
2813             OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
2814 
2815             // create a content for the source file
2816             ::ucbhelper::Content aSourceContent;
2817             if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
2818             {
2819                 try
2820                 {
2821                     // do the transfer ( copy source file to backup dir )
2822                     OUString sMimeType = pImpl->getFilterMimeType();
2823                     aContent.transferContent( aSourceContent,
2824                                                         ::ucbhelper::InsertOperation::Copy,
2825                                                         aFileName,
2826                                                         NameClash::OVERWRITE,
2827                                                         sMimeType );
2828                     pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2829                     pImpl->m_bRemoveBackup = false;
2830                     bSuccess = true;
2831                 }
2832                 catch ( const css::uno::Exception& )
2833                 {
2834                 }
2835             }
2836         }
2837     }
2838 
2839     if ( !bSuccess )
2840     {
2841         // in case a webdav server prevents file creation, or a partition is full, or whatever...
2842         if (bOnErrorRetryUsingBackupPath)
2843             return DoBackup_Impl(/*bForceUsingBackupPath=*/true);
2844 
2845         pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
2846     }
2847 }
2848 
2849 
ClearBackup_Impl()2850 void SfxMedium::ClearBackup_Impl()
2851 {
2852     if( pImpl->m_bRemoveBackup )
2853     {
2854         // currently a document is always stored in a new medium,
2855         // thus if a backup can not be removed the backup URL should not be cleaned
2856         if ( !pImpl->m_aBackupURL.isEmpty() )
2857         {
2858             if ( ::utl::UCBContentHelper::Kill( pImpl->m_aBackupURL ) )
2859             {
2860                 pImpl->m_bRemoveBackup = false;
2861                 pImpl->m_aBackupURL.clear();
2862             }
2863             else
2864             {
2865 
2866                 SAL_WARN( "sfx.doc", "Couldn't remove backup file!");
2867             }
2868         }
2869     }
2870     else
2871         pImpl->m_aBackupURL.clear();
2872 }
2873 
2874 
GetLockingStream_Impl()2875 void SfxMedium::GetLockingStream_Impl()
2876 {
2877     if ( GetURLObject().GetProtocol() != INetProtocol::File
2878          || pImpl->m_xLockingStream.is() )
2879         return;
2880 
2881     const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
2882     if ( pWriteStreamItem )
2883         pWriteStreamItem->GetValue() >>= pImpl->m_xLockingStream;
2884 
2885     if ( pImpl->m_xLockingStream.is() )
2886         return;
2887 
2888     // open the original document
2889     uno::Sequence< beans::PropertyValue > xProps;
2890     TransformItems( SID_OPENDOC, GetItemSet(), xProps );
2891     utl::MediaDescriptor aMedium( xProps );
2892 
2893     aMedium.addInputStreamOwnLock();
2894 
2895     uno::Reference< io::XInputStream > xInputStream;
2896     aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->m_xLockingStream;
2897     aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream;
2898 
2899     if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() )
2900     {
2901         // the medium is still based on the original file, it makes sense to initialize the streams
2902         if ( pImpl->m_xLockingStream.is() )
2903             pImpl->xStream = pImpl->m_xLockingStream;
2904 
2905         if ( xInputStream.is() )
2906             pImpl->xInputStream = xInputStream;
2907 
2908         if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
2909             pImpl->xInputStream = pImpl->xStream->getInputStream();
2910     }
2911 }
2912 
2913 
GetMedium_Impl()2914 void SfxMedium::GetMedium_Impl()
2915 {
2916     if ( pImpl->m_pInStream
2917         && (!pImpl->bIsTemp || pImpl->xInputStream.is() || pImpl->m_xInputStreamToLoadFrom.is() || pImpl->xStream.is() || pImpl->m_xLockingStream.is() ) )
2918         return;
2919 
2920     pImpl->bDownloadDone = false;
2921     Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler();
2922 
2923     //TODO/MBA: need support for SID_STREAM
2924     const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
2925     const SfxUnoAnyItem* pInStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INPUTSTREAM, false);
2926     if ( pWriteStreamItem )
2927     {
2928         pWriteStreamItem->GetValue() >>= pImpl->xStream;
2929 
2930         if ( pInStreamItem )
2931             pInStreamItem->GetValue() >>= pImpl->xInputStream;
2932 
2933         if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
2934             pImpl->xInputStream = pImpl->xStream->getInputStream();
2935     }
2936     else if ( pInStreamItem )
2937     {
2938         pInStreamItem->GetValue() >>= pImpl->xInputStream;
2939     }
2940     else
2941     {
2942         uno::Sequence < beans::PropertyValue > xProps;
2943         OUString aFileName;
2944         if (!pImpl->m_aName.isEmpty())
2945         {
2946             if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aFileName )
2947                  != osl::FileBase::E_None )
2948             {
2949                 SAL_WARN( "sfx.doc", "Physical name not convertible!");
2950             }
2951         }
2952         else
2953             aFileName = GetName();
2954 
2955         // in case the temporary file exists the streams should be initialized from it,
2956         // but the original MediaDescriptor should not be changed
2957         bool bFromTempFile = ( pImpl->pTempFile != nullptr );
2958 
2959         if ( !bFromTempFile )
2960         {
2961             GetItemSet().Put( SfxStringItem( SID_FILE_NAME, aFileName ) );
2962             if( !(pImpl->m_nStorOpenMode & StreamMode::WRITE) )
2963                 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
2964             if (xInteractionHandler.is())
2965                 GetItemSet().Put( SfxUnoAnyItem( SID_INTERACTIONHANDLER, Any(xInteractionHandler) ) );
2966         }
2967 
2968         if ( pImpl->m_xInputStreamToLoadFrom.is() )
2969         {
2970             pImpl->xInputStream = pImpl->m_xInputStreamToLoadFrom;
2971             if (pImpl->m_bInputStreamIsReadOnly)
2972                 GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
2973         }
2974         else
2975         {
2976             TransformItems( SID_OPENDOC, GetItemSet(), xProps );
2977             utl::MediaDescriptor aMedium( xProps );
2978 
2979             if ( pImpl->m_xLockingStream.is() && !bFromTempFile )
2980             {
2981                 // the medium is not based on the temporary file, so the original stream can be used
2982                 pImpl->xStream = pImpl->m_xLockingStream;
2983             }
2984             else
2985             {
2986                 if ( bFromTempFile )
2987                 {
2988                     aMedium[utl::MediaDescriptor::PROP_URL] <<= aFileName;
2989                     aMedium.erase( utl::MediaDescriptor::PROP_READONLY );
2990                     aMedium.addInputStream();
2991                 }
2992                 else if ( GetURLObject().GetProtocol() == INetProtocol::File )
2993                 {
2994                     // use the special locking approach only for file URLs
2995                     aMedium.addInputStreamOwnLock();
2996                 }
2997                 else
2998                 {
2999                     // add a check for protocol, if it's http or https or provide webdav then add
3000                     // the interaction handler to be used by the authentication dialog
3001                     if ( GetURLObject().isAnyKnownWebDAVScheme() )
3002                     {
3003                         aMedium[utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER] <<= GetInteractionHandler( true );
3004                     }
3005                     aMedium.addInputStream();
3006                 }
3007                 // the ReadOnly property set in aMedium is ignored
3008                 // the check is done in LockOrigFileOnDemand() for file and non-file URLs
3009 
3010                 //TODO/MBA: what happens if property is not there?!
3011                 aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->xStream;
3012                 aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= pImpl->xInputStream;
3013             }
3014 
3015             GetContent();
3016             if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
3017                 pImpl->xInputStream = pImpl->xStream->getInputStream();
3018         }
3019 
3020         if ( !bFromTempFile )
3021         {
3022             //TODO/MBA: need support for SID_STREAM
3023             if ( pImpl->xStream.is() )
3024                 GetItemSet().Put( SfxUnoAnyItem( SID_STREAM, Any( pImpl->xStream ) ) );
3025 
3026             GetItemSet().Put( SfxUnoAnyItem( SID_INPUTSTREAM, Any( pImpl->xInputStream ) ) );
3027         }
3028     }
3029 
3030     //TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor
3031     if ( !GetErrorIgnoreWarning() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
3032         SetError(ERRCODE_IO_ACCESSDENIED);
3033 
3034     if ( !GetErrorIgnoreWarning() && !pImpl->m_pInStream )
3035     {
3036         if ( pImpl->xStream.is() )
3037             pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xStream );
3038         else if ( pImpl->xInputStream.is() )
3039             pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xInputStream );
3040     }
3041 
3042     pImpl->bDownloadDone = true;
3043     pImpl->aDoneLink.ClearPendingCall();
3044     ErrCodeMsg nError = GetErrorIgnoreWarning();
3045     sal_uIntPtr nErrorCode = sal_uInt32(nError.GetCode());
3046     pImpl->aDoneLink.Call( reinterpret_cast<void*>(nErrorCode) );
3047 }
3048 
IsRemote() const3049 bool SfxMedium::IsRemote() const
3050 {
3051     return pImpl->m_bRemote;
3052 }
3053 
SetUpdatePickList(bool bVal)3054 void SfxMedium::SetUpdatePickList(bool bVal)
3055 {
3056     pImpl->bUpdatePickList = bVal;
3057 }
3058 
IsUpdatePickList() const3059 bool SfxMedium::IsUpdatePickList() const
3060 {
3061     return pImpl->bUpdatePickList;
3062 }
3063 
SetLongName(const OUString & rName)3064 void SfxMedium::SetLongName(const OUString &rName)
3065 {
3066     pImpl->m_aLongName = rName;
3067 }
3068 
GetLongName() const3069 const OUString& SfxMedium::GetLongName() const
3070 {
3071     return pImpl->m_aLongName;
3072 }
3073 
SetDoneLink(const Link<void *,void> & rLink)3074 void SfxMedium::SetDoneLink( const Link<void*,void>& rLink )
3075 {
3076     pImpl->aDoneLink = rLink;
3077 }
3078 
Download(const Link<void *,void> & aLink)3079 void SfxMedium::Download( const Link<void*,void>& aLink )
3080 {
3081     SetDoneLink( aLink );
3082     GetInStream();
3083     if ( pImpl->m_pInStream && !aLink.IsSet() )
3084     {
3085         while( !pImpl->bDownloadDone && !Application::IsQuit())
3086             Application::Yield();
3087     }
3088 }
3089 
3090 
3091 /**
3092     Sets m_aLogicName to a valid URL and if available sets
3093     the physical name m_aName to the file name.
3094  */
Init_Impl()3095 void SfxMedium::Init_Impl()
3096 {
3097     Reference< XOutputStream > rOutStream;
3098 
3099     // TODO/LATER: handle lifetime of storages
3100     pImpl->bDisposeStorage = false;
3101 
3102     const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
3103     if ( pSalvageItem && pSalvageItem->GetValue().isEmpty() )
3104     {
3105         pSalvageItem = nullptr;
3106         pImpl->m_pSet->ClearItem( SID_DOC_SALVAGE );
3107     }
3108 
3109     if (!pImpl->m_aLogicName.isEmpty())
3110     {
3111         INetURLObject aUrl( pImpl->m_aLogicName );
3112         INetProtocol eProt = aUrl.GetProtocol();
3113         if ( eProt == INetProtocol::NotValid )
3114         {
3115             SAL_WARN( "sfx.doc", "URL <" << pImpl->m_aLogicName << "> with unknown protocol" );
3116         }
3117         else
3118         {
3119             if ( aUrl.HasMark() )
3120             {
3121                 std::unique_lock<std::recursive_mutex> chkEditLock;
3122                 if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3123                     chkEditLock = std::unique_lock<std::recursive_mutex>(
3124                         *(pImpl->m_pCheckEditableWorkerMutex));
3125                 pImpl->m_aLogicName = aUrl.GetURLNoMark( INetURLObject::DecodeMechanism::NONE );
3126                 if (chkEditLock.owns_lock())
3127                     chkEditLock.unlock();
3128                 GetItemSet().Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() ) );
3129             }
3130 
3131             // try to convert the URL into a physical name - but never change a physical name
3132             // physical name may be set if the logical name is changed after construction
3133             if ( pImpl->m_aName.isEmpty() )
3134                 osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName );
3135             else
3136             {
3137                 DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" );
3138             }
3139         }
3140     }
3141 
3142     if ( pSalvageItem )
3143     {
3144         std::unique_lock<std::recursive_mutex> chkEditLock;
3145         if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3146             chkEditLock
3147                 = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3148         pImpl->m_aLogicName = pSalvageItem->GetValue();
3149         pImpl->m_pURLObj.reset();
3150         if (chkEditLock.owns_lock())
3151             chkEditLock.unlock();
3152         pImpl->m_bSalvageMode = true;
3153     }
3154 
3155     // in case output stream is by mistake here
3156     // clear the reference
3157     const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
3158     if( pOutStreamItem
3159      && ( !( pOutStreamItem->GetValue() >>= rOutStream )
3160           || !pImpl->m_aLogicName.startsWith("private:stream")) )
3161     {
3162         pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
3163         SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" );
3164     }
3165 
3166     if (!pImpl->m_aLogicName.isEmpty())
3167     {
3168         // if the logic name is set it should be set in MediaDescriptor as well
3169         const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3170         if ( !pFileNameItem )
3171         {
3172             // let the ItemSet be created if necessary
3173             GetItemSet().Put(
3174                 SfxStringItem(
3175                     SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
3176         }
3177     }
3178 
3179     SetIsRemote_Impl();
3180 
3181     osl::DirectoryItem item;
3182     if (osl::DirectoryItem::get(GetName(), item) == osl::FileBase::E_None) {
3183         osl::FileStatus stat(osl_FileStatus_Mask_Attributes);
3184         if (item.getFileStatus(stat) == osl::FileBase::E_None
3185             && stat.isValid(osl_FileStatus_Mask_Attributes))
3186         {
3187             if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0)
3188             {
3189                 pImpl->m_bOriginallyReadOnly = true;
3190             }
3191         }
3192     }
3193 }
3194 
3195 
SfxMedium()3196 SfxMedium::SfxMedium() : pImpl(new SfxMedium_Impl)
3197 {
3198     Init_Impl();
3199 }
3200 
3201 
UseInteractionHandler(bool bUse)3202 void SfxMedium::UseInteractionHandler( bool bUse )
3203 {
3204     pImpl->bAllowDefaultIntHdl = bUse;
3205 }
3206 
3207 
3208 css::uno::Reference< css::task::XInteractionHandler >
GetInteractionHandler(bool bGetAlways)3209 SfxMedium::GetInteractionHandler( bool bGetAlways )
3210 {
3211     // if interaction isn't allowed explicitly ... return empty reference!
3212     if ( !bGetAlways && !pImpl->bUseInteractionHandler )
3213         return css::uno::Reference< css::task::XInteractionHandler >();
3214 
3215     // search a possible existing handler inside cached item set
3216     if ( pImpl->m_pSet )
3217     {
3218         css::uno::Reference< css::task::XInteractionHandler > xHandler;
3219         const SfxUnoAnyItem* pHandler = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INTERACTIONHANDLER, false);
3220         if ( pHandler && (pHandler->GetValue() >>= xHandler) && xHandler.is() )
3221             return xHandler;
3222     }
3223 
3224     // if default interaction isn't allowed explicitly ... return empty reference!
3225     if ( !bGetAlways && !pImpl->bAllowDefaultIntHdl )
3226         return css::uno::Reference< css::task::XInteractionHandler >();
3227 
3228     // otherwise return cached default handler ... if it exist.
3229     if ( pImpl->xInteraction.is() )
3230         return pImpl->xInteraction;
3231 
3232     // create default handler and cache it!
3233     Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
3234     pImpl->xInteraction.set(
3235         task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW );
3236     return pImpl->xInteraction;
3237 }
3238 
SetFilter(const std::shared_ptr<const SfxFilter> & pFilter)3239 void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter )
3240 {
3241     pImpl->m_pFilter = pFilter;
3242 }
3243 
GetFilter() const3244 const std::shared_ptr<const SfxFilter>& SfxMedium::GetFilter() const
3245 {
3246     return pImpl->m_pFilter;
3247 }
3248 
CreatePasswordToModifyHash(std::u16string_view aPasswd,bool bWriter)3249 sal_uInt32 SfxMedium::CreatePasswordToModifyHash( std::u16string_view aPasswd, bool bWriter )
3250 {
3251     sal_uInt32 nHash = 0;
3252 
3253     if ( !aPasswd.empty() )
3254     {
3255         if ( bWriter )
3256         {
3257             nHash = ::comphelper::DocPasswordHelper::GetWordHashAsUINT32( aPasswd );
3258         }
3259         else
3260         {
3261             rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
3262             nHash = ::comphelper::DocPasswordHelper::GetXLHashAsUINT16( aPasswd, nEncoding );
3263         }
3264     }
3265 
3266     return nHash;
3267 }
3268 
3269 
Close(bool bInDestruction)3270 void SfxMedium::Close(bool bInDestruction)
3271 {
3272     if ( pImpl->xStorage.is() )
3273     {
3274         CloseStorage();
3275     }
3276 
3277     CloseStreams_Impl(bInDestruction);
3278 
3279     UnlockFile( false );
3280 }
3281 
CloseAndRelease()3282 void SfxMedium::CloseAndRelease()
3283 {
3284     if ( pImpl->xStorage.is() )
3285     {
3286         CloseStorage();
3287     }
3288 
3289     CloseAndReleaseStreams_Impl();
3290 
3291     UnlockFile( true );
3292 }
3293 
DisableUnlockWebDAV(bool bDisableUnlockWebDAV)3294 void SfxMedium::DisableUnlockWebDAV( bool bDisableUnlockWebDAV )
3295 {
3296     pImpl->m_bDisableUnlockWebDAV = bDisableUnlockWebDAV;
3297 }
3298 
DisableFileSync(bool bDisableFileSync)3299 void SfxMedium::DisableFileSync(bool bDisableFileSync)
3300 {
3301     pImpl->m_bDisableFileSync = bDisableFileSync;
3302 }
3303 
UnlockFile(bool bReleaseLockStream)3304 void SfxMedium::UnlockFile( bool bReleaseLockStream )
3305 {
3306 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
3307     (void) bReleaseLockStream;
3308 #else
3309     // check if webdav
3310     if ( GetURLObject().isAnyKnownWebDAVScheme() )
3311     {
3312         // do nothing if WebDAV locking if disabled
3313         // (shouldn't happen because we already skipped locking,
3314         // see LockOrigFileOnDemand, but just in case ...)
3315         if (!IsWebDAVLockingUsed())
3316             return;
3317 
3318         if ( pImpl->m_bLocked )
3319         {
3320             // an interaction handler should be used for authentication, if needed
3321             try {
3322                 uno::Reference< css::task::XInteractionHandler > xHandler = GetInteractionHandler( true );
3323                 uno::Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( xHandler,
3324                                                                Reference< css::ucb::XProgressHandler >() );
3325                 ucbhelper::Content aContentToUnlock( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext());
3326                 pImpl->m_bLocked = false;
3327                 //check if WebDAV unlock was explicitly disabled
3328                 if ( !pImpl->m_bDisableUnlockWebDAV )
3329                     aContentToUnlock.unlock();
3330             }
3331             catch ( uno::Exception& )
3332             {
3333                 TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
3334             }
3335         }
3336         return;
3337     }
3338 
3339     if ( pImpl->m_xLockingStream.is() )
3340     {
3341         if ( bReleaseLockStream )
3342         {
3343             try
3344             {
3345                 uno::Reference< io::XInputStream > xInStream = pImpl->m_xLockingStream->getInputStream();
3346                 uno::Reference< io::XOutputStream > xOutStream = pImpl->m_xLockingStream->getOutputStream();
3347                 if ( xInStream.is() )
3348                     xInStream->closeInput();
3349                 if ( xOutStream.is() )
3350                     xOutStream->closeOutput();
3351             }
3352             catch( const uno::Exception& )
3353             {}
3354         }
3355 
3356         pImpl->m_xLockingStream.clear();
3357     }
3358 
3359     if ( !pImpl->m_bLocked )
3360         return;
3361 
3362     try
3363     {
3364         ::svt::DocumentLockFile aLockFile(pImpl->m_aLogicName);
3365 
3366         try
3367         {
3368             pImpl->m_bLocked = false;
3369             // TODO/LATER: A warning could be shown in case the file is not the own one
3370             aLockFile.RemoveFile();
3371         }
3372         catch (const io::WrongFormatException&)
3373         {
3374             // erase the empty or corrupt file
3375             aLockFile.RemoveFileDirectly();
3376         }
3377     }
3378     catch( const uno::Exception& )
3379     {}
3380 
3381     if(!pImpl->m_bMSOLockFileCreated)
3382         return;
3383 
3384     try
3385     {
3386         ::svt::MSODocumentLockFile aMSOLockFile(pImpl->m_aLogicName);
3387 
3388         try
3389         {
3390             pImpl->m_bLocked = false;
3391             // TODO/LATER: A warning could be shown in case the file is not the own one
3392             aMSOLockFile.RemoveFile();
3393         }
3394         catch (const io::WrongFormatException&)
3395         {
3396             // erase the empty or corrupt file
3397             aMSOLockFile.RemoveFileDirectly();
3398         }
3399     }
3400     catch( const uno::Exception& )
3401     {}
3402     pImpl->m_bMSOLockFileCreated = false;
3403 #endif
3404 }
3405 
CloseAndReleaseStreams_Impl()3406 void SfxMedium::CloseAndReleaseStreams_Impl()
3407 {
3408     CloseZipStorage_Impl();
3409 
3410     uno::Reference< io::XInputStream > xInToClose = pImpl->xInputStream;
3411     uno::Reference< io::XOutputStream > xOutToClose;
3412     if ( pImpl->xStream.is() )
3413     {
3414         xOutToClose = pImpl->xStream->getOutputStream();
3415 
3416         // if the locking stream is closed here the related member should be cleaned
3417         if ( pImpl->xStream == pImpl->m_xLockingStream )
3418             pImpl->m_xLockingStream.clear();
3419     }
3420 
3421     // The probably existing SvStream wrappers should be closed first
3422     CloseStreams_Impl();
3423 
3424     // in case of salvage mode the storage is based on the streams
3425     if ( pImpl->m_bSalvageMode )
3426         return;
3427 
3428     try
3429     {
3430         if ( xInToClose.is() )
3431             xInToClose->closeInput();
3432         if ( xOutToClose.is() )
3433             xOutToClose->closeOutput();
3434     }
3435     catch ( const uno::Exception& )
3436     {
3437     }
3438 }
3439 
3440 
CloseStreams_Impl(bool bInDestruction)3441 void SfxMedium::CloseStreams_Impl(bool bInDestruction)
3442 {
3443     CloseInStream_Impl(bInDestruction);
3444     CloseOutStream_Impl();
3445 
3446     if ( pImpl->m_pSet )
3447         pImpl->m_pSet->ClearItem( SID_CONTENT );
3448 
3449     pImpl->aContent = ::ucbhelper::Content();
3450 }
3451 
3452 
SetIsRemote_Impl()3453 void SfxMedium::SetIsRemote_Impl()
3454 {
3455     INetURLObject aObj( GetName() );
3456     switch( aObj.GetProtocol() )
3457     {
3458         case INetProtocol::Ftp:
3459         case INetProtocol::Http:
3460         case INetProtocol::Https:
3461             pImpl->m_bRemote = true;
3462         break;
3463         default:
3464             pImpl->m_bRemote = GetName().startsWith("private:msgid");
3465             break;
3466     }
3467 
3468     // As files that are written to the remote transmission must also be able
3469     // to be read.
3470     if (pImpl->m_bRemote)
3471         pImpl->m_nStorOpenMode |= StreamMode::READ;
3472 }
3473 
3474 
SetName(const OUString & aNameP,bool bSetOrigURL)3475 void SfxMedium::SetName( const OUString& aNameP, bool bSetOrigURL )
3476 {
3477     if (pImpl->aOrigURL.isEmpty())
3478         pImpl->aOrigURL = pImpl->m_aLogicName;
3479     if( bSetOrigURL )
3480         pImpl->aOrigURL = aNameP;
3481     std::unique_lock<std::recursive_mutex> chkEditLock;
3482     if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3483         chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3484     pImpl->m_aLogicName = aNameP;
3485     pImpl->m_pURLObj.reset();
3486     if (chkEditLock.owns_lock())
3487         chkEditLock.unlock();
3488     pImpl->aContent = ::ucbhelper::Content();
3489     Init_Impl();
3490 }
3491 
3492 
GetOrigURL() const3493 const OUString& SfxMedium::GetOrigURL() const
3494 {
3495     return pImpl->aOrigURL.isEmpty() ? pImpl->m_aLogicName : pImpl->aOrigURL;
3496 }
3497 
3498 
SetPhysicalName_Impl(const OUString & rNameP)3499 void SfxMedium::SetPhysicalName_Impl( const OUString& rNameP )
3500 {
3501     if ( rNameP != pImpl->m_aName )
3502     {
3503         pImpl->pTempFile.reset();
3504 
3505         if ( !pImpl->m_aName.isEmpty() || !rNameP.isEmpty() )
3506             pImpl->aContent = ::ucbhelper::Content();
3507 
3508         pImpl->m_aName = rNameP;
3509         pImpl->m_bTriedStorage = false;
3510         pImpl->bIsStorage = false;
3511     }
3512 }
3513 
ReOpen()3514 void SfxMedium::ReOpen()
3515 {
3516     bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
3517     pImpl->bUseInteractionHandler = false;
3518     GetMedium_Impl();
3519     pImpl->bUseInteractionHandler = bUseInteractionHandler;
3520 }
3521 
CompleteReOpen()3522 void SfxMedium::CompleteReOpen()
3523 {
3524     // do not use temporary file for reopen and in case of success throw the temporary file away
3525     bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
3526     pImpl->bUseInteractionHandler = false;
3527 
3528     std::unique_ptr<::utl::TempFileNamed> pTmpFile;
3529     if ( pImpl->pTempFile )
3530     {
3531         pTmpFile = std::move(pImpl->pTempFile);
3532         pImpl->m_aName.clear();
3533     }
3534 
3535     GetMedium_Impl();
3536 
3537     if ( GetErrorIgnoreWarning() )
3538     {
3539         if ( pImpl->pTempFile )
3540         {
3541             pImpl->pTempFile->EnableKillingFile();
3542             pImpl->pTempFile.reset();
3543         }
3544         pImpl->pTempFile = std::move( pTmpFile );
3545         if ( pImpl->pTempFile )
3546             pImpl->m_aName = pImpl->pTempFile->GetFileName();
3547     }
3548     else if (pTmpFile)
3549     {
3550         pTmpFile->EnableKillingFile();
3551         pTmpFile.reset();
3552     }
3553 
3554     pImpl->bUseInteractionHandler = bUseInteractionHandler;
3555 }
3556 
SfxMedium(const OUString & rName,StreamMode nOpenMode,std::shared_ptr<const SfxFilter> pFilter,const std::shared_ptr<SfxItemSet> & pInSet)3557 SfxMedium::SfxMedium(const OUString &rName, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
3558     pImpl(new SfxMedium_Impl)
3559 {
3560     pImpl->m_pSet = pInSet;
3561     pImpl->m_pFilter = std::move(pFilter);
3562     pImpl->m_aLogicName = rName;
3563     pImpl->m_nStorOpenMode = nOpenMode;
3564     Init_Impl();
3565 }
3566 
SfxMedium(const OUString & rName,const OUString & rReferer,StreamMode nOpenMode,std::shared_ptr<const SfxFilter> pFilter,const std::shared_ptr<SfxItemSet> & pInSet)3567 SfxMedium::SfxMedium(const OUString &rName, const OUString &rReferer, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
3568     pImpl(new SfxMedium_Impl)
3569 {
3570     pImpl->m_pSet = pInSet;
3571     SfxItemSet& s = GetItemSet();
3572     if (s.GetItem(SID_REFERER) == nullptr) {
3573         s.Put(SfxStringItem(SID_REFERER, rReferer));
3574     }
3575     pImpl->m_pFilter = std::move(pFilter);
3576     pImpl->m_aLogicName = rName;
3577     pImpl->m_nStorOpenMode = nOpenMode;
3578     Init_Impl();
3579 }
3580 
SfxMedium(const uno::Sequence<beans::PropertyValue> & aArgs)3581 SfxMedium::SfxMedium( const uno::Sequence<beans::PropertyValue>& aArgs ) :
3582     pImpl(new SfxMedium_Impl)
3583 {
3584     SfxAllItemSet *pParams = new SfxAllItemSet( SfxGetpApp()->GetPool() );
3585     pImpl->m_pSet.reset( pParams );
3586     TransformParameters( SID_OPENDOC, aArgs, *pParams );
3587     SetArgs(aArgs);
3588 
3589     OUString aFilterProvider, aFilterName;
3590     {
3591         const SfxStringItem* pItem = nullptr;
3592         if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_PROVIDER)))
3593             aFilterProvider = pItem->GetValue();
3594 
3595         if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_NAME)))
3596             aFilterName = pItem->GetValue();
3597     }
3598 
3599     if (aFilterProvider.isEmpty())
3600     {
3601         // This is a conventional filter type.
3602         pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( aFilterName );
3603     }
3604     else
3605     {
3606         // This filter is from an external provider such as orcus.
3607         pImpl->m_pCustomFilter = std::make_shared<SfxFilter>(aFilterProvider, aFilterName);
3608         pImpl->m_pFilter = pImpl->m_pCustomFilter;
3609     }
3610 
3611     const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
3612     if( pSalvageItem )
3613     {
3614         // QUESTION: there is some treatment of Salvage in Init_Impl; align!
3615         if ( !pSalvageItem->GetValue().isEmpty() )
3616         {
3617             // if a URL is provided in SalvageItem that means that the FileName refers to a temporary file
3618             // that must be copied here
3619 
3620             const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3621             if (!pFileNameItem) throw uno::RuntimeException();
3622             OUString aNewTempFileURL = SfxMedium::CreateTempCopyWithExt( pFileNameItem->GetValue() );
3623             if ( !aNewTempFileURL.isEmpty() )
3624             {
3625                 pImpl->m_pSet->Put( SfxStringItem( SID_FILE_NAME, aNewTempFileURL ) );
3626                 pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
3627                 pImpl->m_pSet->ClearItem( SID_STREAM );
3628                 pImpl->m_pSet->ClearItem( SID_CONTENT );
3629             }
3630             else
3631             {
3632                 SAL_WARN( "sfx.doc", "Can not create a new temporary file for crash recovery!" );
3633             }
3634         }
3635     }
3636 
3637     const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
3638     if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
3639         pImpl->m_bOriginallyLoadedReadOnly = true;
3640 
3641     const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
3642     if (!pFileNameItem) throw uno::RuntimeException();
3643     pImpl->m_aLogicName = pFileNameItem->GetValue();
3644     pImpl->m_nStorOpenMode = pImpl->m_bOriginallyLoadedReadOnly
3645         ? SFX_STREAM_READONLY : SFX_STREAM_READWRITE;
3646     Init_Impl();
3647 }
3648 
SetArgs(const uno::Sequence<beans::PropertyValue> & rArgs)3649 void SfxMedium::SetArgs(const uno::Sequence<beans::PropertyValue>& rArgs)
3650 {
3651     static constexpr OUStringLiteral sStream(u"Stream");
3652     static constexpr OUStringLiteral sInputStream(u"InputStream");
3653     comphelper::SequenceAsHashMap aArgsMap(rArgs);
3654     aArgsMap.erase(sStream);
3655     aArgsMap.erase(sInputStream);
3656     pImpl->m_aArgs = aArgsMap.getAsConstPropertyValueList();
3657 }
3658 
GetArgs() const3659 const uno::Sequence<beans::PropertyValue> & SfxMedium::GetArgs() const { return pImpl->m_aArgs; }
3660 
SfxMedium(const uno::Reference<embed::XStorage> & rStor,const OUString & rBaseURL,const std::shared_ptr<SfxItemSet> & p)3661 SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const std::shared_ptr<SfxItemSet>& p ) :
3662     pImpl(new SfxMedium_Impl)
3663 {
3664     OUString aType = SfxFilter::GetTypeFromStorage(rStor);
3665     pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( aType );
3666     DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
3667 
3668     Init_Impl();
3669     pImpl->xStorage = rStor;
3670     pImpl->bDisposeStorage = false;
3671 
3672     // always take BaseURL first, could be overwritten by ItemSet
3673     GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
3674     if ( p )
3675         GetItemSet().Put( *p );
3676 }
3677 
3678 
SfxMedium(const uno::Reference<embed::XStorage> & rStor,const OUString & rBaseURL,const OUString & rTypeName,const std::shared_ptr<SfxItemSet> & p)3679 SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const OUString &rTypeName, const std::shared_ptr<SfxItemSet>& p ) :
3680     pImpl(new SfxMedium_Impl)
3681 {
3682     pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( rTypeName );
3683     DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
3684 
3685     Init_Impl();
3686     pImpl->xStorage = rStor;
3687     pImpl->bDisposeStorage = false;
3688 
3689     // always take BaseURL first, could be overwritten by ItemSet
3690     GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
3691     if ( p )
3692         GetItemSet().Put( *p );
3693 }
3694 
3695 // NOTE: should only be called on main thread
~SfxMedium()3696 SfxMedium::~SfxMedium()
3697 {
3698     CancelCheckEditableEntry();
3699 
3700     // if there is a requirement to clean the backup this is the last possibility to do it
3701     ClearBackup_Impl();
3702 
3703     Close(/*bInDestruction*/true);
3704 
3705     if( !pImpl->bIsTemp || pImpl->m_aName.isEmpty() )
3706         return;
3707 
3708     OUString aTemp;
3709     if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aTemp )
3710          != osl::FileBase::E_None )
3711     {
3712         SAL_WARN( "sfx.doc", "Physical name not convertible!");
3713     }
3714 
3715     if ( !::utl::UCBContentHelper::Kill( aTemp ) )
3716     {
3717         SAL_WARN( "sfx.doc", "Couldn't remove temporary file!");
3718     }
3719 }
3720 
GetName() const3721 const OUString& SfxMedium::GetName() const
3722 {
3723     return pImpl->m_aLogicName;
3724 }
3725 
GetURLObject() const3726 const INetURLObject& SfxMedium::GetURLObject() const
3727 {
3728     std::unique_lock<std::recursive_mutex> chkEditLock;
3729     if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
3730         chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
3731 
3732     if (!pImpl->m_pURLObj)
3733     {
3734         pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) );
3735         pImpl->m_pURLObj->SetMark(u"");
3736     }
3737 
3738     return *pImpl->m_pURLObj;
3739 }
3740 
SetExpired_Impl(const DateTime & rDateTime)3741 void SfxMedium::SetExpired_Impl( const DateTime& rDateTime )
3742 {
3743     pImpl->aExpireTime = rDateTime;
3744 }
3745 
3746 
IsExpired() const3747 bool SfxMedium::IsExpired() const
3748 {
3749     return pImpl->aExpireTime.IsValidAndGregorian() && pImpl->aExpireTime < DateTime( DateTime::SYSTEM );
3750 }
3751 
3752 
GetLoadTargetFrame() const3753 SfxFrame* SfxMedium::GetLoadTargetFrame() const
3754 {
3755     return pImpl->wLoadTargetFrame;
3756 }
3757 
setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream> & xInputStream,bool bIsReadOnly)3758 void SfxMedium::setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream>& xInputStream, bool bIsReadOnly )
3759 {
3760     pImpl->m_xInputStreamToLoadFrom = xInputStream;
3761     pImpl->m_bInputStreamIsReadOnly = bIsReadOnly;
3762 }
3763 
SetLoadTargetFrame(SfxFrame * pFrame)3764 void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame )
3765 {
3766     pImpl->wLoadTargetFrame = pFrame;
3767 }
3768 
SetStorage_Impl(const uno::Reference<embed::XStorage> & xStorage)3769 void SfxMedium::SetStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
3770 {
3771     pImpl->xStorage = xStorage;
3772     pImpl->m_bODFWholesomeEncryption = false;
3773 }
3774 
SetInnerStorage_Impl(const uno::Reference<embed::XStorage> & xStorage)3775 void SfxMedium::SetInnerStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
3776 {
3777     pImpl->xStorage = xStorage;
3778     pImpl->m_bODFWholesomeEncryption = true;
3779 }
3780 
GetItemSet() const3781 SfxItemSet& SfxMedium::GetItemSet() const
3782 {
3783     if (!pImpl->m_pSet)
3784         pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() );
3785     return *pImpl->m_pSet;
3786 }
3787 
3788 
GetHeaderAttributes_Impl()3789 SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl()
3790 {
3791     if( !pImpl->xAttributes.is() )
3792     {
3793         pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator );
3794 
3795         if ( GetContent().is() )
3796         {
3797             try
3798             {
3799                 Any aAny = pImpl->aContent.getPropertyValue(u"MediaType"_ustr);
3800                 OUString aContentType;
3801                 aAny >>= aContentType;
3802 
3803                 pImpl->xAttributes->Append( SvKeyValue( u"content-type"_ustr, aContentType ) );
3804             }
3805             catch ( const css::uno::Exception& )
3806             {
3807             }
3808         }
3809     }
3810 
3811     return pImpl->xAttributes.get();
3812 }
3813 
GetInputStream()3814 css::uno::Reference< css::io::XInputStream > const &  SfxMedium::GetInputStream()
3815 {
3816     if ( !pImpl->xInputStream.is() )
3817         GetMedium_Impl();
3818     return pImpl->xInputStream;
3819 }
3820 
GetVersionList(bool _bNoReload)3821 const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload )
3822 {
3823     // if the medium has no name, then this medium should represent a new document and can have no version info
3824     if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.hasElements() &&
3825          ( !pImpl->m_aName.isEmpty() || !pImpl->m_aLogicName.isEmpty() ) && GetStorage().is() )
3826     {
3827         uno::Reference < document::XDocumentRevisionListPersistence > xReader =
3828                 document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3829         try
3830         {
3831             pImpl->aVersions = xReader->load( GetStorage() );
3832         }
3833         catch ( const uno::Exception& )
3834         {
3835         }
3836     }
3837 
3838     if ( !pImpl->m_bVersionsAlreadyLoaded )
3839         pImpl->m_bVersionsAlreadyLoaded = true;
3840 
3841     return pImpl->aVersions;
3842 }
3843 
GetVersionList(const uno::Reference<embed::XStorage> & xStorage)3844 uno::Sequence < util::RevisionTag > SfxMedium::GetVersionList( const uno::Reference < embed::XStorage >& xStorage )
3845 {
3846     uno::Reference < document::XDocumentRevisionListPersistence > xReader =
3847         document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3848     try
3849     {
3850         return xReader->load( xStorage );
3851     }
3852     catch ( const uno::Exception& )
3853     {
3854     }
3855 
3856     return uno::Sequence < util::RevisionTag >();
3857 }
3858 
AddVersion_Impl(util::RevisionTag & rRevision)3859 void SfxMedium::AddVersion_Impl( util::RevisionTag& rRevision )
3860 {
3861     if ( !GetStorage().is() )
3862         return;
3863 
3864     // To determine a unique name for the stream
3865     std::vector<sal_uInt32> aLongs;
3866     sal_Int32 nLength = pImpl->aVersions.getLength();
3867     for (const auto& rVersion : pImpl->aVersions)
3868     {
3869         sal_uInt32 nVer = static_cast<sal_uInt32>( o3tl::toInt32(rVersion.Identifier.subView(7)));
3870         size_t n;
3871         for ( n=0; n<aLongs.size(); ++n )
3872             if ( nVer<aLongs[n] )
3873                 break;
3874 
3875         aLongs.insert( aLongs.begin()+n, nVer );
3876     }
3877 
3878     std::vector<sal_uInt32>::size_type nKey;
3879     for ( nKey=0; nKey<aLongs.size(); ++nKey )
3880         if ( aLongs[nKey] > nKey+1 )
3881             break;
3882 
3883     rRevision.Identifier = "Version" + OUString::number( nKey + 1 );
3884     pImpl->aVersions.realloc( nLength+1 );
3885     pImpl->aVersions.getArray()[nLength] = rRevision;
3886 }
3887 
RemoveVersion_Impl(const OUString & rName)3888 void SfxMedium::RemoveVersion_Impl( const OUString& rName )
3889 {
3890     if ( !pImpl->aVersions.hasElements() )
3891         return;
3892 
3893     auto pVersion = std::find_if(std::cbegin(pImpl->aVersions), std::cend(pImpl->aVersions),
3894         [&rName](const auto& rVersion) { return rVersion.Identifier == rName; });
3895     if (pVersion != std::cend(pImpl->aVersions))
3896     {
3897         auto nIndex = static_cast<sal_Int32>(std::distance(std::cbegin(pImpl->aVersions), pVersion));
3898         comphelper::removeElementAt(pImpl->aVersions, nIndex);
3899     }
3900 }
3901 
TransferVersionList_Impl(SfxMedium const & rMedium)3902 bool SfxMedium::TransferVersionList_Impl( SfxMedium const & rMedium )
3903 {
3904     if ( rMedium.pImpl->aVersions.hasElements() )
3905     {
3906         pImpl->aVersions = rMedium.pImpl->aVersions;
3907         return true;
3908     }
3909 
3910     return false;
3911 }
3912 
SaveVersionList_Impl()3913 void SfxMedium::SaveVersionList_Impl()
3914 {
3915     if ( !GetStorage().is() )
3916         return;
3917 
3918     if ( !pImpl->aVersions.hasElements() )
3919         return;
3920 
3921     uno::Reference < document::XDocumentRevisionListPersistence > xWriter =
3922              document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
3923     try
3924     {
3925         xWriter->store( GetStorage(), pImpl->aVersions );
3926     }
3927     catch ( const uno::Exception& )
3928     {
3929     }
3930 }
3931 
IsReadOnly() const3932 bool SfxMedium::IsReadOnly() const
3933 {
3934     // a) ReadOnly filter can't produce read/write contents!
3935     bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY);
3936 
3937     // b) if filter allow read/write contents .. check open mode of the storage
3938     if (!bReadOnly)
3939         bReadOnly = !( GetOpenMode() & StreamMode::WRITE );
3940 
3941     // c) the API can force the readonly state!
3942     if (!bReadOnly)
3943     {
3944         const SfxBoolItem* pItem = GetItemSet().GetItem(SID_DOC_READONLY, false);
3945         if (pItem)
3946             bReadOnly = pItem->GetValue();
3947     }
3948 
3949     return bReadOnly;
3950 }
3951 
IsOriginallyReadOnly() const3952 bool SfxMedium::IsOriginallyReadOnly() const
3953 {
3954     return pImpl->m_bOriginallyReadOnly;
3955 }
3956 
SetOriginallyReadOnly(bool val)3957 void SfxMedium::SetOriginallyReadOnly(bool val)
3958 {
3959     pImpl->m_bOriginallyReadOnly = val;
3960 }
3961 
IsOriginallyLoadedReadOnly() const3962 bool SfxMedium::IsOriginallyLoadedReadOnly() const
3963 {
3964     return pImpl->m_bOriginallyLoadedReadOnly;
3965 }
3966 
SetWritableForUserOnly(const OUString & aURL)3967 bool SfxMedium::SetWritableForUserOnly( const OUString& aURL )
3968 {
3969     // UCB does not allow to allow write access only for the user,
3970     // use osl API
3971     bool bResult = false;
3972 
3973     ::osl::DirectoryItem aDirItem;
3974     if ( ::osl::DirectoryItem::get( aURL, aDirItem ) == ::osl::FileBase::E_None )
3975     {
3976         ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Attributes );
3977         if ( aDirItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None
3978           && aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) )
3979         {
3980             sal_uInt64 nAttributes = aFileStatus.getAttributes();
3981 
3982             nAttributes &= ~(osl_File_Attribute_OwnWrite |
3983                              osl_File_Attribute_GrpWrite |
3984                              osl_File_Attribute_OthWrite |
3985                              osl_File_Attribute_ReadOnly);
3986             nAttributes |=  (osl_File_Attribute_OwnWrite |
3987                              osl_File_Attribute_OwnRead);
3988 
3989             bResult = ( osl::File::setAttributes( aURL, nAttributes ) == ::osl::FileBase::E_None );
3990         }
3991     }
3992 
3993     return bResult;
3994 }
3995 
3996 namespace
3997 {
3998 /// Get the parent directory of a temporary file for output purposes.
GetLogicBase(const INetURLObject & rURL,std::unique_ptr<SfxMedium_Impl> const & pImpl)3999 OUString GetLogicBase(const INetURLObject& rURL, std::unique_ptr<SfxMedium_Impl> const & pImpl)
4000 {
4001     OUString aLogicBase;
4002 
4003 #if HAVE_FEATURE_MACOSX_SANDBOX
4004     // In a sandboxed environment we don't want to attempt to create temporary files in the same
4005     // directory where the user has selected an output file to be stored. The sandboxed process has
4006     // permission only to create the specifically named output file in that directory.
4007     (void) rURL;
4008     (void) pImpl;
4009 #else
4010     if (!officecfg::Office::Common::Misc::TempFileNextToLocalFile::get())
4011         return aLogicBase;
4012 
4013     if (!pImpl->m_bHasEmbeddedObjects // Embedded objects would mean a special base, ignore that.
4014         && rURL.GetProtocol() == INetProtocol::File && !pImpl->m_pInStream)
4015     {
4016         // Try to create the temp file in the same directory when storing.
4017         INetURLObject aURL(rURL);
4018         aURL.removeSegment();
4019         aLogicBase = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset);
4020     }
4021 
4022 #endif // !HAVE_FEATURE_MACOSX_SANDBOX
4023 
4024     return aLogicBase;
4025 }
4026 }
4027 
CreateTempFile(bool bReplace)4028 void SfxMedium::CreateTempFile( bool bReplace )
4029 {
4030     if ( pImpl->pTempFile )
4031     {
4032         if ( !bReplace )
4033             return;
4034 
4035         pImpl->pTempFile.reset();
4036         pImpl->m_aName.clear();
4037     }
4038 
4039     OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
4040     pImpl->pTempFile.reset(new ::utl::TempFileNamed(&aLogicBase));
4041     if (!aLogicBase.isEmpty() && pImpl->pTempFile->GetFileName().isEmpty())
4042         pImpl->pTempFile.reset(new ::utl::TempFileNamed());
4043     pImpl->pTempFile->EnableKillingFile();
4044     pImpl->m_aName = pImpl->pTempFile->GetFileName();
4045     OUString aTmpURL = pImpl->pTempFile->GetURL();
4046     if ( pImpl->m_aName.isEmpty() || aTmpURL.isEmpty() )
4047     {
4048         SetError(ERRCODE_IO_CANTWRITE);
4049         return;
4050     }
4051 
4052     if ( !(pImpl->m_nStorOpenMode & StreamMode::TRUNC) )
4053     {
4054         bool bTransferSuccess = false;
4055 
4056         if ( GetContent().is()
4057           && GetURLObject().GetProtocol() == INetProtocol::File
4058           && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
4059         {
4060             // if there is already such a document, we should copy it
4061             // if it is a file system use OS copy process
4062             try
4063             {
4064                 uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
4065                 INetURLObject aTmpURLObj( aTmpURL );
4066                 OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT,
4067                                                                 true,
4068                                                                 INetURLObject::DecodeMechanism::WithCharset );
4069                 if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() )
4070                 {
4071                     ::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4072                     OUString sMimeType = pImpl->getFilterMimeType();
4073                     aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType );
4074                     SetWritableForUserOnly( aTmpURL );
4075                     bTransferSuccess = true;
4076                 }
4077             }
4078             catch( const uno::Exception& )
4079             {}
4080 
4081             if ( bTransferSuccess )
4082             {
4083                 CloseOutStream();
4084                 CloseInStream();
4085             }
4086         }
4087 
4088         if ( !bTransferSuccess && pImpl->m_pInStream )
4089         {
4090             // the case when there is no URL-access available or this is a remote protocol
4091             // but there is an input stream
4092             GetOutStream();
4093             if ( pImpl->m_pOutStream )
4094             {
4095                 std::unique_ptr<char[]> pBuf(new char [8192]);
4096                 ErrCode      nErr = ERRCODE_NONE;
4097 
4098                 pImpl->m_pInStream->Seek(0);
4099                 pImpl->m_pOutStream->Seek(0);
4100 
4101                 while( !pImpl->m_pInStream->eof() && nErr == ERRCODE_NONE )
4102                 {
4103                     sal_uInt32 nRead = pImpl->m_pInStream->ReadBytes(pBuf.get(), 8192);
4104                     nErr = pImpl->m_pInStream->GetError();
4105                     pImpl->m_pOutStream->WriteBytes( pBuf.get(), nRead );
4106                 }
4107 
4108                 bTransferSuccess = true;
4109                 CloseInStream();
4110             }
4111             CloseOutStream_Impl();
4112         }
4113         else
4114         {
4115             // Quite strange design, but currently it is expected that in this case no transfer happens
4116             // TODO/LATER: get rid of this inconsistent part of the call design
4117             bTransferSuccess = true;
4118             CloseInStream();
4119         }
4120 
4121         if ( !bTransferSuccess )
4122         {
4123             SetError(ERRCODE_IO_CANTWRITE);
4124             return;
4125         }
4126     }
4127 
4128     CloseStorage();
4129 }
4130 
4131 
CreateTempFileNoCopy()4132 void SfxMedium::CreateTempFileNoCopy()
4133 {
4134     // this call always replaces the existing temporary file
4135     pImpl->pTempFile.reset();
4136 
4137     OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
4138     pImpl->pTempFile.reset(new ::utl::TempFileNamed(&aLogicBase));
4139     if (!aLogicBase.isEmpty() && pImpl->pTempFile->GetFileName().isEmpty())
4140         pImpl->pTempFile.reset(new ::utl::TempFileNamed());
4141     pImpl->pTempFile->EnableKillingFile();
4142     pImpl->m_aName = pImpl->pTempFile->GetFileName();
4143     if ( pImpl->m_aName.isEmpty() )
4144     {
4145         SetError(ERRCODE_IO_CANTWRITE);
4146         return;
4147     }
4148 
4149     CloseOutStream_Impl();
4150     CloseStorage();
4151 }
4152 
SignDocumentContentUsingCertificate(const css::uno::Reference<css::frame::XModel> & xModel,bool bHasValidDocumentSignature,const Reference<XCertificate> & xCertificate)4153 bool SfxMedium::SignDocumentContentUsingCertificate(
4154     const css::uno::Reference<css::frame::XModel>& xModel, bool bHasValidDocumentSignature,
4155     const Reference<XCertificate>& xCertificate)
4156 {
4157     bool bChanges = false;
4158 
4159     if (IsOpen() || GetErrorIgnoreWarning())
4160     {
4161         SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
4162         return bChanges;
4163     }
4164 
4165     // The component should know if there was a valid document signature, since
4166     // it should show a warning in this case
4167     OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
4168     uno::Reference< security::XDocumentDigitalSignatures > xSigner(
4169         security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
4170             comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
4171     auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get());
4172     if (!xModelSigner)
4173     {
4174         return bChanges;
4175     }
4176 
4177     uno::Reference< embed::XStorage > xWriteableZipStor;
4178 
4179     // we can reuse the temporary file if there is one already
4180     CreateTempFile( false );
4181     GetMedium_Impl();
4182 
4183     try
4184     {
4185         if ( !pImpl->xStream.is() )
4186             throw uno::RuntimeException();
4187 
4188         bool bODF = GetFilter()->IsOwnFormat();
4189         try
4190         {
4191             xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
4192         }
4193         catch (const io::IOException&)
4194         {
4195             if (bODF)
4196             {
4197                 TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
4198             }
4199         }
4200 
4201         if ( !xWriteableZipStor.is() && bODF )
4202             throw uno::RuntimeException();
4203 
4204         uno::Reference< embed::XStorage > xMetaInf;
4205         if (xWriteableZipStor.is() && xWriteableZipStor->hasByName(u"META-INF"_ustr))
4206         {
4207             xMetaInf = xWriteableZipStor->openStorageElement(
4208                                             u"META-INF"_ustr,
4209                                             embed::ElementModes::READWRITE );
4210             if ( !xMetaInf.is() )
4211                 throw uno::RuntimeException();
4212         }
4213 
4214         {
4215             if (xMetaInf.is())
4216             {
4217                 // ODF.
4218                 uno::Reference< io::XStream > xStream;
4219                 if (GetFilter() && GetFilter()->IsOwnFormat())
4220                     xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
4221 
4222                 bool bSuccess = xModelSigner->SignModelWithCertificate(
4223                     xModel, xCertificate, GetZipStorageToSign_Impl(), xStream);
4224 
4225                 if (bSuccess)
4226                 {
4227                     uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4228                     xTransact->commit();
4229                     xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4230                     xTransact->commit();
4231 
4232                     // the temporary file has been written, commit it to the original file
4233                     Commit();
4234                     bChanges = true;
4235                 }
4236             }
4237             else if (xWriteableZipStor.is())
4238             {
4239                 // OOXML.
4240                 uno::Reference<io::XStream> xStream;
4241 
4242                     // We need read-write to be able to add the signature relation.
4243                 bool bSuccess = xModelSigner->SignModelWithCertificate(
4244                     xModel, xCertificate, GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
4245 
4246                 if (bSuccess)
4247                 {
4248                     uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
4249                     xTransact->commit();
4250 
4251                     // the temporary file has been written, commit it to the original file
4252                     Commit();
4253                     bChanges = true;
4254                 }
4255             }
4256             else
4257             {
4258                 // Something not ZIP based: e.g. PDF.
4259                 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
4260                 uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
4261                 if (xModelSigner->SignModelWithCertificate(
4262                         xModel, xCertificate, uno::Reference<embed::XStorage>(), xStream))
4263                     bChanges = true;
4264             }
4265         }
4266     }
4267     catch ( const uno::Exception& )
4268     {
4269         TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
4270     }
4271 
4272     CloseAndRelease();
4273 
4274     ResetError();
4275 
4276     return bChanges;
4277 }
4278 
4279 // note: this is the only function creating scripting signature
SignContents_Impl(weld::Window * pDialogParent,bool bSignScriptingContent,bool bHasValidDocumentSignature,const OUString & aSignatureLineId,const Reference<XCertificate> & xCert,const Reference<XGraphic> & xValidGraphic,const Reference<XGraphic> & xInvalidGraphic,const OUString & aComment)4280 bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
4281                                   bool bSignScriptingContent,
4282                                   bool bHasValidDocumentSignature,
4283                                   const OUString& aSignatureLineId,
4284                                   const Reference<XCertificate>& xCert,
4285                                   const Reference<XGraphic>& xValidGraphic,
4286                                   const Reference<XGraphic>& xInvalidGraphic,
4287                                   const OUString& aComment)
4288 {
4289     bool bChanges = false;
4290 
4291     if (IsOpen() || GetErrorIgnoreWarning())
4292     {
4293         SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
4294         return bChanges;
4295     }
4296 
4297     // The component should know if there was a valid document signature, since
4298     // it should show a warning in this case
4299     OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
4300     uno::Reference< security::XDocumentDigitalSignatures > xSigner(
4301         security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
4302             comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
4303     if (pDialogParent)
4304         xSigner->setParentWindow(pDialogParent->GetXWindow());
4305 
4306     uno::Reference< embed::XStorage > xWriteableZipStor;
4307 
4308     // we can reuse the temporary file if there is one already
4309     CreateTempFile( false );
4310     GetMedium_Impl();
4311 
4312     try
4313     {
4314         if ( !pImpl->xStream.is() )
4315             throw uno::RuntimeException();
4316 
4317         bool bODF = GetFilter()->IsOwnFormat();
4318         try
4319         {
4320             if (pImpl->m_bODFWholesomeEncryption && bSignScriptingContent)
4321             {
4322                 assert(pImpl->xStorage); // GetStorage was called above
4323                 assert(pImpl->m_xODFDecryptedInnerPackageStream);
4324                 xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
4325                     ZIP_STORAGE_FORMAT_STRING, pImpl->m_xODFDecryptedInnerPackageStream);
4326             }
4327             else
4328             {
4329                 xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
4330                     ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
4331             }
4332         }
4333         catch (const io::IOException&)
4334         {
4335             if (bODF)
4336             {
4337                 TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
4338             }
4339         }
4340 
4341         if ( !xWriteableZipStor.is() && bODF )
4342             throw uno::RuntimeException();
4343 
4344         uno::Reference< embed::XStorage > xMetaInf;
4345         if (xWriteableZipStor.is() && xWriteableZipStor->hasByName(u"META-INF"_ustr))
4346         {
4347             xMetaInf = xWriteableZipStor->openStorageElement(
4348                                             u"META-INF"_ustr,
4349                                             embed::ElementModes::READWRITE );
4350             if ( !xMetaInf.is() )
4351                 throw uno::RuntimeException();
4352         }
4353 
4354         if ( bSignScriptingContent )
4355         {
4356             // If the signature has already the document signature it will be removed
4357             // after the scripting signature is inserted.
4358             uno::Reference< io::XStream > xStream(
4359                 xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(),
4360                                                 embed::ElementModes::READWRITE ),
4361                 uno::UNO_SET_THROW );
4362 
4363             // note: the storage passed here must be independent from the
4364             // xWriteableZipStor because a writable storage can't have 2
4365             // instances of sub-storage for the same directory open, but with
4366             // independent storages it somehow works
4367             if (xSigner->signScriptingContent(GetScriptingStorageToSign_Impl(), xStream))
4368             {
4369                 // remove the document signature if any
4370                 OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
4371                 if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) )
4372                     xMetaInf->removeElement( aDocSigName );
4373 
4374                 uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4375                 xTransact->commit();
4376                 xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4377                 xTransact->commit();
4378 
4379                 if (pImpl->m_bODFWholesomeEncryption)
4380                 {   // manually copy the inner package to the outer one
4381                     uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
4382                     uno::Reference<io::XStream> const xEncryptedPackage =
4383                         pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
4384                             u"encrypted-package"_ustr,
4385                             embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
4386                     comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
4387                     xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
4388                     xTransact->commit(); // Commit() below won't do this
4389                 }
4390 
4391                 assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
4392                     || !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified());
4393                 // the temporary file has been written, commit it to the original file
4394                 Commit();
4395                 bChanges = true;
4396             }
4397         }
4398         else
4399         {
4400             if (xMetaInf.is())
4401             {
4402                 // ODF.
4403                 uno::Reference< io::XStream > xStream;
4404                 if (GetFilter() && GetFilter()->IsOwnFormat())
4405                     xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
4406 
4407                 bool bSuccess = false;
4408                 if (xCert.is())
4409                     bSuccess = xSigner->signSignatureLine(
4410                         GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
4411                         xValidGraphic, xInvalidGraphic, aComment);
4412                 else
4413                     bSuccess = xSigner->signDocumentContent(GetZipStorageToSign_Impl(),
4414                                                             xStream);
4415 
4416                 if (bSuccess)
4417                 {
4418                     uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
4419                     xTransact->commit();
4420                     xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
4421                     xTransact->commit();
4422 
4423                     // the temporary file has been written, commit it to the original file
4424                     Commit();
4425                     bChanges = true;
4426                 }
4427             }
4428             else if (xWriteableZipStor.is())
4429             {
4430                 // OOXML.
4431                 uno::Reference<io::XStream> xStream;
4432 
4433                 bool bSuccess = false;
4434                 if (xCert.is())
4435                 {
4436                     bSuccess = xSigner->signSignatureLine(
4437                         GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId,
4438                         xCert, xValidGraphic, xInvalidGraphic, aComment);
4439                 }
4440                 else
4441                 {
4442                     // We need read-write to be able to add the signature relation.
4443                     bSuccess =xSigner->signDocumentContent(
4444                         GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
4445                 }
4446 
4447                 if (bSuccess)
4448                 {
4449                     uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
4450                     xTransact->commit();
4451 
4452                     // the temporary file has been written, commit it to the original file
4453                     Commit();
4454                     bChanges = true;
4455                 }
4456             }
4457             else
4458             {
4459                 // Something not ZIP based: e.g. PDF.
4460                 std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
4461                 uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
4462                 if (xSigner->signDocumentContent(uno::Reference<embed::XStorage>(), xStream))
4463                     bChanges = true;
4464             }
4465         }
4466     }
4467     catch ( const uno::Exception& )
4468     {
4469         TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
4470     }
4471 
4472     CloseAndRelease();
4473 
4474     ResetError();
4475 
4476     return bChanges;
4477 }
4478 
4479 
GetCachedSignatureState_Impl() const4480 SignatureState SfxMedium::GetCachedSignatureState_Impl() const
4481 {
4482     return pImpl->m_nSignatureState;
4483 }
4484 
4485 
SetCachedSignatureState_Impl(SignatureState nState)4486 void SfxMedium::SetCachedSignatureState_Impl( SignatureState nState )
4487 {
4488     pImpl->m_nSignatureState = nState;
4489 }
4490 
SetHasEmbeddedObjects(bool bHasEmbeddedObjects)4491 void SfxMedium::SetHasEmbeddedObjects(bool bHasEmbeddedObjects)
4492 {
4493     pImpl->m_bHasEmbeddedObjects = bHasEmbeddedObjects;
4494 }
4495 
HasStorage_Impl() const4496 bool SfxMedium::HasStorage_Impl() const
4497 {
4498     return pImpl->xStorage.is();
4499 }
4500 
IsOpen() const4501 bool SfxMedium::IsOpen() const
4502 {
4503     return pImpl->m_pInStream || pImpl->m_pOutStream || pImpl->xStorage.is();
4504 }
4505 
CreateTempCopyWithExt(std::u16string_view aURL)4506 OUString SfxMedium::CreateTempCopyWithExt( std::u16string_view aURL )
4507 {
4508     OUString aResult;
4509 
4510     if ( !aURL.empty() )
4511     {
4512         size_t nPrefixLen = aURL.rfind( '.' );
4513         std::u16string_view aExt = ( nPrefixLen == std::u16string_view::npos ) ? std::u16string_view() : aURL.substr( nPrefixLen );
4514 
4515         OUString aNewTempFileURL = ::utl::CreateTempURL( u"", true, aExt );
4516         if ( !aNewTempFileURL.isEmpty() )
4517         {
4518             INetURLObject aSource( aURL );
4519             INetURLObject aDest( aNewTempFileURL );
4520             OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT,
4521                                                         true,
4522                                                         INetURLObject::DecodeMechanism::WithCharset );
4523             if ( !aFileName.isEmpty() && aDest.removeSegment() )
4524             {
4525                 try
4526                 {
4527                     uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
4528                     ::ucbhelper::Content aTargetContent( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4529                     ::ucbhelper::Content aSourceContent( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
4530                     aTargetContent.transferContent( aSourceContent,
4531                                                         ::ucbhelper::InsertOperation::Copy,
4532                                                         aFileName,
4533                                                         NameClash::OVERWRITE );
4534                     aResult = aNewTempFileURL;
4535                 }
4536                 catch( const uno::Exception& )
4537                 {}
4538             }
4539         }
4540     }
4541 
4542     return aResult;
4543 }
4544 
CallApproveHandler(const uno::Reference<task::XInteractionHandler> & xHandler,const uno::Any & rRequest,bool bAllowAbort)4545 bool SfxMedium::CallApproveHandler(const uno::Reference< task::XInteractionHandler >& xHandler, const uno::Any& rRequest, bool bAllowAbort)
4546 {
4547     bool bResult = false;
4548 
4549     if ( xHandler.is() )
4550     {
4551         try
4552         {
4553             uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( bAllowAbort ? 2 : 1 );
4554             auto pContinuations = aContinuations.getArray();
4555 
4556             ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove );
4557             pContinuations[ 0 ] = pApprove.get();
4558 
4559             if ( bAllowAbort )
4560             {
4561                 ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort( new ::comphelper::OInteractionAbort );
4562                 pContinuations[ 1 ] = pAbort.get();
4563             }
4564 
4565             xHandler->handle(::framework::InteractionRequest::CreateRequest(rRequest, aContinuations));
4566             bResult = pApprove->wasSelected();
4567         }
4568         catch( const Exception& )
4569         {
4570         }
4571     }
4572 
4573     return bResult;
4574 }
4575 
SwitchDocumentToTempFile()4576 OUString SfxMedium::SwitchDocumentToTempFile()
4577 {
4578     // the method returns empty string in case of failure
4579     OUString aResult;
4580     OUString aOrigURL = pImpl->m_aLogicName;
4581 
4582     if ( !aOrigURL.isEmpty() )
4583     {
4584         sal_Int32 nPrefixLen = aOrigURL.lastIndexOf( '.' );
4585         std::u16string_view aExt = (nPrefixLen == -1)
4586                                 ? std::u16string_view()
4587                                 : aOrigURL.subView(nPrefixLen);
4588         OUString aNewURL = ::utl::CreateTempURL( u"", true, aExt );
4589 
4590         // TODO/LATER: In future the aLogicName should be set to shared folder URL
4591         //             and a temporary file should be created. Transport_Impl should be impossible then.
4592         if ( !aNewURL.isEmpty() )
4593         {
4594             uno::Reference< embed::XStorage > xStorage = GetStorage();
4595             uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
4596 
4597             if ( xOptStorage.is() )
4598             {
4599                 // TODO/LATER: reuse the pImpl->pTempFile if it already exists
4600                 CanDisposeStorage_Impl( false );
4601                 Close();
4602                 SetPhysicalName_Impl( OUString() );
4603                 SetName( aNewURL );
4604 
4605                 // remove the readonly state
4606                 bool bWasReadonly = false;
4607                 pImpl->m_nStorOpenMode = SFX_STREAM_READWRITE;
4608                 const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
4609                 if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
4610                     bWasReadonly = true;
4611                 GetItemSet().ClearItem( SID_DOC_READONLY );
4612 
4613                 GetMedium_Impl();
4614                 LockOrigFileOnDemand( false, false );
4615                 CreateTempFile();
4616                 GetMedium_Impl();
4617 
4618                 if ( pImpl->xStream.is() )
4619                 {
4620                     try
4621                     {
4622                         xOptStorage->writeAndAttachToStream( pImpl->xStream );
4623                         pImpl->xStorage = xStorage;
4624                         aResult = aNewURL;
4625                     }
4626                     catch( const uno::Exception& )
4627                     {}
4628                 }
4629 
4630                 if (bWasReadonly)
4631                 {
4632                     // set the readonly state back
4633                     pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
4634                     GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
4635                 }
4636 
4637                 if ( aResult.isEmpty() )
4638                 {
4639                     Close();
4640                     SetPhysicalName_Impl( OUString() );
4641                     SetName( aOrigURL );
4642                     GetMedium_Impl();
4643                     pImpl->xStorage = xStorage;
4644                 }
4645             }
4646         }
4647     }
4648 
4649     return aResult;
4650 }
4651 
SwitchDocumentToFile(const OUString & aURL)4652 bool SfxMedium::SwitchDocumentToFile( const OUString& aURL )
4653 {
4654     // the method is only for storage based documents
4655     bool bResult = false;
4656     OUString aOrigURL = pImpl->m_aLogicName;
4657 
4658     if ( !aURL.isEmpty() && !aOrigURL.isEmpty() )
4659     {
4660         uno::Reference< embed::XStorage > xStorage = GetStorage();
4661         uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
4662 
4663         // TODO/LATER: reuse the pImpl->pTempFile if it already exists
4664         CanDisposeStorage_Impl( false );
4665         Close();
4666         SetPhysicalName_Impl( OUString() );
4667         SetName( aURL );
4668 
4669         // open the temporary file based document
4670         GetMedium_Impl();
4671         LockOrigFileOnDemand( false, false );
4672         CreateTempFile();
4673         GetMedium_Impl();
4674 
4675         if ( pImpl->xStream.is() )
4676         {
4677             try
4678             {
4679                 uno::Reference< io::XTruncate > xTruncate( pImpl->xStream, uno::UNO_QUERY_THROW );
4680                 xTruncate->truncate();
4681                 if ( xOptStorage.is() )
4682                     xOptStorage->writeAndAttachToStream( pImpl->xStream );
4683                 pImpl->xStorage = xStorage;
4684                 bResult = true;
4685             }
4686             catch( const uno::Exception& )
4687             {}
4688         }
4689 
4690         if ( !bResult )
4691         {
4692             Close();
4693             SetPhysicalName_Impl( OUString() );
4694             SetName( aOrigURL );
4695             GetMedium_Impl();
4696             pImpl->xStorage = xStorage;
4697         }
4698     }
4699 
4700     return bResult;
4701 }
4702 
SetInCheckIn(bool bInCheckIn)4703 void SfxMedium::SetInCheckIn( bool bInCheckIn )
4704 {
4705     pImpl->m_bInCheckIn = bInCheckIn;
4706 }
4707 
IsInCheckIn() const4708 bool SfxMedium::IsInCheckIn( ) const
4709 {
4710     return pImpl->m_bInCheckIn;
4711 }
4712 
4713 // should only be called on main thread
GetCheckEditableMutex() const4714 const std::shared_ptr<std::recursive_mutex>& SfxMedium::GetCheckEditableMutex() const
4715 {
4716     return pImpl->m_pCheckEditableWorkerMutex;
4717 }
4718 
4719 // should only be called while holding pImpl->m_pCheckEditableWorkerMutex
SetWorkerReloadEvent(ImplSVEvent * pEvent)4720 void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent)
4721 {
4722     pImpl->m_pReloadEvent = pEvent;
4723 }
4724 
4725 // should only be called while holding pImpl->m_pCheckEditableWorkerMutex
GetWorkerReloadEvent() const4726 ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const
4727 {
4728     return pImpl->m_pReloadEvent;
4729 }
4730 
4731 // should only be called on main thread
AddToCheckEditableWorkerList()4732 void SfxMedium::AddToCheckEditableWorkerList()
4733 {
4734     if (!pImpl->m_bNotifyWhenEditable)
4735         return;
4736 
4737     CancelCheckEditableEntry();
4738 
4739     if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
4740     {
4741         pImpl->m_pCheckEditableWorkerMutex = std::make_shared<std::recursive_mutex>();
4742         if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
4743             return;
4744     }
4745 
4746     pImpl->m_pIsDestructed = std::make_shared<bool>(false);
4747     if (pImpl->m_pIsDestructed == nullptr)
4748         return;
4749 
4750     std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
4751     if (g_newReadOnlyDocs.find(this) == g_newReadOnlyDocs.end())
4752     {
4753         bool bAddNewEntry = false;
4754         if (!g_bChkReadOnlyTaskRunning)
4755         {
4756             std::shared_ptr<comphelper::ThreadTaskTag> pTag
4757                 = comphelper::ThreadPool::createThreadTaskTag();
4758             if (pTag != nullptr)
4759             {
4760                 g_bChkReadOnlyTaskRunning = true;
4761                 bAddNewEntry = true;
4762                 comphelper::ThreadPool::getSharedOptimalPool().pushTask(
4763                     std::make_unique<CheckReadOnlyTask>(pTag));
4764             }
4765         }
4766         else
4767             bAddNewEntry = true;
4768 
4769         if (bAddNewEntry)
4770         {
4771             std::shared_ptr<ReadOnlyMediumEntry> newEntry = std::make_shared<ReadOnlyMediumEntry>(
4772                 pImpl->m_pCheckEditableWorkerMutex, pImpl->m_pIsDestructed);
4773 
4774             if (newEntry != nullptr)
4775             {
4776                 g_newReadOnlyDocs[this] = newEntry;
4777             }
4778         }
4779     }
4780 }
4781 
4782 // should only be called on main thread
CancelCheckEditableEntry(bool bRemoveEvent)4783 void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent)
4784 {
4785     if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
4786     {
4787         std::unique_lock<std::recursive_mutex> lock(*(pImpl->m_pCheckEditableWorkerMutex));
4788 
4789         if (pImpl->m_pReloadEvent != nullptr)
4790         {
4791             if (bRemoveEvent)
4792                 Application::RemoveUserEvent(pImpl->m_pReloadEvent);
4793             // make sure destructor doesn't use a freed reference
4794             // and reset the event so we can check again
4795             pImpl->m_pReloadEvent = nullptr;
4796         }
4797 
4798         if (pImpl->m_pIsDestructed != nullptr)
4799         {
4800             *(pImpl->m_pIsDestructed) = true;
4801             pImpl->m_pIsDestructed = nullptr;
4802         }
4803     }
4804 }
4805 
4806 /** callback function, which is triggered by worker thread after successfully checking if the file
4807      is editable. Sent from <Application::PostUserEvent(..)>
4808      Note: This method has to be run in the main thread.
4809 */
IMPL_STATIC_LINK(SfxMedium,ShowReloadEditableDialog,void *,p,void)4810 IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void)
4811 {
4812     SfxMedium* pMed = static_cast<SfxMedium*>(p);
4813     if (pMed == nullptr)
4814         return;
4815 
4816     pMed->CancelCheckEditableEntry(false);
4817 
4818     uno::Reference<task::XInteractionHandler> xHandler = pMed->GetInteractionHandler();
4819     if (xHandler.is())
4820     {
4821         OUString aDocumentURL
4822             = pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
4823         ::rtl::Reference<::ucbhelper::InteractionRequest> xInteractionRequestImpl
4824             = new ::ucbhelper::InteractionRequest(uno::Any(document::ReloadEditableRequest(
4825                 OUString(), uno::Reference<uno::XInterface>(), aDocumentURL)));
4826         if (xInteractionRequestImpl != nullptr)
4827         {
4828             uno::Sequence<uno::Reference<task::XInteractionContinuation>> aContinuations{
4829                 new ::ucbhelper::InteractionAbort(xInteractionRequestImpl.get()),
4830                 new ::ucbhelper::InteractionApprove(xInteractionRequestImpl.get())
4831             };
4832             xInteractionRequestImpl->setContinuations(aContinuations);
4833             xHandler->handle(xInteractionRequestImpl);
4834             ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
4835                 = xInteractionRequestImpl->getSelection();
4836             if (uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
4837             {
4838                 for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame;
4839                      pFrame = SfxViewFrame::GetNext(*pFrame))
4840                 {
4841                     if (pFrame->GetObjectShell()->GetMedium() == pMed)
4842                     {
4843                         // special case to ensure view isn't set to read-only in
4844                         // SfxViewFrame::ExecReload_Impl after reloading
4845                         pMed->SetOriginallyReadOnly(false);
4846                         pFrame->GetDispatcher()->Execute(SID_RELOAD);
4847                         break;
4848                     }
4849                 }
4850             }
4851         }
4852     }
4853 }
4854 
CheckCanGetLockfile() const4855 bool SfxMedium::CheckCanGetLockfile() const
4856 {
4857 #if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
4858     bool bCanReload = true;
4859 #else
4860     bool bCanReload = false;
4861     ::svt::DocumentLockFile aLockFile(GetName());
4862     LockFileEntry aData;
4863     osl::DirectoryItem rItem;
4864     auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem);
4865     if (nError1 == osl::FileBase::E_None)
4866     {
4867         try
4868         {
4869             aData = aLockFile.GetLockData();
4870         }
4871         catch (const io::WrongFormatException&)
4872         {
4873             // we get empty or corrupt data
4874             return false;
4875         }
4876         catch (const uno::Exception&)
4877         {
4878             // locked from other app
4879             return false;
4880         }
4881         LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
4882         bool bOwnLock
4883             = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
4884         if (bOwnLock
4885             && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
4886             && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
4887         {
4888             // this is own lock from the same installation, it could remain because of crash
4889             bCanReload = true;
4890         }
4891     }
4892     else if (nError1 == osl::FileBase::E_NOENT) // file doesn't exist
4893     {
4894         try
4895         {
4896             aLockFile.CreateOwnLockFile();
4897             try
4898             {
4899                 // TODO/LATER: A warning could be shown in case the file is not the own one
4900                 aLockFile.RemoveFile();
4901             }
4902             catch (const io::WrongFormatException&)
4903             {
4904                 try
4905                 {
4906                     // erase the empty or corrupt file
4907                     aLockFile.RemoveFileDirectly();
4908                 }
4909                 catch (const uno::Exception&)
4910                 {
4911                 }
4912             }
4913             bCanReload = true;
4914         }
4915         catch (const uno::Exception&)
4916         {
4917         }
4918     }
4919 #endif
4920     return bCanReload;
4921 }
4922 
4923 // worker thread method, should only be one thread globally
doWork()4924 void CheckReadOnlyTask::doWork()
4925 {
4926     if (m_xListener == nullptr)
4927         return;
4928 
4929     while (true)
4930     {
4931         std::unique_lock<std::mutex> termLock(m_xListener->mMutex);
4932         if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60),
4933                                         [this] { return m_xListener->bIsTerminated; }))
4934             // signalled, spurious wakeups should not be possible
4935             return;
4936 
4937         // must have timed-out
4938         termLock.unlock();
4939         std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
4940         for (auto it = g_newReadOnlyDocs.begin(); it != g_newReadOnlyDocs.end(); )
4941         {
4942             g_existingReadOnlyDocs[it->first] = it->second;
4943             it = g_newReadOnlyDocs.erase(it);
4944         }
4945         if (g_existingReadOnlyDocs.empty())
4946         {
4947             g_bChkReadOnlyTaskRunning = false;
4948             return;
4949         }
4950         globalLock.unlock();
4951 
4952         auto checkForErase = [](SfxMedium* pMed, const std::shared_ptr<ReadOnlyMediumEntry>& roEntry) -> bool
4953         {
4954             if (pMed == nullptr || roEntry == nullptr || roEntry->_pMutex == nullptr
4955                 || roEntry->_pIsDestructed == nullptr)
4956                 return true;
4957 
4958             std::unique_lock<std::recursive_mutex> medLock(*(roEntry->_pMutex));
4959             if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != nullptr)
4960                 return true;
4961 
4962             osl::File aFile(
4963                 pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
4964             if (aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None)
4965                 return false;
4966 
4967             if (!pMed->CheckCanGetLockfile())
4968                 return false;
4969 
4970             if (aFile.close() != osl::FileBase::E_None)
4971                 return true;
4972 
4973             // we can load, ask user
4974             ImplSVEvent* pEvent = Application::PostUserEvent(
4975                 LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed);
4976             pMed->SetWorkerReloadEvent(pEvent);
4977             return true;
4978         };
4979 
4980         for (auto it = g_existingReadOnlyDocs.begin(); it != g_existingReadOnlyDocs.end(); )
4981         {
4982             if (checkForErase(it->first, it->second))
4983                 it = g_existingReadOnlyDocs.erase(it);
4984             else
4985                 ++it;
4986         }
4987     }
4988 }
4989 
4990 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
4991