1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <comphelper/docpasswordhelper.hxx>
21 #include <sal/log.hxx>
22 #include <unotools/configmgr.hxx>
23 #include <unotools/mediadescriptor.hxx>
24 #include <unotools/securityoptions.hxx>
25 #include <unotools/ucbhelper.hxx>
26 #include <comphelper/namedvaluecollection.hxx>
27 #include <comphelper/stillreadwriteinteraction.hxx>
29 #include <com/sun/star/ucb/ContentCreationException.hpp>
30 #include <com/sun/star/ucb/XContent.hpp>
31 #include <com/sun/star/task/XInteractionHandler.hpp>
32 #include <com/sun/star/io/XStream.hpp>
33 #include <com/sun/star/io/XActiveDataSink.hpp>
34 #include <com/sun/star/io/XSeekable.hpp>
35 #include <com/sun/star/lang/IllegalArgumentException.hpp>
36 #include <com/sun/star/uri/UriReferenceFactory.hpp>
37 #include <com/sun/star/uri/XUriReference.hpp>
38 #include <com/sun/star/ucb/PostCommandArgument2.hpp>
39 #include <officecfg/Office/Common.hxx>
40 #include <ucbhelper/content.hxx>
41 #include <ucbhelper/commandenvironment.hxx>
42 #include <ucbhelper/activedatasink.hxx>
43 #include <comphelper/processfactory.hxx>
44 #include <tools/urlobj.hxx>
45 #include <osl/diagnose.h>
46 #include <comphelper/diagnose_ex.hxx>
52 OUString
removeFragment(OUString
const & uri
) {
53 css::uno::Reference
< css::uri::XUriReference
> ref(
54 css::uri::UriReferenceFactory::create(
55 comphelper::getProcessComponentContext())->
59 return ref
->getUriReference();
61 SAL_WARN("unotools.misc", "cannot parse <" << uri
<< ">");
68 MediaDescriptor::MediaDescriptor()
72 MediaDescriptor::MediaDescriptor(const css::uno::Sequence
< css::beans::PropertyValue
>& lSource
)
73 : SequenceAsHashMap(lSource
)
77 bool MediaDescriptor::isStreamReadOnly() const
79 bool bReadOnly
= false;
81 // check for explicit readonly state
82 const_iterator pIt
= find(MediaDescriptor::PROP_READONLY
);
85 pIt
->second
>>= bReadOnly
;
89 // streams based on post data are readonly by definition
90 pIt
= find(MediaDescriptor::PROP_POSTDATA
);
94 // A XStream capsulate XInputStream and XOutputStream ...
95 // If it exists - the file must be open in read/write mode!
96 pIt
= find(MediaDescriptor::PROP_STREAM
);
100 // Only file system content provider is able to provide XStream
101 // so for this content impossibility to create XStream triggers
102 // switch to readonly mode.
105 css::uno::Reference
< css::ucb::XContent
> xContent
= getUnpackedValueOrDefault(MediaDescriptor::PROP_UCBCONTENT
, css::uno::Reference
< css::ucb::XContent
>());
108 css::uno::Reference
< css::ucb::XContentIdentifier
> xId
= xContent
->getIdentifier();
111 aScheme
= xId
->getContentProviderScheme();
113 if (aScheme
.equalsIgnoreAsciiCase("file"))
117 ::ucbhelper::Content
aContent(xContent
,
118 utl::UCBContentHelper::getDefaultCommandEnvironment(),
119 comphelper::getProcessComponentContext());
120 aContent
.getPropertyValue("IsReadOnly") >>= bReadOnly
;
124 catch(const css::uno::RuntimeException
& )
126 catch(const css::uno::Exception
&)
132 css::uno::Any
MediaDescriptor::getComponentDataEntry( const OUString
& rName
) const
134 comphelper::SequenceAsHashMap::const_iterator aPropertyIter
= find( PROP_COMPONENTDATA
);
135 if( aPropertyIter
!= end() )
136 return comphelper::NamedValueCollection( aPropertyIter
->second
).get( rName
);
137 return css::uno::Any();
140 void MediaDescriptor::setComponentDataEntry( const OUString
& rName
, const css::uno::Any
& rValue
)
142 if( rValue
.hasValue() )
144 // get or create the 'ComponentData' property entry
145 css::uno::Any
& rCompDataAny
= operator[]( PROP_COMPONENTDATA
);
146 // insert the value (retain sequence type, create NamedValue elements by default)
147 bool bHasNamedValues
= !rCompDataAny
.hasValue() || rCompDataAny
.has
< css::uno::Sequence
< css::beans::NamedValue
> >();
148 bool bHasPropValues
= rCompDataAny
.has
< css::uno::Sequence
< css::beans::PropertyValue
> >();
149 OSL_ENSURE( bHasNamedValues
|| bHasPropValues
, "MediaDescriptor::setComponentDataEntry - incompatible 'ComponentData' property in media descriptor" );
150 if( bHasNamedValues
|| bHasPropValues
)
152 // insert or overwrite the passed value
153 comphelper::SequenceAsHashMap
aCompDataMap( rCompDataAny
);
154 aCompDataMap
[ rName
] = rValue
;
155 // write back the sequence (restore sequence with correct element type)
156 rCompDataAny
= aCompDataMap
.getAsConstAny( bHasPropValues
);
161 // if an empty Any is passed, clear the entry
162 clearComponentDataEntry( rName
);
166 void MediaDescriptor::clearComponentDataEntry( const OUString
& rName
)
168 comphelper::SequenceAsHashMap::iterator aPropertyIter
= find( PROP_COMPONENTDATA
);
169 if( aPropertyIter
== end() )
172 css::uno::Any
& rCompDataAny
= aPropertyIter
->second
;
173 bool bHasNamedValues
= rCompDataAny
.has
< css::uno::Sequence
< css::beans::NamedValue
> >();
174 bool bHasPropValues
= rCompDataAny
.has
< css::uno::Sequence
< css::beans::PropertyValue
> >();
175 OSL_ENSURE( bHasNamedValues
|| bHasPropValues
, "MediaDescriptor::clearComponentDataEntry - incompatible 'ComponentData' property in media descriptor" );
176 if( bHasNamedValues
|| bHasPropValues
)
178 // remove the value with the passed name
179 comphelper::SequenceAsHashMap
aCompDataMap( rCompDataAny
);
180 aCompDataMap
.erase( rName
);
181 // write back the sequence, or remove it completely if it is empty
182 if( aCompDataMap
.empty() )
183 erase( aPropertyIter
);
185 rCompDataAny
= aCompDataMap
.getAsConstAny( bHasPropValues
);
189 css::uno::Sequence
< css::beans::NamedValue
> MediaDescriptor::requestAndVerifyDocPassword(
190 comphelper::IDocPasswordVerifier
& rVerifier
,
191 comphelper::DocPasswordRequestType eRequestType
,
192 const ::std::vector
< OUString
>* pDefaultPasswords
)
194 css::uno::Sequence
< css::beans::NamedValue
> aMediaEncData
= getUnpackedValueOrDefault(
195 PROP_ENCRYPTIONDATA
, css::uno::Sequence
< css::beans::NamedValue
>() );
196 OUString aMediaPassword
= getUnpackedValueOrDefault(
197 PROP_PASSWORD
, OUString() );
198 css::uno::Reference
< css::task::XInteractionHandler
> xInteractHandler
= getUnpackedValueOrDefault(
199 PROP_INTERACTIONHANDLER
, css::uno::Reference
< css::task::XInteractionHandler
>() );
200 OUString aDocumentName
= getUnpackedValueOrDefault(
201 PROP_URL
, OUString() );
203 bool bIsDefaultPassword
= false;
204 css::uno::Sequence
< css::beans::NamedValue
> aEncryptionData
= comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
205 rVerifier
, aMediaEncData
, aMediaPassword
, xInteractHandler
, aDocumentName
, eRequestType
, pDefaultPasswords
, &bIsDefaultPassword
);
207 erase( PROP_PASSWORD
);
208 erase( PROP_ENCRYPTIONDATA
);
210 // insert encryption info into media descriptor
212 if( aEncryptionData
.hasElements() )
213 (*this)[ PROP_ENCRYPTIONDATA
] <<= aEncryptionData
;
215 return aEncryptionData
;
218 bool MediaDescriptor::addInputStream()
220 return impl_addInputStream( true );
223 /*-----------------------------------------------*/
224 bool MediaDescriptor::addInputStreamOwnLock()
226 const bool bLock
= !utl::ConfigManager::IsFuzzing()
227 && officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get();
228 return impl_addInputStream(bLock
);
231 /*-----------------------------------------------*/
232 bool MediaDescriptor::impl_addInputStream( bool bLockFile
)
234 // check for an already existing stream item first
235 const_iterator pIt
= find(MediaDescriptor::PROP_INPUTSTREAM
);
241 // No stream available - create a new one
242 // a) data comes as PostData ...
243 pIt
= find(MediaDescriptor::PROP_POSTDATA
);
246 const css::uno::Any
& rPostData
= pIt
->second
;
247 css::uno::Reference
< css::io::XInputStream
> xPostData
;
248 rPostData
>>= xPostData
;
250 return impl_openStreamWithPostData( xPostData
);
253 // b) ... or we must get it from the given URL
254 OUString sURL
= getUnpackedValueOrDefault(MediaDescriptor::PROP_URL
, OUString());
256 throw css::uno::Exception("Found no URL.",
257 css::uno::Reference
< css::uno::XInterface
>());
259 return impl_openStreamWithURL( removeFragment(sURL
), bLockFile
);
261 catch(const css::uno::Exception
&)
263 TOOLS_WARN_EXCEPTION("unotools.misc", "invalid MediaDescriptor detected");
268 bool MediaDescriptor::impl_openStreamWithPostData( const css::uno::Reference
< css::io::XInputStream
>& _rxPostData
)
270 if ( !_rxPostData
.is() )
271 throw css::lang::IllegalArgumentException("Found invalid PostData.",
272 css::uno::Reference
< css::uno::XInterface
>(), 1);
274 // PostData can't be used in read/write mode!
275 (*this)[MediaDescriptor::PROP_READONLY
] <<= true;
277 // prepare the environment
278 css::uno::Reference
< css::task::XInteractionHandler
> xInteraction
= getUnpackedValueOrDefault(
279 MediaDescriptor::PROP_INTERACTIONHANDLER
,
280 css::uno::Reference
< css::task::XInteractionHandler
>());
281 css::uno::Reference
< css::ucb::XProgressHandler
> xProgress
;
282 rtl::Reference
<::ucbhelper::CommandEnvironment
> xCommandEnv
= new ::ucbhelper::CommandEnvironment(xInteraction
, xProgress
);
285 OUString sMediaType
= getUnpackedValueOrDefault(MediaDescriptor::PROP_MEDIATYPE
, OUString());
286 if (sMediaType
.isEmpty())
288 sMediaType
= "application/x-www-form-urlencoded";
289 (*this)[MediaDescriptor::PROP_MEDIATYPE
] <<= sMediaType
;
293 OUString
sURL( getUnpackedValueOrDefault( PROP_URL
, OUString() ) );
295 css::uno::Reference
< css::io::XInputStream
> xResultStream
;
298 // seek PostData stream to the beginning
299 css::uno::Reference
< css::io::XSeekable
> xSeek( _rxPostData
, css::uno::UNO_QUERY
);
303 // a content for the URL
304 ::ucbhelper::Content
aContent( sURL
, xCommandEnv
, comphelper::getProcessComponentContext() );
307 css::ucb::PostCommandArgument2 aPostArgument
;
308 aPostArgument
.Source
= _rxPostData
;
309 css::uno::Reference
< css::io::XActiveDataSink
> xSink( new ucbhelper::ActiveDataSink
);
310 aPostArgument
.Sink
= xSink
;
311 aPostArgument
.MediaType
= sMediaType
;
312 aPostArgument
.Referer
= getUnpackedValueOrDefault( PROP_REFERRER
, OUString() );
314 aContent
.executeCommand( "post", css::uno::Any( aPostArgument
) );
317 xResultStream
= xSink
->getInputStream();
319 catch( const css::uno::Exception
& )
324 if ( !xResultStream
.is() )
326 OSL_FAIL( "no valid reply to the HTTP-Post" );
330 (*this)[MediaDescriptor::PROP_INPUTSTREAM
] <<= xResultStream
;
334 /*-----------------------------------------------*/
335 bool MediaDescriptor::impl_openStreamWithURL( const OUString
& sURL
, bool bLockFile
)
337 OUString
referer(getUnpackedValueOrDefault(PROP_REFERRER
, OUString()));
338 if (SvtSecurityOptions::isUntrustedReferer(referer
)) {
342 // prepare the environment
343 css::uno::Reference
< css::task::XInteractionHandler
> xOrgInteraction
= getUnpackedValueOrDefault(
344 MediaDescriptor::PROP_INTERACTIONHANDLER
,
345 css::uno::Reference
< css::task::XInteractionHandler
>());
347 css::uno::Reference
< css::task::XInteractionHandler
> xAuthenticationInteraction
= getUnpackedValueOrDefault(
348 MediaDescriptor::PROP_AUTHENTICATIONHANDLER
,
349 css::uno::Reference
< css::task::XInteractionHandler
>());
351 rtl::Reference
<comphelper::StillReadWriteInteraction
> xInteraction
= new comphelper::StillReadWriteInteraction(xOrgInteraction
,xAuthenticationInteraction
);
353 css::uno::Reference
< css::ucb::XProgressHandler
> xProgress
;
354 rtl::Reference
<::ucbhelper::CommandEnvironment
> xCommandEnv
= new ::ucbhelper::CommandEnvironment(xInteraction
, xProgress
);
356 // try to create the content
357 // no content -> no stream => return immediately with FALSE
358 ::ucbhelper::Content aContent
;
359 css::uno::Reference
< css::ucb::XContent
> xContent
;
362 aContent
= ::ucbhelper::Content(sURL
, xCommandEnv
, comphelper::getProcessComponentContext());
363 xContent
= aContent
.get();
365 catch(const css::uno::RuntimeException
&)
367 catch(const css::ucb::ContentCreationException
&)
369 TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL
<< "'");
370 return false; // TODO error handling
372 catch(const css::uno::Exception
&)
374 TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL
<< "'");
375 return false; // TODO error handling
378 // try to open the file in read/write mode
379 // (if it's allowed to do so).
380 // But handle errors in a "hidden mode". Because
381 // we try it readonly later - if read/write is not an option.
382 css::uno::Reference
< css::io::XStream
> xStream
;
383 css::uno::Reference
< css::io::XInputStream
> xInputStream
;
385 bool bReadOnly
= false;
386 bool bModeRequestedExplicitly
= false;
387 const_iterator pIt
= find(MediaDescriptor::PROP_READONLY
);
390 pIt
->second
>>= bReadOnly
;
391 bModeRequestedExplicitly
= true;
394 if ( !bReadOnly
&& bLockFile
)
398 // TODO: use "special" still interaction to suppress error messages
399 xStream
= aContent
.openWriteableStream();
401 xInputStream
= xStream
->getInputStream();
403 catch(const css::uno::RuntimeException
&)
405 catch(const css::uno::Exception
&)
407 css::uno::Any
ex( cppu::getCaughtException() );
408 // ignore exception, if reason was problem reasoned on
409 // open it in WRITABLE mode! Then we try it READONLY
410 // later a second time.
411 // All other errors must be handled as real error an
412 // break this method.
413 if (!xInteraction
->wasWriteError() || bModeRequestedExplicitly
)
415 SAL_WARN("unotools.misc","url: '" << sURL
<< "' " << exceptionToString(ex
));
416 // If the protocol is webdav, then we need to treat the stream as readonly, even if the
417 // operation was requested as read/write explicitly (the WebDAV UCB implementation is monodirectional
418 // read or write not both at the same time).
419 if ( !INetURLObject( sURL
).isAnyKnownWebDAVScheme() )
423 xInputStream
.clear();
427 // If opening of the stream in read/write mode was not allowed
428 // or failed by an error - we must try it in readonly mode.
429 if (!xInputStream
.is())
435 css::uno::Reference
< css::ucb::XContentIdentifier
> xContId(
436 aContent
.get().is() ? aContent
.get()->getIdentifier() : nullptr );
439 aScheme
= xContId
->getContentProviderScheme();
441 // Only file system content provider is able to provide XStream
442 // so for this content impossibility to create XStream triggers
443 // switch to readonly mode in case of opening with locking on
444 if( bLockFile
&& aScheme
.equalsIgnoreAsciiCase("file") )
448 bool bRequestReadOnly
= bReadOnly
;
449 aContent
.getPropertyValue("IsReadOnly") >>= bReadOnly
;
450 if ( bReadOnly
&& !bRequestReadOnly
&& bModeRequestedExplicitly
)
451 return false; // the document is explicitly requested with WRITABLE mode
454 catch(const css::uno::RuntimeException
&)
456 catch(const css::uno::Exception
&)
457 { /* no error handling if IsReadOnly property does not exist for UCP */ }
460 (*this)[MediaDescriptor::PROP_READONLY
] <<= bReadOnly
;
462 xInteraction
->resetInterceptions();
463 xInteraction
->resetErrorStates();
466 // all the contents except file-URLs should be opened as usual
467 if ( bLockFile
|| !aScheme
.equalsIgnoreAsciiCase("file") )
468 xInputStream
= aContent
.openStream();
470 xInputStream
= aContent
.openStreamNoLock();
472 catch(const css::uno::RuntimeException
&)
476 catch(const css::uno::Exception
&)
478 TOOLS_INFO_EXCEPTION("unotools.misc","url: '" << sURL
<< "'");
483 // add streams to the descriptor
485 (*this)[MediaDescriptor::PROP_UCBCONTENT
] <<= xContent
;
487 (*this)[MediaDescriptor::PROP_STREAM
] <<= xStream
;
488 if (xInputStream
.is())
489 (*this)[MediaDescriptor::PROP_INPUTSTREAM
] <<= xInputStream
;
491 // At least we need an input stream. The r/w stream is optional ...
492 return xInputStream
.is();
495 } // namespace comphelper
497 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */