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