LibreOffice Module ucb (master) 1
ucpext_content.cxx
Go to the documentation of this file.
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 "ucpext_content.hxx"
21#include "ucpext_provider.hxx"
22#include "ucpext_resultset.hxx"
23
24#include <com/sun/star/beans/PropertyAttribute.hpp>
25#include <com/sun/star/beans/XPropertySetInfo.hpp>
26#include <com/sun/star/lang/IllegalAccessException.hpp>
27#include <com/sun/star/sdbc/XRow.hpp>
28#include <com/sun/star/ucb/XCommandInfo.hpp>
29#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
30#include <com/sun/star/ucb/OpenMode.hpp>
31#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
32#include <com/sun/star/ucb/XDynamicResultSet.hpp>
33#include <com/sun/star/deployment/PackageInformationProvider.hpp>
34
35#include <o3tl/string_view.hxx>
38#include <ucbhelper/content.hxx>
40#include <rtl/ustrbuf.hxx>
41#include <rtl/uri.hxx>
42#include <sal/macros.h>
43#include <sal/log.hxx>
44
45#include <algorithm>
46#include <string_view>
47
48
50{
51
52
53 using ::com::sun::star::uno::Reference;
54 using ::com::sun::star::uno::UNO_SET_THROW;
55 using ::com::sun::star::uno::Exception;
56 using ::com::sun::star::uno::Any;
57 using ::com::sun::star::uno::Sequence;
58 using ::com::sun::star::uno::XComponentContext;
59 using ::com::sun::star::ucb::XContentIdentifier;
60 using ::com::sun::star::ucb::XCommandEnvironment;
61 using ::com::sun::star::ucb::Command;
62 using ::com::sun::star::beans::Property;
63 using ::com::sun::star::lang::IllegalArgumentException;
64 using ::com::sun::star::beans::PropertyValue;
65 using ::com::sun::star::ucb::OpenCommandArgument2;
66 using ::com::sun::star::ucb::XDynamicResultSet;
67 using ::com::sun::star::ucb::UnsupportedCommandException;
68 using ::com::sun::star::sdbc::XRow;
69 using ::com::sun::star::beans::PropertyChangeEvent;
70 using ::com::sun::star::lang::IllegalAccessException;
71 using ::com::sun::star::ucb::CommandInfo;
72 using ::com::sun::star::deployment::PackageInformationProvider;
73 using ::com::sun::star::deployment::XPackageInformationProvider;
74
75 namespace OpenMode = ::com::sun::star::ucb::OpenMode;
76 namespace PropertyAttribute = ::com::sun::star::beans::PropertyAttribute;
77
78
79 //= helper
80
81 namespace
82 {
83
84 OUString lcl_compose( std::u16string_view i_rBaseURL, const OUString& i_rRelativeURL )
85 {
86 ENSURE_OR_RETURN( !i_rBaseURL.empty(), "illegal base URL", i_rRelativeURL );
87
88 OUStringBuffer aComposer( i_rBaseURL );
89 if ( !o3tl::ends_with(i_rBaseURL, u"/") )
90 aComposer.append( '/' );
91 aComposer.append( i_rRelativeURL );
92 return aComposer.makeStringAndClear();
93 }
94
95
96 struct SelectPropertyName
97 {
98 const OUString& operator()( const Property& i_rProperty ) const
99 {
100 return i_rProperty.Name;
101 }
102 };
103 }
104
105
106 //= Content
107
108
109 Content::Content( const Reference< XComponentContext >& rxContext, ::ucbhelper::ContentProviderImplHelper* i_pProvider,
110 const Reference< XContentIdentifier >& i_rIdentifier )
111 :Content_Base( rxContext, i_pProvider, i_rIdentifier )
112 ,m_eExtContentType( E_UNKNOWN )
113 {
114 const OUString sURL( getIdentifier()->getContentIdentifier() );
115 if ( denotesRootContent( sURL ) )
116 {
117 m_eExtContentType = E_ROOT;
118 }
119 else
120 {
121 const std::u16string_view sRelativeURL( sURL.subView( ContentProvider::getRootURL().getLength() ) );
122 const size_t nSepPos = sRelativeURL.find( '/' );
123 if ( ( nSepPos == std::u16string_view::npos ) || ( nSepPos == sRelativeURL.size() - 1 ) )
124 {
125 m_eExtContentType = E_EXTENSION_ROOT;
126 }
127 else
128 {
129 m_eExtContentType = E_EXTENSION_CONTENT;
130 }
131 }
132
133 if ( m_eExtContentType == E_ROOT )
134 return;
135
136 const OUString sRootURL = ContentProvider::getRootURL();
137 m_sExtensionId = sURL.copy( sRootURL.getLength() );
138
139 const sal_Int32 nNextSep = m_sExtensionId.indexOf( '/' );
140 if ( nNextSep > -1 )
141 {
142 m_sPathIntoExtension = m_sExtensionId.copy( nNextSep + 1 );
143 m_sExtensionId = m_sExtensionId.copy( 0, nNextSep );
144 }
145 m_sExtensionId = Content::decodeIdentifier( m_sExtensionId );
146 }
147
148
150 {
151 }
152
153
155 {
156 return "org.openoffice.comp.ucp.ext.Content";
157 }
158
159
160 Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
161 {
162 return { "com.sun.star.ucb.Content", "com.sun.star.ucb.ExtensionContent" };
163 }
164
165
166 OUString SAL_CALL Content::getContentType()
167 {
169 return *m_aContentType;
170 }
171
172
173 Any SAL_CALL Content::execute( const Command& aCommand, sal_Int32 /* CommandId */, const Reference< XCommandEnvironment >& i_rEnvironment )
174 {
175 Any aRet;
176
177 if ( aCommand.Name == "getPropertyValues" )
178 {
179 Sequence< Property > Properties;
180 if ( !( aCommand.Argument >>= Properties ) )
181 {
182 ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
183 OUString(), *this, -1 ) ),
184 i_rEnvironment );
185 // unreachable
186 }
187
188 aRet <<= getPropertyValues( Properties, i_rEnvironment );
189 }
190 else if ( aCommand.Name == "setPropertyValues" )
191 {
192 Sequence< PropertyValue > aProperties;
193 if ( !( aCommand.Argument >>= aProperties ) )
194 {
195 ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
196 OUString(), *this, -1 ) ),
197 i_rEnvironment );
198 // unreachable
199 }
200
201 if ( !aProperties.hasElements() )
202 {
203 ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
204 OUString(), *this, -1 ) ),
205 i_rEnvironment );
206 // unreachable
207 }
208
209 aRet <<= setPropertyValues( aProperties );
210 }
211 else if ( aCommand.Name == "getPropertySetInfo" )
212 {
213 // implemented by base class.
214 aRet <<= getPropertySetInfo( i_rEnvironment );
215 }
216 else if ( aCommand.Name == "getCommandInfo" )
217 {
218 // implemented by base class.
219 aRet <<= getCommandInfo( i_rEnvironment );
220 }
221 else if ( aCommand.Name == "open" )
222 {
223 OpenCommandArgument2 aOpenCommand;
224 if ( !( aCommand.Argument >>= aOpenCommand ) )
225 {
226 ::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
227 OUString(), *this, -1 ) ),
228 i_rEnvironment );
229 // unreachable
230 }
231
232 bool bOpenFolder =
233 ( ( aOpenCommand.Mode == OpenMode::ALL ) ||
234 ( aOpenCommand.Mode == OpenMode::FOLDERS ) ||
235 ( aOpenCommand.Mode == OpenMode::DOCUMENTS ) );
236
237
238 if ( bOpenFolder && impl_isFolder() )
239 {
240 Reference< XDynamicResultSet > xSet = new ResultSet( m_xContext, this, aOpenCommand, i_rEnvironment );
241 aRet <<= xSet;
242 }
243
244 if ( aOpenCommand.Sink.is() )
245 {
246 const OUString sPhysicalContentURL( getPhysicalURL() );
247 ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnvironment, m_xContext );
248 aRet = aRequestedContent.executeCommand( "open", Any( aOpenCommand ) );
249 }
250 }
251
252 else
253 {
254 ::ucbhelper::cancelCommandExecution( Any( UnsupportedCommandException(
255 OUString(), *this ) ),
256 i_rEnvironment );
257 // unreachable
258 }
259
260 return aRet;
261 }
262
263
264 void SAL_CALL Content::abort( sal_Int32 )
265 {
266 }
267
268
269 OUString Content::encodeIdentifier( const OUString& i_rIdentifier )
270 {
271 return ::rtl::Uri::encode( i_rIdentifier, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes,
272 RTL_TEXTENCODING_UTF8 );
273 }
274
275
276 OUString Content::decodeIdentifier( const OUString& i_rIdentifier )
277 {
278 return ::rtl::Uri::decode( i_rIdentifier, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
279 }
280
281
282 bool Content::denotesRootContent( std::u16string_view i_rContentIdentifier )
283 {
284 const OUString sRootURL( ContentProvider::getRootURL() );
285 if ( i_rContentIdentifier == sRootURL )
286 return true;
287
288 // the root URL contains only two trailing /, but we also recognize 3 of them as denoting the root URL
289 if ( o3tl::starts_with(i_rContentIdentifier, sRootURL )
290 && ( sal_Int32(i_rContentIdentifier.size()) == sRootURL.getLength() + 1 )
291 && ( i_rContentIdentifier[ i_rContentIdentifier.size() - 1 ] == '/' )
292 )
293 return true;
294
295 return false;
296 }
297
298
300 {
301 const OUString sRootURL( ContentProvider::getRootURL() );
302
303 switch ( m_eExtContentType )
304 {
305 case E_ROOT:
306 // don't have a parent
307 return sRootURL;
308
309 case E_EXTENSION_ROOT:
310 // our parent is the root itself
311 return sRootURL;
312
314 {
315 const OUString sURL = m_xIdentifier->getContentIdentifier();
316
317 // cut the root URL
318 if ( !sURL.match( sRootURL ) )
319 {
320 SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no root" );
321 break;
322 }
323
324 OUString sRelativeURL( sURL.copy( sRootURL.getLength() ) );
325
326 // cut the extension ID
327 const OUString sSeparatedExtensionId( encodeIdentifier( m_sExtensionId ) + "/" );
328 if ( !sRelativeURL.match( sSeparatedExtensionId ) )
329 {
330 SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no extension ID" );
331 break;
332 }
333
334 sRelativeURL = sRelativeURL.copy( sSeparatedExtensionId.getLength() );
335
336 // cut the final slash (if any)
337 if ( sRelativeURL.isEmpty() )
338 {
339 SAL_INFO( "ucb.ucp.ext", "illegal URL structure - ExtensionContent should have a level below the extension ID" );
340 break;
341 }
342
343 if ( sRelativeURL.endsWith("/") )
344 sRelativeURL = sRelativeURL.copy( 0, sRelativeURL.getLength() - 1 );
345
346 // remove the last segment
347 const sal_Int32 nLastSep = sRelativeURL.lastIndexOf( '/' );
348 sRelativeURL = sRelativeURL.copy( 0, nLastSep != -1 ? nLastSep : 0 );
349
350 return sRootURL + sSeparatedExtensionId + sRelativeURL;
351 }
352
353 default:
354 OSL_FAIL( "Content::getParentURL: unhandled case!" );
355 break;
356 }
357 return OUString();
358 }
359
360
361 Reference< XRow > Content::getArtificialNodePropertyValues( const Reference< XComponentContext >& rxContext,
362 const Sequence< Property >& i_rProperties, const OUString& i_rTitle )
363 {
364 // note: empty sequence means "get values of all supported properties".
365 ::rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( rxContext );
366
367 if ( i_rProperties.hasElements() )
368 {
369 for ( const Property& rProp : i_rProperties )
370 {
371 // Process Core properties.
372 if ( rProp.Name == "ContentType" )
373 {
374 xRow->appendString ( rProp, ContentProvider::getArtificialNodeContentType() );
375 }
376 else if ( rProp.Name == "Title" )
377 {
378 xRow->appendString ( rProp, i_rTitle );
379 }
380 else if ( rProp.Name == "IsDocument" )
381 {
382 xRow->appendBoolean( rProp, false );
383 }
384 else if ( rProp.Name == "IsFolder" )
385 {
386 xRow->appendBoolean( rProp, true );
387 }
388 else
389 {
390 // append empty entry.
391 xRow->appendVoid( rProp );
392 }
393 }
394 }
395 else
396 {
397 // Append all Core Properties.
398 xRow->appendString ( Property( "ContentType",
399 -1,
401 PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
403 xRow->appendString ( Property( "Title",
404 -1,
406 PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
407 i_rTitle );
408 xRow->appendBoolean( Property( "IsDocument",
409 -1,
411 PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
412 false );
413 xRow->appendBoolean( Property( "IsFolder",
414 -1,
416 PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
417 true );
418 }
419
420 return xRow;
421 }
422
423
424 OUString Content::getPhysicalURL() const
425 {
426 ENSURE_OR_RETURN( m_eExtContentType != E_ROOT, "illegal call", OUString() );
427
428 // create a ucb::XContent for the physical file within the deployed extension
429 const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get(m_xContext);
430 const OUString sPackageLocation( xPackageInfo->getPackageLocation( m_sExtensionId ) );
431
432 if ( m_sPathIntoExtension.isEmpty() )
433 return sPackageLocation;
434 return lcl_compose( sPackageLocation, m_sPathIntoExtension );
435 }
436
437
438 Reference< XRow > Content::getPropertyValues( const Sequence< Property >& i_rProperties, const Reference< XCommandEnvironment >& i_rEnv )
439 {
440 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
441
442 switch ( m_eExtContentType )
443 {
444 case E_ROOT:
446 case E_EXTENSION_ROOT:
449 {
450 const OUString sPhysicalContentURL( getPhysicalURL() );
451 ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnv, m_xContext );
452
453 // translate the property request
454 Sequence< OUString > aPropertyNames( i_rProperties.getLength() );
455 ::std::transform(
456 i_rProperties.begin(),
457 i_rProperties.end(),
458 aPropertyNames.getArray(),
459 SelectPropertyName()
460 );
461 const Sequence< Any > aPropertyValues = aRequestedContent.getPropertyValues( aPropertyNames );
462 const ::rtl::Reference< ::ucbhelper::PropertyValueSet > xValueRow = new ::ucbhelper::PropertyValueSet( m_xContext );
463 sal_Int32 i=0;
464 for ( const Any* value = aPropertyValues.getConstArray();
465 value != aPropertyValues.getConstArray() + aPropertyValues.getLength();
466 ++value, ++i
467 )
468 {
469 xValueRow->appendObject( aPropertyNames[i], *value );
470 }
471 return xValueRow;
472 }
473
474 default:
475 OSL_FAIL( "Content::getPropertyValues: unhandled case!" );
476 break;
477 }
478
479 OSL_FAIL( "Content::getPropertyValues: unreachable!" );
480 return nullptr;
481 }
482
483
484 Sequence< Any > Content::setPropertyValues( const Sequence< PropertyValue >& i_rValues)
485 {
486 ::osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
487
488 Sequence< Any > aRet( i_rValues.getLength() );
489
490 PropertyChangeEvent aEvent;
491 aEvent.Source = getXWeak();
492 aEvent.Further = false;
493 aEvent.PropertyHandle = -1;
494
495 for ( auto& rRet : asNonConstRange(aRet) )
496 {
497 // all our properties are read-only ...
498 rRet <<= IllegalAccessException("property is read-only.", *this );
499 }
500
501 return aRet;
502 }
503
504
505 Sequence< CommandInfo > Content::getCommands( const Reference< XCommandEnvironment > & /*xEnv*/ )
506 {
507 static const CommandInfo aCommandInfoTable[] =
508 {
509 // Mandatory commands
510
511 CommandInfo(
512 "getCommandInfo",
513 -1,
515 ),
516 CommandInfo(
517 "getPropertySetInfo",
518 -1,
520 ),
521 CommandInfo(
522 "getPropertyValues",
523 -1,
524 cppu::UnoType<Sequence< Property >>::get()
525 ),
526 CommandInfo(
527 "setPropertyValues",
528 -1,
529 cppu::UnoType<Sequence< PropertyValue >>::get()
530 )
531
532 // Optional standard commands
533
534 , CommandInfo(
535 "open",
536 -1,
538 )
539 };
540
541 return Sequence< CommandInfo >( aCommandInfoTable, SAL_N_ELEMENTS(aCommandInfoTable) );
542 }
543
544
545 Sequence< Property > Content::getProperties( const Reference< XCommandEnvironment > & /*xEnv*/ )
546 {
547 static const Property aProperties[] =
548 {
549 Property(
550 "ContentType",
551 -1,
553 PropertyAttribute::BOUND | PropertyAttribute::READONLY
554 ),
555 Property(
556 "IsDocument",
557 -1,
559 PropertyAttribute::BOUND | PropertyAttribute::READONLY
560 ),
561 Property(
562 "IsFolder",
563 -1,
565 PropertyAttribute::BOUND | PropertyAttribute::READONLY
566 ),
567 Property(
568 "Title",
569 -1,
571 PropertyAttribute::BOUND | PropertyAttribute::READONLY
572 )
573 };
574 return Sequence< Property >( aProperties, SAL_N_ELEMENTS( aProperties ) );
575 }
576
577
579 {
580 if ( !!m_aIsFolder )
581 return *m_aIsFolder;
582
583 bool bIsFolder = false;
584 try
585 {
586 Sequence< Property > aProps{ { /*Name*/ "IsFolder", {}, {}, {} } };
587 Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
588 bIsFolder = xRow->getBoolean(1);
589 }
590 catch( const Exception& )
591 {
592 DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
593 }
594 m_aIsFolder = bIsFolder;
595 return *m_aIsFolder;
596 }
597
598
600 {
601 if ( !!m_aContentType )
602 return;
603
606 return;
607
608 try
609 {
610 Sequence< Property > aProps{ { /*Name*/ "ContentType", {}, {}, {} } };
611 Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
612 m_aContentType = xRow->getString(1);
613 }
614 catch( const Exception& )
615 {
616 DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
617 }
618 }
619
620
621} // namespace ucp::ext
622
623
624/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
PropertyValueVector_t aPropertyValues
PropertiesInfo aProperties
AnyEventRef aEvent
static OUString getArtificialNodeContentType()
css::uno::Sequence< css::uno::Any > setPropertyValues(const css::uno::Sequence< css::beans::PropertyValue > &rValues)
ExtensionContentType m_eExtContentType
virtual OUString SAL_CALL getContentType() override
::std::optional< OUString > m_aContentType
virtual css::uno::Any SAL_CALL execute(const css::ucb::Command &aCommand, sal_Int32 CommandId, const css::uno::Reference< css::ucb::XCommandEnvironment > &Environment) override
css::uno::Reference< css::sdbc::XRow > getPropertyValues(const css::uno::Sequence< css::beans::Property > &rProperties, const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv)
Content(const css::uno::Reference< css::uno::XComponentContext > &rxContext, ::ucbhelper::ContentProviderImplHelper *pProvider, const css::uno::Reference< css::ucb::XContentIdentifier > &Identifier)
static OUString encodeIdentifier(const OUString &i_rIdentifier)
virtual css::uno::Sequence< css::ucb::CommandInfo > getCommands(const css::uno::Reference< css::ucb::XCommandEnvironment > &i_rEnv) override
static css::uno::Reference< css::sdbc::XRow > getArtificialNodePropertyValues(const css::uno::Reference< css::uno::XComponentContext > &rxContext, const css::uno::Sequence< css::beans::Property > &rProperties, const OUString &rTitle)
virtual OUString getParentURL() override
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
static bool denotesRootContent(std::u16string_view i_rContentIdentifier)
virtual ~Content() override
::std::optional< bool > m_aIsFolder
static OUString decodeIdentifier(const OUString &i_rIdentifier)
virtual OUString SAL_CALL getImplementationName() override
virtual css::uno::Sequence< css::beans::Property > getProperties(const css::uno::Reference< css::ucb::XCommandEnvironment > &i_rEnv) override
OUString getPhysicalURL() const
retrieves the URL of the underlying physical content.
virtual void SAL_CALL abort(sal_Int32 CommandId) override
css::uno::Reference< css::ucb::XCommandInfo > getCommandInfo(const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv, bool bCache=true)
css::uno::Reference< css::beans::XPropertySetInfo > getPropertySetInfo(const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv, bool bCache=true)
css::uno::Reference< css::ucb::XContentIdentifier > m_xIdentifier
css::uno::Reference< css::uno::XComponentContext > m_xContext
#define ENSURE_OR_RETURN(c, m, r)
#define DBG_UNHANDLED_EXCEPTION(...)
float u
#define SAL_INFO(area, stream)
#define SAL_N_ELEMENTS(arr)
@ Exception
DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getIdentifier(css::uno::Reference< css::deployment::XPackage > const &package)
int i
constexpr bool ends_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
::ucbhelper::ContentImplHelper Content_Base
OUString aCommand