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