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>
39 #include <tools/diagnose_ex.h>
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 
49 namespace ucb::ucp::ext
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 
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 
154  OUString SAL_CALL Content::getImplementationName()
155  {
156  return "org.openoffice.comp.ucp.ext.Content";
157  }
158 
159 
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( const OUString& 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 ( i_rContentIdentifier.match( sRootURL )
290  && ( i_rContentIdentifier.getLength() == sRootURL.getLength() + 1 )
291  && ( i_rContentIdentifier[ i_rContentIdentifier.getLength() - 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 
313  case E_EXTENSION_CONTENT:
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 
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:
448  case E_EXTENSION_CONTENT:
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 = static_cast< cppu::OWeakObject * >( this );
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: */
css::uno::Reference< css::uno::XComponentContext > m_xContext
static bool denotesRootContent(const OUString &i_rContentIdentifier)
Reference< XRow > xRow
static OUString getArtificialNodeContentType()
virtual void SAL_CALL abort(sal_Int32 CommandId) override
virtual OUString getParentURL() override
virtual css::uno::Any SAL_CALL execute(const css::ucb::Command &aCommand, sal_Int32 CommandId, const css::uno::Reference< css::ucb::XCommandEnvironment > &Environment) override
PropertiesInfo aProperties
OUString getPhysicalURL() const
retrieves the URL of the underlying physical content.
#define SAL_N_ELEMENTS(arr)
virtual css::uno::Sequence< css::beans::Property > getProperties(const css::uno::Reference< css::ucb::XCommandEnvironment > &i_rEnv) override
#define DBG_UNHANDLED_EXCEPTION(...)
int i
css::uno::Reference< css::ucb::XContentIdentifier > m_xIdentifier
virtual OUString SAL_CALL getContentType() override
#define ENSURE_OR_RETURN(c, m, r)
css::uno::Reference< css::beans::XPropertySetInfo > getPropertySetInfo(const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv, bool bCache=true)
float u
css::uno::Reference< css::ucb::XCommandInfo > getCommandInfo(const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv, bool bCache=true)
PropertyValueVector_t aPropertyValues
::std::optional< bool > m_aIsFolder
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 css::uno::Sequence< css::ucb::CommandInfo > getCommands(const css::uno::Reference< css::ucb::XCommandEnvironment > &i_rEnv) override
::ucbhelper::ContentImplHelper Content_Base
#define SAL_INFO(area, stream)
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
::std::optional< OUString > m_aContentType
ExtensionContentType m_eExtContentType
Any value
constexpr bool ends_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
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)
css::uno::Sequence< css::uno::Any > setPropertyValues(const css::uno::Sequence< css::beans::PropertyValue > &rValues)
DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getIdentifier(css::uno::Reference< css::deployment::XPackage > const &package)
virtual ~Content() override
AnyEventRef aEvent
virtual OUString SAL_CALL getImplementationName() override
css::uno::Reference< css::sdbc::XRow > getPropertyValues(const css::uno::Sequence< css::beans::Property > &rProperties, const css::uno::Reference< css::ucb::XCommandEnvironment > &xEnv)
static OUString decodeIdentifier(const OUString &i_rIdentifier)