Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/unotools/source/misc/mediadescriptor.cxx
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <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>
28
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>
47
48
namespace utl {
49
50
namespace {
51
52
25.6k
OUString removeFragment(OUString const & uri) {
53
25.6k
    css::uno::Reference< css::uri::XUriReference > ref(
54
25.6k
        css::uri::UriReferenceFactory::create(
55
25.6k
            comphelper::getProcessComponentContext())->
56
25.6k
        parse(uri));
57
25.6k
    if (ref.is()) {
58
25.6k
        ref->clearFragment();
59
25.6k
        return ref->getUriReference();
60
25.6k
    } else {
61
0
        SAL_WARN("unotools.misc", "cannot parse <" << uri << ">");
62
0
        return uri;
63
0
    }
64
25.6k
}
65
66
}
67
68
MediaDescriptor::MediaDescriptor()
69
49.3k
{
70
49.3k
}
71
72
MediaDescriptor::MediaDescriptor(const css::uno::Sequence< css::beans::PropertyValue >& lSource)
73
179k
    : SequenceAsHashMap(lSource)
74
179k
{
75
179k
}
76
77
bool MediaDescriptor::isStreamReadOnly() const
78
0
{
79
0
    bool bReadOnly = false;
80
81
    // check for explicit readonly state
82
0
    const_iterator pIt = find(MediaDescriptor::PROP_READONLY);
83
0
    if (pIt != end())
84
0
    {
85
0
        pIt->second >>= bReadOnly;
86
0
        return bReadOnly;
87
0
    }
88
89
    // streams based on post data are readonly by definition
90
0
    pIt = find(MediaDescriptor::PROP_POSTDATA);
91
0
    if (pIt != end())
92
0
        return true;
93
94
    // A XStream capsulate XInputStream and XOutputStream ...
95
    // If it exists - the file must be open in read/write mode!
96
0
    pIt = find(MediaDescriptor::PROP_STREAM);
97
0
    if (pIt != end())
98
0
        return false;
99
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.
103
0
    try
104
0
    {
105
0
        css::uno::Reference< css::ucb::XContent > xContent = getUnpackedValueOrDefault(MediaDescriptor::PROP_UCBCONTENT, css::uno::Reference< css::ucb::XContent >());
106
0
        if (xContent.is())
107
0
        {
108
0
            css::uno::Reference< css::ucb::XContentIdentifier > xId = xContent->getIdentifier();
109
0
            OUString aScheme;
110
0
            if (xId.is())
111
0
                aScheme = xId->getContentProviderScheme();
112
113
0
            if (aScheme.equalsIgnoreAsciiCase("file"))
114
0
                bReadOnly = true;
115
0
            else
116
0
            {
117
0
                ::ucbhelper::Content aContent(xContent,
118
0
                                              utl::UCBContentHelper::getDefaultCommandEnvironment(),
119
0
                                              comphelper::getProcessComponentContext());
120
0
                aContent.getPropertyValue(u"IsReadOnly"_ustr) >>= bReadOnly;
121
0
            }
122
0
        }
123
0
    }
124
0
    catch(const css::uno::RuntimeException& )
125
0
        { throw; }
126
0
    catch(const css::uno::Exception&)
127
0
        {}
128
129
0
    return bReadOnly;
130
0
}
131
132
css::uno::Any MediaDescriptor::getComponentDataEntry( const OUString& rName ) const
133
8.24k
{
134
8.24k
    comphelper::SequenceAsHashMap::const_iterator aPropertyIter = find( PROP_COMPONENTDATA );
135
8.24k
    if( aPropertyIter != end() )
136
3
        return comphelper::NamedValueCollection( aPropertyIter->second ).get( rName );
137
8.24k
    return css::uno::Any();
138
8.24k
}
139
140
void MediaDescriptor::setComponentDataEntry( const OUString& rName, const css::uno::Any& rValue )
141
28
{
142
28
    if( rValue.hasValue() )
143
28
    {
144
        // get or create the 'ComponentData' property entry
145
28
        css::uno::Any& rCompDataAny = operator[]( PROP_COMPONENTDATA );
146
        // insert the value (retain sequence type, create NamedValue elements by default)
147
28
        bool bHasNamedValues = !rCompDataAny.hasValue() || rCompDataAny.has< css::uno::Sequence< css::beans::NamedValue > >();
148
28
        bool bHasPropValues = rCompDataAny.has< css::uno::Sequence< css::beans::PropertyValue > >();
149
28
        OSL_ENSURE( bHasNamedValues || bHasPropValues, "MediaDescriptor::setComponentDataEntry - incompatible 'ComponentData' property in media descriptor" );
150
28
        if( bHasNamedValues || bHasPropValues )
151
28
        {
152
            // insert or overwrite the passed value
153
28
            comphelper::SequenceAsHashMap aCompDataMap( rCompDataAny );
154
28
            aCompDataMap[ rName ] = rValue;
155
            // write back the sequence (restore sequence with correct element type)
156
28
            rCompDataAny = aCompDataMap.getAsConstAny( bHasPropValues );
157
28
        }
158
28
    }
159
0
    else
160
0
    {
161
        // if an empty Any is passed, clear the entry
162
0
        clearComponentDataEntry( rName );
163
0
    }
164
28
}
165
166
void MediaDescriptor::clearComponentDataEntry( const OUString& rName )
167
0
{
168
0
    comphelper::SequenceAsHashMap::iterator aPropertyIter = find( PROP_COMPONENTDATA );
169
0
    if( aPropertyIter == end() )
170
0
        return;
171
172
0
    css::uno::Any& rCompDataAny = aPropertyIter->second;
173
0
    bool bHasNamedValues = rCompDataAny.has< css::uno::Sequence< css::beans::NamedValue > >();
174
0
    bool bHasPropValues = rCompDataAny.has< css::uno::Sequence< css::beans::PropertyValue > >();
175
0
    OSL_ENSURE( bHasNamedValues || bHasPropValues, "MediaDescriptor::clearComponentDataEntry - incompatible 'ComponentData' property in media descriptor" );
176
0
    if( bHasNamedValues || bHasPropValues )
177
0
    {
178
        // remove the value with the passed name
179
0
        comphelper::SequenceAsHashMap aCompDataMap( rCompDataAny );
180
0
        aCompDataMap.erase( rName );
181
        // write back the sequence, or remove it completely if it is empty
182
0
        if( aCompDataMap.empty() )
183
0
            erase( aPropertyIter );
184
0
        else
185
0
            rCompDataAny = aCompDataMap.getAsConstAny( bHasPropValues );
186
0
    }
187
0
}
188
189
css::uno::Sequence< css::beans::NamedValue > MediaDescriptor::requestAndVerifyDocPassword(
190
        comphelper::IDocPasswordVerifier& rVerifier,
191
        comphelper::DocPasswordRequestType eRequestType,
192
        const ::std::vector< OUString >* pDefaultPasswords )
193
47
{
194
47
    css::uno::Sequence< css::beans::NamedValue > aMediaEncData = getUnpackedValueOrDefault(
195
47
        PROP_ENCRYPTIONDATA, css::uno::Sequence< css::beans::NamedValue >() );
196
47
    OUString aMediaPassword = getUnpackedValueOrDefault(
197
47
        PROP_PASSWORD, OUString() );
198
47
    css::uno::Reference< css::task::XInteractionHandler > xInteractHandler = getUnpackedValueOrDefault(
199
47
        PROP_INTERACTIONHANDLER, css::uno::Reference< css::task::XInteractionHandler >() );
200
47
    OUString aDocumentName = getUnpackedValueOrDefault(
201
47
        PROP_URL, OUString() );
202
203
47
    bool bIsDefaultPassword = false;
204
47
    css::uno::Sequence< css::beans::NamedValue > aEncryptionData = comphelper::DocPasswordHelper::requestAndVerifyDocPassword(
205
47
        rVerifier, aMediaEncData, aMediaPassword, xInteractHandler, aDocumentName, eRequestType, pDefaultPasswords, &bIsDefaultPassword );
206
207
47
    erase( PROP_PASSWORD );
208
47
    erase( PROP_ENCRYPTIONDATA );
209
210
    // insert encryption info into media descriptor
211
    // TODO
212
47
    if( aEncryptionData.hasElements() )
213
28
        (*this)[ PROP_ENCRYPTIONDATA ] <<= aEncryptionData;
214
215
47
    return aEncryptionData;
216
47
}
217
218
bool MediaDescriptor::addInputStream()
219
65.2k
{
220
65.2k
    return impl_addInputStream( true );
221
65.2k
}
222
223
/*-----------------------------------------------*/
224
bool MediaDescriptor::addInputStreamOwnLock()
225
1.64k
{
226
1.64k
    const bool bLock = !comphelper::IsFuzzing()
227
1.64k
        && officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get();
228
1.64k
    return impl_addInputStream(bLock);
229
1.64k
}
230
231
/*-----------------------------------------------*/
232
bool MediaDescriptor::impl_addInputStream( bool bLockFile )
233
66.9k
{
234
    // check for an already existing stream item first
235
66.9k
    const_iterator pIt = find(MediaDescriptor::PROP_INPUTSTREAM);
236
66.9k
    if (pIt != end())
237
22.8k
        return true;
238
239
44.1k
    try
240
44.1k
    {
241
        // No stream available - create a new one
242
        // a) data comes as PostData ...
243
44.1k
        pIt = find(MediaDescriptor::PROP_POSTDATA);
244
44.1k
        if (pIt != end())
245
0
        {
246
0
            const css::uno::Any& rPostData = pIt->second;
247
0
            css::uno::Reference< css::io::XInputStream > xPostData;
248
0
            rPostData >>= xPostData;
249
250
0
            return impl_openStreamWithPostData( xPostData );
251
0
        }
252
253
        // b) ... or we must get it from the given URL
254
44.1k
        OUString sURL = getUnpackedValueOrDefault(MediaDescriptor::PROP_URL, OUString());
255
44.1k
        if (sURL.isEmpty())
256
18.5k
            throw css::uno::Exception(u"Found no URL."_ustr,
257
18.5k
                    css::uno::Reference< css::uno::XInterface >());
258
259
25.6k
        return impl_openStreamWithURL( removeFragment(sURL), bLockFile );
260
44.1k
    }
261
44.1k
    catch(const css::uno::Exception&)
262
44.1k
    {
263
18.5k
        TOOLS_WARN_EXCEPTION("unotools.misc", "invalid MediaDescriptor detected");
264
18.5k
        return false;
265
18.5k
    }
266
44.1k
}
267
268
bool MediaDescriptor::impl_openStreamWithPostData( const css::uno::Reference< css::io::XInputStream >& _rxPostData )
269
0
{
270
0
    if ( !_rxPostData.is() )
271
0
        throw css::lang::IllegalArgumentException(u"Found invalid PostData."_ustr,
272
0
                css::uno::Reference< css::uno::XInterface >(), 1);
273
274
    // PostData can't be used in read/write mode!
275
0
    (*this)[MediaDescriptor::PROP_READONLY] <<= true;
276
277
    // prepare the environment
278
0
    css::uno::Reference< css::task::XInteractionHandler > xInteraction = getUnpackedValueOrDefault(
279
0
        MediaDescriptor::PROP_INTERACTIONHANDLER,
280
0
        css::uno::Reference< css::task::XInteractionHandler >());
281
0
    css::uno::Reference< css::ucb::XProgressHandler > xProgress;
282
0
    rtl::Reference<::ucbhelper::CommandEnvironment> xCommandEnv = new ::ucbhelper::CommandEnvironment(xInteraction, xProgress);
283
284
    // media type
285
0
    OUString sMediaType = getUnpackedValueOrDefault(MediaDescriptor::PROP_MEDIATYPE, OUString());
286
0
    if (sMediaType.isEmpty())
287
0
    {
288
0
        sMediaType = "application/x-www-form-urlencoded";
289
0
        (*this)[MediaDescriptor::PROP_MEDIATYPE] <<= sMediaType;
290
0
    }
291
292
    // url
293
0
    OUString sURL( getUnpackedValueOrDefault( PROP_URL, OUString() ) );
294
295
0
    css::uno::Reference< css::io::XInputStream > xResultStream;
296
0
    try
297
0
    {
298
        // seek PostData stream to the beginning
299
0
        css::uno::Reference< css::io::XSeekable > xSeek( _rxPostData, css::uno::UNO_QUERY );
300
0
        if ( xSeek.is() )
301
0
            xSeek->seek( 0 );
302
303
        // a content for the URL
304
0
        ::ucbhelper::Content aContent( sURL, xCommandEnv, comphelper::getProcessComponentContext() );
305
306
        // use post command
307
0
        css::ucb::PostCommandArgument2 aPostArgument;
308
0
        aPostArgument.Source = _rxPostData;
309
0
        css::uno::Reference< css::io::XActiveDataSink > xSink( new ucbhelper::ActiveDataSink );
310
0
        aPostArgument.Sink = xSink;
311
0
        aPostArgument.MediaType = sMediaType;
312
0
        aPostArgument.Referer = getUnpackedValueOrDefault( PROP_REFERRER, OUString() );
313
314
0
        aContent.executeCommand( u"post"_ustr, css::uno::Any( aPostArgument ) );
315
316
        // get result
317
0
        xResultStream = xSink->getInputStream();
318
0
    }
319
0
    catch( const css::uno::Exception& )
320
0
    {
321
0
    }
322
323
    // success?
324
0
    if ( !xResultStream.is() )
325
0
    {
326
0
        OSL_FAIL( "no valid reply to the HTTP-Post" );
327
0
        return false;
328
0
    }
329
330
0
    (*this)[MediaDescriptor::PROP_INPUTSTREAM] <<= xResultStream;
331
0
    return true;
332
0
}
333
334
/*-----------------------------------------------*/
335
bool MediaDescriptor::impl_openStreamWithURL( const OUString& sURL, bool bLockFile )
336
25.6k
{
337
25.6k
    if (sURL.matchIgnoreAsciiCase(".component:") || sURL.matchIgnoreAsciiCase("private:factory/"))
338
0
        return false; // No UCB content for .component URLs and factory URLs
339
340
341
25.6k
    if (INetURLObject(sURL).IsExoticProtocol())
342
0
        return false;
343
344
25.6k
    OUString referer(getUnpackedValueOrDefault(PROP_REFERRER, OUString()));
345
25.6k
    if (SvtSecurityOptions::isUntrustedReferer(referer)) {
346
0
        return false;
347
0
    }
348
349
    // prepare the environment
350
25.6k
    css::uno::Reference< css::task::XInteractionHandler > xOrgInteraction = getUnpackedValueOrDefault(
351
25.6k
        MediaDescriptor::PROP_INTERACTIONHANDLER,
352
25.6k
        css::uno::Reference< css::task::XInteractionHandler >());
353
354
25.6k
    css::uno::Reference< css::task::XInteractionHandler > xAuthenticationInteraction = getUnpackedValueOrDefault(
355
25.6k
        MediaDescriptor::PROP_AUTHENTICATIONHANDLER,
356
25.6k
        css::uno::Reference< css::task::XInteractionHandler >());
357
358
25.6k
    rtl::Reference<comphelper::StillReadWriteInteraction> xInteraction = new comphelper::StillReadWriteInteraction(xOrgInteraction,xAuthenticationInteraction);
359
360
25.6k
    css::uno::Reference< css::ucb::XProgressHandler > xProgress;
361
25.6k
    rtl::Reference<::ucbhelper::CommandEnvironment> xCommandEnv = new ::ucbhelper::CommandEnvironment(xInteraction, xProgress);
362
363
    // try to create the content
364
    // no content -> no stream => return immediately with FALSE
365
25.6k
    ::ucbhelper::Content                      aContent;
366
25.6k
    css::uno::Reference< css::ucb::XContent > xContent;
367
25.6k
    try
368
25.6k
    {
369
25.6k
        aContent = ::ucbhelper::Content(sURL, xCommandEnv, comphelper::getProcessComponentContext());
370
25.6k
        xContent = aContent.get();
371
25.6k
    }
372
25.6k
    catch(const css::uno::RuntimeException&)
373
25.6k
        { throw; }
374
25.6k
    catch(const css::ucb::ContentCreationException&)
375
25.6k
        {
376
25.6k
            TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL << "'");
377
25.6k
            return false; // TODO error handling
378
25.6k
        }
379
25.6k
    catch(const css::uno::Exception&)
380
25.6k
        {
381
0
            TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL << "'");
382
0
            return false; // TODO error handling
383
0
        }
384
385
    // try to open the file in read/write mode
386
    // (if it's allowed to do so).
387
    // But handle errors in a "hidden mode". Because
388
    // we try it readonly later - if read/write is not an option.
389
0
    css::uno::Reference< css::io::XStream >      xStream;
390
0
    css::uno::Reference< css::io::XInputStream > xInputStream;
391
392
0
    bool bReadOnly = false;
393
0
    bool bModeRequestedExplicitly = false;
394
0
    const_iterator pIt = find(MediaDescriptor::PROP_READONLY);
395
0
    if (pIt != end())
396
0
    {
397
0
        pIt->second >>= bReadOnly;
398
0
        bModeRequestedExplicitly = true;
399
0
    }
400
401
0
    if ( !bReadOnly && bLockFile )
402
0
    {
403
0
        try
404
0
        {
405
            // TODO: use "special" still interaction to suppress error messages
406
0
            xStream = aContent.openWriteableStream();
407
0
            if (xStream.is())
408
0
                xInputStream = xStream->getInputStream();
409
0
        }
410
0
        catch(const css::uno::RuntimeException&)
411
0
            { throw; }
412
0
        catch(const css::uno::Exception&)
413
0
            {
414
0
                css::uno::Any ex( cppu::getCaughtException() );
415
                // ignore exception, if reason was problem reasoned on
416
                // open it in WRITABLE mode! Then we try it READONLY
417
                // later a second time.
418
                // All other errors must be handled as real error an
419
                // break this method.
420
0
                if (!xInteraction->wasWriteError() || bModeRequestedExplicitly)
421
0
                {
422
0
                    SAL_WARN("unotools.misc","url: '" << sURL << "' " << exceptionToString(ex));
423
                    // If the protocol is webdav, then we need to treat the stream as readonly, even if the
424
                    // operation was requested as read/write explicitly (the WebDAV UCB implementation is monodirectional
425
                    // read or write not both at the same time).
426
0
                    if ( !INetURLObject( sURL ).isAnyKnownWebDAVScheme() )
427
0
                        return false;
428
0
                }
429
0
                xStream.clear();
430
0
                xInputStream.clear();
431
0
            }
432
0
    }
433
434
    // If opening of the stream in read/write mode was not allowed
435
    // or failed by an error - we must try it in readonly mode.
436
0
    if (!xInputStream.is())
437
0
    {
438
0
        OUString aScheme;
439
440
0
        try
441
0
        {
442
0
            css::uno::Reference< css::ucb::XContentIdentifier > xContId(
443
0
                aContent.get().is() ? aContent.get()->getIdentifier() : nullptr );
444
445
0
            if ( xContId.is() )
446
0
                aScheme = xContId->getContentProviderScheme();
447
448
            // Only file system content provider is able to provide XStream
449
            // so for this content impossibility to create XStream triggers
450
            // switch to readonly mode in case of opening with locking on
451
0
            if( bLockFile && aScheme.equalsIgnoreAsciiCase("file") )
452
0
                bReadOnly = true;
453
0
            else
454
0
            {
455
0
                bool bRequestReadOnly = bReadOnly;
456
0
                aContent.getPropertyValue(u"IsReadOnly"_ustr) >>= bReadOnly;
457
0
                if ( bReadOnly && !bRequestReadOnly && bModeRequestedExplicitly )
458
0
                        return false; // the document is explicitly requested with WRITABLE mode
459
0
            }
460
0
        }
461
0
        catch(const css::uno::RuntimeException&)
462
0
            { throw; }
463
0
        catch(const css::uno::Exception&)
464
0
            { /* no error handling if IsReadOnly property does not exist for UCP */ }
465
466
0
        if ( bReadOnly )
467
0
               (*this)[MediaDescriptor::PROP_READONLY] <<= bReadOnly;
468
469
0
        xInteraction->resetInterceptions();
470
0
        xInteraction->resetErrorStates();
471
0
        try
472
0
        {
473
            // all the contents except file-URLs should be opened as usual
474
0
            if ( bLockFile || !aScheme.equalsIgnoreAsciiCase("file") )
475
0
                xInputStream = aContent.openStream();
476
0
            else
477
0
                xInputStream = aContent.openStreamNoLock();
478
0
        }
479
0
        catch(const css::uno::RuntimeException&)
480
0
        {
481
0
            throw;
482
0
        }
483
0
        catch(const css::uno::Exception&)
484
0
        {
485
0
            TOOLS_INFO_EXCEPTION("unotools.misc","url: '" << sURL << "'");
486
0
            return false;
487
0
        }
488
0
    }
489
490
    // add streams to the descriptor
491
0
    if (xContent.is())
492
0
        (*this)[MediaDescriptor::PROP_UCBCONTENT] <<= xContent;
493
0
    if (xStream.is())
494
0
        (*this)[MediaDescriptor::PROP_STREAM] <<= xStream;
495
0
    if (xInputStream.is())
496
0
        (*this)[MediaDescriptor::PROP_INPUTSTREAM] <<= xInputStream;
497
498
    // At least we need an input stream. The r/w stream is optional ...
499
0
    return xInputStream.is();
500
0
}
501
502
} // namespace comphelper
503
504
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */