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/CommandAbortedException.hpp>
29 #include <com/sun/star/ucb/XCommandInfo.hpp>
30 #include <com/sun/star/ucb/OpenCommandArgument2.hpp>
31 #include <com/sun/star/ucb/OpenMode.hpp>
32 #include <com/sun/star/ucb/UnsupportedCommandException.hpp>
33 #include <com/sun/star/ucb/XDynamicResultSet.hpp>
34 #include <com/sun/star/deployment/PackageInformationProvider.hpp>
35 
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 
47 
48 namespace ucb::ucp::ext
49 {
50 
51 
52  using ::com::sun::star::uno::Reference;
53  using ::com::sun::star::uno::UNO_SET_THROW;
54  using ::com::sun::star::uno::Exception;
55  using ::com::sun::star::uno::Any;
56  using ::com::sun::star::uno::makeAny;
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( const OUString& i_rBaseURL, const OUString& i_rRelativeURL )
85  {
86  ENSURE_OR_RETURN( !i_rBaseURL.isEmpty(), "illegal base URL", i_rRelativeURL );
87 
88  OUStringBuffer aComposer( i_rBaseURL );
89  if ( !i_rBaseURL.endsWith("/") )
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  ,m_aIsFolder()
114  ,m_aContentType()
115  ,m_sExtensionId()
116  ,m_sPathIntoExtension()
117  {
118  const OUString sURL( getIdentifier()->getContentIdentifier() );
119  if ( denotesRootContent( sURL ) )
120  {
121  m_eExtContentType = E_ROOT;
122  }
123  else
124  {
125  const OUString sRelativeURL( sURL.copy( ContentProvider::getRootURL().getLength() ) );
126  const sal_Int32 nSepPos = sRelativeURL.indexOf( '/' );
127  if ( ( nSepPos == -1 ) || ( nSepPos == sRelativeURL.getLength() - 1 ) )
128  {
129  m_eExtContentType = E_EXTENSION_ROOT;
130  }
131  else
132  {
133  m_eExtContentType = E_EXTENSION_CONTENT;
134  }
135  }
136 
137  if ( m_eExtContentType != E_ROOT )
138  {
139  const OUString sRootURL = ContentProvider::getRootURL();
140  m_sExtensionId = sURL.copy( sRootURL.getLength() );
141 
142  const sal_Int32 nNextSep = m_sExtensionId.indexOf( '/' );
143  if ( nNextSep > -1 )
144  {
145  m_sPathIntoExtension = m_sExtensionId.copy( nNextSep + 1 );
146  m_sExtensionId = m_sExtensionId.copy( 0, nNextSep );
147  }
148  m_sExtensionId = Content::decodeIdentifier( m_sExtensionId );
149  }
150  }
151 
152 
154  {
155  }
156 
157 
158  OUString SAL_CALL Content::getImplementationName()
159  {
160  return "org.openoffice.comp.ucp.ext.Content";
161  }
162 
163 
165  {
166  return { "com.sun.star.ucb.Content", "com.sun.star.ucb.ExtensionContent" };
167  }
168 
169 
170  OUString SAL_CALL Content::getContentType()
171  {
173  return *m_aContentType;
174  }
175 
176 
177  Any SAL_CALL Content::execute( const Command& aCommand, sal_Int32 /* CommandId */, const Reference< XCommandEnvironment >& i_rEvironment )
178  {
179  Any aRet;
180 
181  if ( aCommand.Name == "getPropertyValues" )
182  {
183  Sequence< Property > Properties;
184  if ( !( aCommand.Argument >>= Properties ) )
185  {
186  ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException(
187  OUString(), *this, -1 ) ),
188  i_rEvironment );
189  // unreachable
190  }
191 
192  aRet <<= getPropertyValues( Properties, i_rEvironment );
193  }
194  else if ( aCommand.Name == "setPropertyValues" )
195  {
196  Sequence< PropertyValue > aProperties;
197  if ( !( aCommand.Argument >>= aProperties ) )
198  {
199  ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException(
200  OUString(), *this, -1 ) ),
201  i_rEvironment );
202  // unreachable
203  }
204 
205  if ( !aProperties.hasElements() )
206  {
207  ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException(
208  OUString(), *this, -1 ) ),
209  i_rEvironment );
210  // unreachable
211  }
212 
213  aRet <<= setPropertyValues( aProperties );
214  }
215  else if ( aCommand.Name == "getPropertySetInfo" )
216  {
217  // implemented by base class.
218  aRet <<= getPropertySetInfo( i_rEvironment );
219  }
220  else if ( aCommand.Name == "getCommandInfo" )
221  {
222  // implemented by base class.
223  aRet <<= getCommandInfo( i_rEvironment );
224  }
225  else if ( aCommand.Name == "open" )
226  {
227  OpenCommandArgument2 aOpenCommand;
228  if ( !( aCommand.Argument >>= aOpenCommand ) )
229  {
230  ::ucbhelper::cancelCommandExecution( makeAny( IllegalArgumentException(
231  OUString(), *this, -1 ) ),
232  i_rEvironment );
233  // unreachable
234  }
235 
236  bool bOpenFolder =
237  ( ( aOpenCommand.Mode == OpenMode::ALL ) ||
238  ( aOpenCommand.Mode == OpenMode::FOLDERS ) ||
239  ( aOpenCommand.Mode == OpenMode::DOCUMENTS ) );
240 
241 
242  if ( bOpenFolder && impl_isFolder() )
243  {
244  Reference< XDynamicResultSet > xSet = new ResultSet( m_xContext, this, aOpenCommand, i_rEvironment );
245  aRet <<= xSet;
246  }
247 
248  if ( aOpenCommand.Sink.is() )
249  {
250  const OUString sPhysicalContentURL( getPhysicalURL() );
251  ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEvironment, m_xContext );
252  aRet = aRequestedContent.executeCommand( "open", makeAny( aOpenCommand ) );
253  }
254  }
255 
256  else
257  {
258  ::ucbhelper::cancelCommandExecution( makeAny( UnsupportedCommandException(
259  OUString(), *this ) ),
260  i_rEvironment );
261  // unreachable
262  }
263 
264  return aRet;
265  }
266 
267 
268  void SAL_CALL Content::abort( sal_Int32 )
269  {
270  }
271 
272 
273  OUString Content::encodeIdentifier( const OUString& i_rIdentifier )
274  {
275  return ::rtl::Uri::encode( i_rIdentifier, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes,
276  RTL_TEXTENCODING_UTF8 );
277  }
278 
279 
280  OUString Content::decodeIdentifier( const OUString& i_rIdentifier )
281  {
282  return ::rtl::Uri::decode( i_rIdentifier, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
283  }
284 
285 
286  bool Content::denotesRootContent( const OUString& i_rContentIdentifier )
287  {
288  const OUString sRootURL( ContentProvider::getRootURL() );
289  if ( i_rContentIdentifier == sRootURL )
290  return true;
291 
292  // the root URL contains only two trailing /, but we also recognize 3 of them as denoting the root URL
293  if ( i_rContentIdentifier.match( sRootURL )
294  && ( i_rContentIdentifier.getLength() == sRootURL.getLength() + 1 )
295  && ( i_rContentIdentifier[ i_rContentIdentifier.getLength() - 1 ] == '/' )
296  )
297  return true;
298 
299  return false;
300  }
301 
302 
304  {
305  const OUString sRootURL( ContentProvider::getRootURL() );
306 
307  switch ( m_eExtContentType )
308  {
309  case E_ROOT:
310  // don't have a parent
311  return sRootURL;
312 
313  case E_EXTENSION_ROOT:
314  // our parent is the root itself
315  return sRootURL;
316 
317  case E_EXTENSION_CONTENT:
318  {
319  const OUString sURL = m_xIdentifier->getContentIdentifier();
320 
321  // cut the root URL
322  if ( !sURL.match( sRootURL ) )
323  {
324  SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no root" );
325  break;
326  }
327 
328  OUString sRelativeURL( sURL.copy( sRootURL.getLength() ) );
329 
330  // cut the extension ID
331  const OUString sSeparatedExtensionId( encodeIdentifier( m_sExtensionId ) + "/" );
332  if ( !sRelativeURL.match( sSeparatedExtensionId ) )
333  {
334  SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no extension ID" );
335  break;
336  }
337 
338  sRelativeURL = sRelativeURL.copy( sSeparatedExtensionId.getLength() );
339 
340  // cut the final slash (if any)
341  if ( sRelativeURL.isEmpty() )
342  {
343  SAL_INFO( "ucb.ucp.ext", "illegal URL structure - ExtensionContent should have a level below the extension ID" );
344  break;
345  }
346 
347  if ( sRelativeURL.endsWith("/") )
348  sRelativeURL = sRelativeURL.copy( 0, sRelativeURL.getLength() - 1 );
349 
350  // remove the last segment
351  const sal_Int32 nLastSep = sRelativeURL.lastIndexOf( '/' );
352  sRelativeURL = sRelativeURL.copy( 0, nLastSep != -1 ? nLastSep : 0 );
353 
354  return sRootURL + sSeparatedExtensionId + sRelativeURL;
355  }
356 
357  default:
358  OSL_FAIL( "Content::getParentURL: unhandled case!" );
359  break;
360  }
361  return OUString();
362  }
363 
364 
366  const Sequence< Property >& i_rProperties, const OUString& i_rTitle )
367  {
368  // note: empty sequence means "get values of all supported properties".
369  ::rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( rxContext );
370 
371  if ( i_rProperties.hasElements() )
372  {
373  for ( const Property& rProp : i_rProperties )
374  {
375  // Process Core properties.
376  if ( rProp.Name == "ContentType" )
377  {
378  xRow->appendString ( rProp, ContentProvider::getArtificialNodeContentType() );
379  }
380  else if ( rProp.Name == "Title" )
381  {
382  xRow->appendString ( rProp, i_rTitle );
383  }
384  else if ( rProp.Name == "IsDocument" )
385  {
386  xRow->appendBoolean( rProp, false );
387  }
388  else if ( rProp.Name == "IsFolder" )
389  {
390  xRow->appendBoolean( rProp, true );
391  }
392  else
393  {
394  // append empty entry.
395  xRow->appendVoid( rProp );
396  }
397  }
398  }
399  else
400  {
401  // Append all Core Properties.
402  xRow->appendString ( Property( "ContentType",
403  -1,
405  PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
407  xRow->appendString ( Property( "Title",
408  -1,
410  PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
411  i_rTitle );
412  xRow->appendBoolean( Property( "IsDocument",
413  -1,
415  PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
416  false );
417  xRow->appendBoolean( Property( "IsFolder",
418  -1,
420  PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
421  true );
422  }
423 
424  return Reference< XRow >( xRow.get() );
425  }
426 
427 
428  OUString Content::getPhysicalURL() const
429  {
430  ENSURE_OR_RETURN( m_eExtContentType != E_ROOT, "illegal call", OUString() );
431 
432  // create a ucb::XContent for the physical file within the deployed extension
433  const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get(m_xContext);
434  const OUString sPackageLocation( xPackageInfo->getPackageLocation( m_sExtensionId ) );
435 
436  if ( m_sPathIntoExtension.isEmpty() )
437  return sPackageLocation;
438  return lcl_compose( sPackageLocation, m_sPathIntoExtension );
439  }
440 
441 
442  Reference< XRow > Content::getPropertyValues( const Sequence< Property >& i_rProperties, const Reference< XCommandEnvironment >& i_rEnv )
443  {
444  ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
445 
446  switch ( m_eExtContentType )
447  {
448  case E_ROOT:
450  case E_EXTENSION_ROOT:
452  case E_EXTENSION_CONTENT:
453  {
454  const OUString sPhysicalContentURL( getPhysicalURL() );
455  ::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnv, m_xContext );
456 
457  // translate the property request
458  Sequence< OUString > aPropertyNames( i_rProperties.getLength() );
459  ::std::transform(
460  i_rProperties.begin(),
461  i_rProperties.end(),
462  aPropertyNames.getArray(),
463  SelectPropertyName()
464  );
465  const Sequence< Any > aPropertyValues = aRequestedContent.getPropertyValues( aPropertyNames );
466  const ::rtl::Reference< ::ucbhelper::PropertyValueSet > xValueRow = new ::ucbhelper::PropertyValueSet( m_xContext );
467  sal_Int32 i=0;
468  for ( const Any* value = aPropertyValues.getConstArray();
469  value != aPropertyValues.getConstArray() + aPropertyValues.getLength();
470  ++value, ++i
471  )
472  {
473  xValueRow->appendObject( aPropertyNames[i], *value );
474  }
475  return xValueRow.get();
476  }
477 
478  default:
479  OSL_FAIL( "Content::getPropertyValues: unhandled case!" );
480  break;
481  }
482 
483  OSL_FAIL( "Content::getPropertyValues: unreachable!" );
484  return nullptr;
485  }
486 
487 
488  Sequence< Any > Content::setPropertyValues( const Sequence< PropertyValue >& i_rValues)
489  {
490  ::osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
491 
492  Sequence< Any > aRet( i_rValues.getLength() );
493 
494  PropertyChangeEvent aEvent;
495  aEvent.Source = static_cast< cppu::OWeakObject * >( this );
496  aEvent.Further = false;
497  aEvent.PropertyHandle = -1;
498 
499  for ( auto& rRet : aRet )
500  {
501  // all our properties are read-only ...
502  rRet <<= IllegalAccessException("property is read-only.", *this );
503  }
504 
505  return aRet;
506  }
507 
508 
509  Sequence< CommandInfo > Content::getCommands( const Reference< XCommandEnvironment > & /*xEnv*/ )
510  {
511  static const CommandInfo aCommandInfoTable[] =
512  {
513  // Mandatory commands
514 
515  CommandInfo(
516  "getCommandInfo",
517  -1,
519  ),
520  CommandInfo(
521  "getPropertySetInfo",
522  -1,
524  ),
525  CommandInfo(
526  "getPropertyValues",
527  -1,
528  cppu::UnoType<Sequence< Property >>::get()
529  ),
530  CommandInfo(
531  "setPropertyValues",
532  -1,
533  cppu::UnoType<Sequence< PropertyValue >>::get()
534  )
535 
536  // Optional standard commands
537 
538  , CommandInfo(
539  "open",
540  -1,
542  )
543  };
544 
545  return Sequence< CommandInfo >( aCommandInfoTable, SAL_N_ELEMENTS(aCommandInfoTable) );
546  }
547 
548 
549  Sequence< Property > Content::getProperties( const Reference< XCommandEnvironment > & /*xEnv*/ )
550  {
551  static const Property aProperties[] =
552  {
553  Property(
554  "ContentType",
555  -1,
557  PropertyAttribute::BOUND | PropertyAttribute::READONLY
558  ),
559  Property(
560  "IsDocument",
561  -1,
563  PropertyAttribute::BOUND | PropertyAttribute::READONLY
564  ),
565  Property(
566  "IsFolder",
567  -1,
569  PropertyAttribute::BOUND | PropertyAttribute::READONLY
570  ),
571  Property(
572  "Title",
573  -1,
575  PropertyAttribute::BOUND | PropertyAttribute::READONLY
576  )
577  };
578  return Sequence< Property >( aProperties, SAL_N_ELEMENTS( aProperties ) );
579  }
580 
581 
583  {
584  if ( !!m_aIsFolder )
585  return *m_aIsFolder;
586 
587  bool bIsFolder = false;
588  try
589  {
590  Sequence< Property > aProps(1);
591  aProps[0].Name = "IsFolder";
592  Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
593  bIsFolder = xRow->getBoolean(1);
594  }
595  catch( const Exception& )
596  {
597  DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
598  }
599  m_aIsFolder = bIsFolder;
600  return *m_aIsFolder;
601  }
602 
603 
605  {
606  if ( !!m_aContentType )
607  return;
608 
611  {
612  try
613  {
614  Sequence< Property > aProps(1);
615  aProps[0].Name = "ContentType";
616  Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
617  m_aContentType = xRow->getString(1);
618  }
619  catch( const Exception& )
620  {
621  DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
622  }
623  }
624  }
625 
626 
627 } // namespace ucp::ext
628 
629 
630 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
css::uno::Reference< css::uno::XComponentContext > m_xContext
static bool denotesRootContent(const OUString &i_rContentIdentifier)
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
Reference< XRow > xRow
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)
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
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)
css::uno::Any SAL_CALL makeAny(const SharedUNOComponent< INTERFACE, COMPONENT > &value)