/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include "ucpext_content.hxx"
#include "ucpext_provider.hxx"
#include "ucpext_resultset.hxx"
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/lang/IllegalAccessException.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/ucb/XCommandInfo.hpp>
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
#include <com/sun/star/ucb/XDynamicResultSet.hpp>
#include <com/sun/star/deployment/PackageInformationProvider.hpp>
#include <o3tl/string_view.hxx>
#include <ucbhelper/propertyvalueset.hxx>
#include <ucbhelper/cancelcommandexecution.hxx>
#include <ucbhelper/content.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/uri.hxx>
#include <sal/macros.h>
#include <sal/log.hxx>
#include <algorithm>
#include <string_view>
namespace ucb::ucp::ext
{
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::UNO_SET_THROW;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::XComponentContext;
using ::com::sun::star::ucb::XContentIdentifier;
using ::com::sun::star::ucb::XCommandEnvironment;
using ::com::sun::star::ucb::Command;
using ::com::sun::star::beans::Property;
using ::com::sun::star::lang::IllegalArgumentException;
using ::com::sun::star::beans::PropertyValue;
using ::com::sun::star::ucb::OpenCommandArgument2;
using ::com::sun::star::ucb::XDynamicResultSet;
using ::com::sun::star::ucb::UnsupportedCommandException;
using ::com::sun::star::sdbc::XRow;
using ::com::sun::star::beans::PropertyChangeEvent;
using ::com::sun::star::lang::IllegalAccessException;
using ::com::sun::star::ucb::CommandInfo;
using ::com::sun::star::deployment::PackageInformationProvider;
using ::com::sun::star::deployment::XPackageInformationProvider;
namespace OpenMode = css::ucb::OpenMode;
namespace PropertyAttribute = css::beans::PropertyAttribute;
//= helper
namespace
{
OUString lcl_compose( std::u16string_view i_rBaseURL, const OUString& i_rRelativeURL )
{
ENSURE_OR_RETURN( !i_rBaseURL.empty(), "illegal base URL", i_rRelativeURL );
OUStringBuffer aComposer( i_rBaseURL );
if ( !o3tl::ends_with(i_rBaseURL, u"/") )
aComposer.append( '/' );
aComposer.append( i_rRelativeURL );
return aComposer.makeStringAndClear();
}
struct SelectPropertyName
{
const OUString& operator()( const Property& i_rProperty ) const
{
return i_rProperty.Name;
}
};
}
//= Content
Content::Content( const Reference< XComponentContext >& rxContext, ::ucbhelper::ContentProviderImplHelper* i_pProvider,
const Reference< XContentIdentifier >& i_rIdentifier )
:Content_Base( rxContext, i_pProvider, i_rIdentifier )
,m_eExtContentType( E_UNKNOWN )
{
const OUString sURL( getIdentifier()->getContentIdentifier() );
if ( denotesRootContent( sURL ) )
{
m_eExtContentType = E_ROOT;
}
else
{
const std::u16string_view sRelativeURL( sURL.subView( ContentProvider::getRootURL().getLength() ) );
const size_t nSepPos = sRelativeURL.find( '/' );
if ( ( nSepPos == std::u16string_view::npos ) || ( nSepPos == sRelativeURL.size() - 1 ) )
{
m_eExtContentType = E_EXTENSION_ROOT;
}
else
{
m_eExtContentType = E_EXTENSION_CONTENT;
}
}
if ( m_eExtContentType == E_ROOT )
return;
const OUString sRootURL = ContentProvider::getRootURL();
m_sExtensionId = sURL.copy( sRootURL.getLength() );
const sal_Int32 nNextSep = m_sExtensionId.indexOf( '/' );
if ( nNextSep > -1 )
{
m_sPathIntoExtension = m_sExtensionId.copy( nNextSep + 1 );
m_sExtensionId = m_sExtensionId.copy( 0, nNextSep );
}
m_sExtensionId = Content::decodeIdentifier( m_sExtensionId );
}
Content::~Content()
{
}
OUString SAL_CALL Content::getImplementationName()
{
return u"org.openoffice.comp.ucp.ext.Content"_ustr;
}
Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
{
return { u"com.sun.star.ucb.Content"_ustr, u"com.sun.star.ucb.ExtensionContent"_ustr };
}
OUString SAL_CALL Content::getContentType()
{
impl_determineContentType();
return *m_aContentType;
}
Any SAL_CALL Content::execute( const Command& aCommand, sal_Int32 /* CommandId */, const Reference< XCommandEnvironment >& i_rEnvironment )
{
Any aRet;
if ( aCommand.Name == "getPropertyValues" )
{
Sequence< Property > Properties;
if ( !( aCommand.Argument >>= Properties ) )
{
::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
OUString(), *this, -1 ) ),
i_rEnvironment );
// unreachable
}
aRet <<= getPropertyValues( Properties, i_rEnvironment );
}
else if ( aCommand.Name == "setPropertyValues" )
{
Sequence< PropertyValue > aProperties;
if ( !( aCommand.Argument >>= aProperties ) )
{
::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
OUString(), *this, -1 ) ),
i_rEnvironment );
// unreachable
}
if ( !aProperties.hasElements() )
{
::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
OUString(), *this, -1 ) ),
i_rEnvironment );
// unreachable
}
aRet <<= setPropertyValues( aProperties );
}
else if ( aCommand.Name == "getPropertySetInfo" )
{
// implemented by base class.
aRet <<= getPropertySetInfo( i_rEnvironment );
}
else if ( aCommand.Name == "getCommandInfo" )
{
// implemented by base class.
aRet <<= getCommandInfo( i_rEnvironment );
}
else if ( aCommand.Name == "open" )
{
OpenCommandArgument2 aOpenCommand;
if ( !( aCommand.Argument >>= aOpenCommand ) )
{
::ucbhelper::cancelCommandExecution( Any( IllegalArgumentException(
OUString(), *this, -1 ) ),
i_rEnvironment );
// unreachable
}
bool bOpenFolder =
( ( aOpenCommand.Mode == OpenMode::ALL ) ||
( aOpenCommand.Mode == OpenMode::FOLDERS ) ||
( aOpenCommand.Mode == OpenMode::DOCUMENTS ) );
if ( bOpenFolder && impl_isFolder() )
{
Reference< XDynamicResultSet > xSet = new ResultSet( m_xContext, this, aOpenCommand, i_rEnvironment );
aRet <<= xSet;
}
if ( aOpenCommand.Sink.is() )
{
const OUString sPhysicalContentURL( getPhysicalURL() );
::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnvironment, m_xContext );
aRet = aRequestedContent.executeCommand( u"open"_ustr, Any( aOpenCommand ) );
}
}
else
{
::ucbhelper::cancelCommandExecution( Any( UnsupportedCommandException(
OUString(), *this ) ),
i_rEnvironment );
// unreachable
}
return aRet;
}
void SAL_CALL Content::abort( sal_Int32 )
{
}
OUString Content::encodeIdentifier( const OUString& i_rIdentifier )
{
return ::rtl::Uri::encode( i_rIdentifier, rtl_UriCharClassRegName, rtl_UriEncodeIgnoreEscapes,
RTL_TEXTENCODING_UTF8 );
}
OUString Content::decodeIdentifier( const OUString& i_rIdentifier )
{
return ::rtl::Uri::decode( i_rIdentifier, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
}
bool Content::denotesRootContent( std::u16string_view i_rContentIdentifier )
{
const OUString sRootURL( ContentProvider::getRootURL() );
if ( i_rContentIdentifier == sRootURL )
return true;
// the root URL contains only two trailing /, but we also recognize 3 of them as denoting the root URL
if ( o3tl::starts_with(i_rContentIdentifier, sRootURL )
&& ( sal_Int32(i_rContentIdentifier.size()) == sRootURL.getLength() + 1 )
&& ( i_rContentIdentifier[ i_rContentIdentifier.size() - 1 ] == '/' )
)
return true;
return false;
}
OUString Content::getParentURL()
{
const OUString sRootURL( ContentProvider::getRootURL() );
switch ( m_eExtContentType )
{
case E_ROOT:
// don't have a parent
return sRootURL;
case E_EXTENSION_ROOT:
// our parent is the root itself
return sRootURL;
case E_EXTENSION_CONTENT:
{
const OUString sURL = m_xIdentifier->getContentIdentifier();
// cut the root URL
if ( !sURL.match( sRootURL ) )
{
SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no root" );
break;
}
OUString sRelativeURL( sURL.copy( sRootURL.getLength() ) );
// cut the extension ID
const OUString sSeparatedExtensionId( encodeIdentifier( m_sExtensionId ) + "/" );
if ( !sRelativeURL.match( sSeparatedExtensionId ) )
{
SAL_INFO( "ucb.ucp.ext", "illegal URL structure - no extension ID" );
break;
}
sRelativeURL = sRelativeURL.copy( sSeparatedExtensionId.getLength() );
// cut the final slash (if any)
if ( sRelativeURL.isEmpty() )
{
SAL_INFO( "ucb.ucp.ext", "illegal URL structure - ExtensionContent should have a level below the extension ID" );
break;
}
if ( sRelativeURL.endsWith("/") )
sRelativeURL = sRelativeURL.copy( 0, sRelativeURL.getLength() - 1 );
// remove the last segment
const sal_Int32 nLastSep = sRelativeURL.lastIndexOf( '/' );
sRelativeURL = sRelativeURL.copy( 0, nLastSep != -1 ? nLastSep : 0 );
return sRootURL + sSeparatedExtensionId + sRelativeURL;
}
default:
OSL_FAIL( "Content::getParentURL: unhandled case!" );
break;
}
return OUString();
}
Reference< XRow > Content::getArtificialNodePropertyValues( const Reference< XComponentContext >& rxContext,
const Sequence< Property >& i_rProperties, const OUString& i_rTitle )
{
// note: empty sequence means "get values of all supported properties".
::rtl::Reference< ::ucbhelper::PropertyValueSet > xRow = new ::ucbhelper::PropertyValueSet( rxContext );
if ( i_rProperties.hasElements() )
{
for ( const Property& rProp : i_rProperties )
{
// Process Core properties.
if ( rProp.Name == "ContentType" )
{
xRow->appendString ( rProp, ContentProvider::getArtificialNodeContentType() );
}
else if ( rProp.Name == "Title" )
{
xRow->appendString ( rProp, i_rTitle );
}
else if ( rProp.Name == "IsDocument" )
{
xRow->appendBoolean( rProp, false );
}
else if ( rProp.Name == "IsFolder" )
{
xRow->appendBoolean( rProp, true );
}
else
{
// append empty entry.
xRow->appendVoid( rProp );
}
}
}
else
{
// Append all Core Properties.
xRow->appendString ( Property( u"ContentType"_ustr,
-1,
cppu::UnoType<OUString>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
ContentProvider::getArtificialNodeContentType() );
xRow->appendString ( Property( u"Title"_ustr,
-1,
cppu::UnoType<OUString>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
i_rTitle );
xRow->appendBoolean( Property( u"IsDocument"_ustr,
-1,
cppu::UnoType<bool>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
false );
xRow->appendBoolean( Property( u"IsFolder"_ustr,
-1,
cppu::UnoType<bool>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY ),
true );
}
return xRow;
}
OUString Content::getPhysicalURL() const
{
ENSURE_OR_RETURN( m_eExtContentType != E_ROOT, "illegal call", OUString() );
// create a ucb::XContent for the physical file within the deployed extension
const Reference< XPackageInformationProvider > xPackageInfo = PackageInformationProvider::get(m_xContext);
const OUString sPackageLocation( xPackageInfo->getPackageLocation( m_sExtensionId ) );
if ( m_sPathIntoExtension.isEmpty() )
return sPackageLocation;
return lcl_compose( sPackageLocation, m_sPathIntoExtension );
}
Reference< XRow > Content::getPropertyValues( const Sequence< Property >& i_rProperties, const Reference< XCommandEnvironment >& i_rEnv )
{
::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
switch ( m_eExtContentType )
{
case E_ROOT:
return getArtificialNodePropertyValues( m_xContext, i_rProperties, ContentProvider::getRootURL() );
case E_EXTENSION_ROOT:
return getArtificialNodePropertyValues( m_xContext, i_rProperties, m_sExtensionId );
case E_EXTENSION_CONTENT:
{
const OUString sPhysicalContentURL( getPhysicalURL() );
::ucbhelper::Content aRequestedContent( sPhysicalContentURL, i_rEnv, m_xContext );
// translate the property request
Sequence< OUString > aPropertyNames( i_rProperties.getLength() );
::std::transform(
i_rProperties.begin(),
i_rProperties.end(),
aPropertyNames.getArray(),
SelectPropertyName()
);
const Sequence< Any > aPropertyValues = aRequestedContent.getPropertyValues( aPropertyNames );
const ::rtl::Reference< ::ucbhelper::PropertyValueSet > xValueRow = new ::ucbhelper::PropertyValueSet( m_xContext );
sal_Int32 i=0;
for ( const Any* value = aPropertyValues.getConstArray();
value != aPropertyValues.getConstArray() + aPropertyValues.getLength();
++value, ++i
)
{
xValueRow->appendObject( aPropertyNames[i], *value );
}
return xValueRow;
}
default:
OSL_FAIL( "Content::getPropertyValues: unhandled case!" );
break;
}
OSL_FAIL( "Content::getPropertyValues: unreachable!" );
return nullptr;
}
Sequence< Any > Content::setPropertyValues( const Sequence< PropertyValue >& i_rValues)
{
::osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex );
Sequence< Any > aRet( i_rValues.getLength() );
PropertyChangeEvent aEvent;
aEvent.Source = getXWeak();
aEvent.Further = false;
aEvent.PropertyHandle = -1;
for ( auto& rRet : asNonConstRange(aRet) )
{
// all our properties are read-only ...
rRet <<= IllegalAccessException(u"property is read-only."_ustr, *this );
}
return aRet;
}
Sequence< CommandInfo > Content::getCommands( const Reference< XCommandEnvironment > & /*xEnv*/ )
{
static const CommandInfo aCommandInfoTable[] =
{
// Mandatory commands
CommandInfo(
u"getCommandInfo"_ustr,
-1,
cppu::UnoType<void>::get()
),
CommandInfo(
u"getPropertySetInfo"_ustr,
-1,
cppu::UnoType<void>::get()
),
CommandInfo(
u"getPropertyValues"_ustr,
-1,
cppu::UnoType<Sequence< Property >>::get()
),
CommandInfo(
u"setPropertyValues"_ustr,
-1,
cppu::UnoType<Sequence< PropertyValue >>::get()
)
// Optional standard commands
, CommandInfo(
u"open"_ustr,
-1,
cppu::UnoType<OpenCommandArgument2>::get()
)
};
return Sequence< CommandInfo >( aCommandInfoTable, SAL_N_ELEMENTS(aCommandInfoTable) );
}
Sequence< Property > Content::getProperties( const Reference< XCommandEnvironment > & /*xEnv*/ )
{
static const Property aProperties[] =
{
Property(
u"ContentType"_ustr,
-1,
cppu::UnoType<OUString>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY
),
Property(
u"IsDocument"_ustr,
-1,
cppu::UnoType<bool>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY
),
Property(
u"IsFolder"_ustr,
-1,
cppu::UnoType<bool>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY
),
Property(
u"Title"_ustr,
-1,
cppu::UnoType<OUString>::get(),
PropertyAttribute::BOUND | PropertyAttribute::READONLY
)
};
return Sequence< Property >( aProperties, SAL_N_ELEMENTS( aProperties ) );
}
bool Content::impl_isFolder()
{
if ( m_aIsFolder.has_value() )
return *m_aIsFolder;
bool bIsFolder = false;
try
{
Sequence< Property > aProps{ { /*Name*/ u"IsFolder"_ustr, {}, {}, {} } };
Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
bIsFolder = xRow->getBoolean(1);
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
}
m_aIsFolder = bIsFolder;
return *m_aIsFolder;
}
void Content::impl_determineContentType()
{
if ( !!m_aContentType )
return;
m_aContentType = ContentProvider::getArtificialNodeContentType();
if ( m_eExtContentType != E_EXTENSION_CONTENT )
return;
try
{
Sequence< Property > aProps{ { /*Name*/ u"ContentType"_ustr, {}, {}, {} } };
Reference< XRow > xRow( getPropertyValues( aProps, nullptr ), UNO_SET_THROW );
m_aContentType = xRow->getString(1);
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("ucb.ucp.ext");
}
}
} // namespace ucp::ext
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V1053 Calling the 'getIdentifier' virtual function in the constructor may lead to unexpected result at runtime.
↑ V547 Expression '!i_rBaseURL.empty()' is always false.