/* -*- 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 <memory>
#include <cppuhelper/queryinterface.hxx>
#include <rtl/uri.hxx>
#include <sal/log.hxx>
#include <officecfg/Office/Common.hxx>
#include <officecfg/Inet.hxx>
#include <ucbhelper/contentidentifier.hxx>
#include <ucbhelper/macros.hxx>
#include <ucbhelper/propertyvalueset.hxx>
#include <ucbhelper/simpleinteractionrequest.hxx>
#include <ucbhelper/cancelcommandexecution.hxx>
#include <svl/lockfilecommon.hxx>
#include <com/sun/star/beans/IllegalTypeException.hpp>
#include <com/sun/star/beans/NotRemoveableException.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/beans/PropertyExistException.hpp>
#include <com/sun/star/beans/PropertySetInfoChange.hpp>
#include <com/sun/star/beans/PropertySetInfoChangeEvent.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/io/XActiveDataSink.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/lang/IllegalAccessException.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/task/PasswordContainerInteractionHandler.hpp>
#include <com/sun/star/ucb/CommandEnvironment.hpp>
#include <com/sun/star/ucb/CommandFailedException.hpp>
#include <com/sun/star/ucb/ContentInfoAttribute.hpp>
#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
#include <com/sun/star/ucb/InsertCommandArgument.hpp>
#include <com/sun/star/ucb/InteractiveBadTransferURLException.hpp>
#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
#include <com/sun/star/ucb/InteractiveLockingLockExpiredException.hpp>
#include <com/sun/star/ucb/InteractiveLockingNotLockedException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkGeneralException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkResolveNameException.hpp>
#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
#include <com/sun/star/ucb/MissingInputStreamException.hpp>
#include <com/sun/star/ucb/MissingPropertiesException.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/NameClashException.hpp>
#include <com/sun/star/ucb/OpenCommandArgument3.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/ucb/PostCommandArgument2.hpp>
#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
#include <com/sun/star/ucb/UnsupportedNameClashException.hpp>
#include <com/sun/star/ucb/UnsupportedOpenModeException.hpp>
#include <com/sun/star/ucb/XCommandInfo.hpp>
#include <com/sun/star/ucb/XPersistentPropertySet.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include "webdavcontent.hxx"
#include "webdavprovider.hxx"
#include "webdavresultset.hxx"
#include "ContentProperties.hxx"
#include "CurlUri.hxx"
#include "UCBDeadPropertyValue.hxx"
#include "DAVException.hxx"
#include "DAVProperties.hxx"
using namespace com::sun::star;
using namespace http_dav_ucp;
namespace
{
void lcl_sendPartialGETRequest(
bool &bError,
DAVException &aLastException,
const std::vector< OUString >& rProps,
std::vector< OUString > &aHeaderNames,
const std::unique_ptr< DAVResourceAccess > &xResAccess,
std::unique_ptr< ContentProperties > &xProps,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
DAVResource aResource;
DAVRequestHeaders aPartialGet;
aPartialGet.emplace_back(
u
"Range"_ustr,
// see <https://tools.ietf.org/html/rfc7233#section-3.1>
u
"bytes=0-0"_ustr);
bool bIsRequestSize = std::any_of(aHeaderNames.begin(), aHeaderNames.end(),
[](
const OUString& rHeaderName) {
return rHeaderName ==
"Content-Length"; });
if ( bIsRequestSize )
{
// we need to know if the server accepts range requests for a resource
// and the range unit it uses
aHeaderNames.emplace_back(
"Accept-Ranges");
// see <https://tools.ietf.org/html/rfc7233#section-2.3>
aHeaderNames.emplace_back(
"Content-Range");
// see <https://tools.ietf.org/html/rfc7233#section-4.2>
}
try
{
xResAccess->GET0( aPartialGet, aHeaderNames, aResource, xEnv );
bError =
false;
if ( bIsRequestSize )
{
// the ContentProperties maps "Content-Length" to the UCB "Size" property
// This would have an unrealistic value of 1 byte because we did only a partial GET
// Solution: if "Content-Range" is present, map it with UCB "Size" property
OUString aAcceptRanges, aContentRange, aContentLength;
std::vector< DAVPropertyValue > &aResponseProps = aResource.properties;
for (
const auto& rResponseProp : aResponseProps )
{
if ( rResponseProp.Name ==
"Accept-Ranges" )
rResponseProp.Value >>= aAcceptRanges;
else if ( rResponseProp.Name ==
"Content-Range" )
rResponseProp.Value >>= aContentRange;
else if ( rResponseProp.Name ==
"Content-Length" )
rResponseProp.Value >>= aContentLength;
}
sal_Int64 nSize = 1;
if ( aContentLength.getLength() )
{
nSize = aContentLength.toInt64();
}
// according to http://tools.ietf.org/html/rfc2616#section-3.12
// the only range unit defined is "bytes" and implementations
// MAY ignore ranges specified using other units.
if ( nSize == 1 &&
aContentRange.getLength() &&
aAcceptRanges ==
"bytes" )
{
// Parse the Content-Range to get the size
// vid. http://tools.ietf.org/html/rfc2616#section-14.16
// Content-Range: <range unit> <bytes range>/<size>
sal_Int32 nSlash = aContentRange.lastIndexOf(
'/' );
if ( nSlash != -1 )
{
OUString aSize = aContentRange.copy( nSlash + 1 );
// "*" means that the instance-length is unknown at the time when the response was generated
if ( aSize !=
"*" )
{
auto it = std::find_if(aResponseProps.begin(), aResponseProps.end(),
[](
const DAVPropertyValue& rProp) {
return rProp.Name ==
"Content-Length"; });
if (it != aResponseProps.end())
{
it->Value <<= aSize;
}
}
}
}
}
if (xProps)
xProps->addProperties(
rProps,
ContentProperties( aResource ) );
else
xProps.reset (
new ContentProperties( aResource ) );
}
catch ( DAVException
const & ex )
{
aLastException = ex;
}
}
}
// Static value, to manage a simple OPTIONS cache
// Key is the URL, element is the DAVOptions resulting from an OPTIONS call.
// Cached DAVOptions have a lifetime that depends on the errors received or not received
// and on the value of received options.
static DAVOptionsCache aStaticDAVOptionsCache;
// Content Implementation.
// ctr for content on an existing webdav resource
Content::Content(
const uno::Reference< uno::XComponentContext >& rxContext,
ContentProvider* pProvider,
const uno::Reference< ucb::XContentIdentifier >& Identifier,
rtl::Reference< DAVSessionFactory >
const & rSessionFactory )
: ContentImplHelper( rxContext, pProvider, Identifier ),
m_eResourceType( UNKNOWN ),
m_eResourceTypeForLocks( UNKNOWN ),
m_pProvider( pProvider ),
m_bTransient(
false ),
m_bCollection(
false ),
m_bDidGetOrHead(
false )
{
try
{
initOptsCacheLifeTime();
m_xResAccess.reset(
new DAVResourceAccess(
rxContext,
rSessionFactory,
Identifier->getContentIdentifier() ) );
CurlUri
const aURI( Identifier->getContentIdentifier() );
m_aEscapedTitle = aURI.GetPathBaseName();
}
catch ( DAVException
const & )
{
throw ucb::ContentCreationException();
}
}
// ctr for content on a non-existing webdav resource
Content::Content(
const uno::Reference< uno::XComponentContext >& rxContext,
ContentProvider* pProvider,
const uno::Reference< ucb::XContentIdentifier >& Identifier,
rtl::Reference< DAVSessionFactory >
const & rSessionFactory,
bool isCollection )
: ContentImplHelper( rxContext, pProvider, Identifier ),
m_eResourceType( UNKNOWN ),
m_eResourceTypeForLocks( UNKNOWN ),
m_pProvider( pProvider ),
m_bTransient(
true ),
m_bCollection( isCollection ),
m_bDidGetOrHead(
false )
{
try
{
initOptsCacheLifeTime();
m_xResAccess.reset(
new DAVResourceAccess(
rxContext, rSessionFactory, Identifier->getContentIdentifier() ) );
}
catch ( DAVException
const & )
{
throw ucb::ContentCreationException();
}
// Do not set m_aEscapedTitle here! Content::insert relays on this!!!
}
// virtual
Content::~Content()
{
}
// XInterface methods.
// virtual
void SAL_CALL Content::acquire() noexcept
{
ContentImplHelper::acquire();
}
// virtual
void SAL_CALL Content::release() noexcept
{
ContentImplHelper::release();
}
// virtual
uno::Any SAL_CALL Content::queryInterface(
const uno::Type & rType )
{
// Note: isFolder may require network activities! So call it only
// if it is really necessary!!!
uno::Any aRet = cppu::queryInterface(
rType,
static_cast< ucb::XContentCreator * >(
this ) );
if ( aRet.hasValue() )
{
try
{
uno::Reference< task::XInteractionHandler > xIH(
task::PasswordContainerInteractionHandler::create(m_xContext) );
// Supply a command env to isFolder() that contains an interaction
// handler that uses the password container service to obtain
// credentials without displaying a password gui.
uno::Reference< ucb::XCommandEnvironment > xCmdEnv(
ucb::CommandEnvironment::create(
m_xContext,
xIH,
uno::Reference< ucb::XProgressHandler >() ) );
return isFolder( xCmdEnv ) ? aRet : uno::Any();
}
catch ( uno::RuntimeException
const & )
{
throw;
}
catch ( uno::Exception
const & )
{
return uno::Any();
}
}
return aRet.hasValue() ? aRet : ContentImplHelper::queryInterface( rType );
}
// XTypeProvider methods.
XTYPEPROVIDER_COMMON_IMPL( Content );
// virtual
uno::Sequence< uno::Type > SAL_CALL Content::getTypes()
{
bool bFolder =
false;
try
{
bFolder
= isFolder( uno::Reference< ucb::XCommandEnvironment >() );
}
catch ( uno::RuntimeException
const & )
{
throw;
}
catch ( uno::Exception
const & )
{
}
if ( bFolder )
{
static cppu::OTypeCollection s_aFolderTypes(
CPPU_TYPE_REF( lang::XTypeProvider ),
CPPU_TYPE_REF( lang::XServiceInfo ),
CPPU_TYPE_REF( lang::XComponent ),
CPPU_TYPE_REF( ucb::XContent ),
CPPU_TYPE_REF( ucb::XCommandProcessor ),
CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
CPPU_TYPE_REF( beans::XPropertyContainer ),
CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
CPPU_TYPE_REF( container::XChild ),
CPPU_TYPE_REF( ucb::XContentCreator ) );
return s_aFolderTypes.getTypes();
}
else
{
static cppu::OTypeCollection s_aDocumentTypes(
CPPU_TYPE_REF( lang::XTypeProvider ),
CPPU_TYPE_REF( lang::XServiceInfo ),
CPPU_TYPE_REF( lang::XComponent ),
CPPU_TYPE_REF( ucb::XContent ),
CPPU_TYPE_REF( ucb::XCommandProcessor ),
CPPU_TYPE_REF( beans::XPropertiesChangeNotifier ),
CPPU_TYPE_REF( ucb::XCommandInfoChangeNotifier ),
CPPU_TYPE_REF( beans::XPropertyContainer ),
CPPU_TYPE_REF( beans::XPropertySetInfoChangeNotifier ),
CPPU_TYPE_REF( container::XChild ) );
return s_aDocumentTypes.getTypes();
}
}
// XServiceInfo methods.
// virtual
OUString SAL_CALL Content::getImplementationName()
{
return u
"com.sun.star.comp.ucb.WebDAVContent"_ustr;
}
// virtual
uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames()
{
uno::Sequence<OUString> aSNS { WEBDAV_CONTENT_SERVICE_NAME };
return aSNS;
}
// XContent methods.
// virtual
OUString SAL_CALL Content::getContentType()
{
bool bFolder =
false;
try
{
bFolder
= isFolder( uno::Reference< ucb::XCommandEnvironment >() );
}
catch ( uno::RuntimeException
const & )
{
throw;
}
catch ( uno::Exception
const & )
{
}
if ( bFolder )
return WEBDAV_COLLECTION_TYPE;
return WEBDAV_CONTENT_TYPE;
}
// XCommandProcessor methods.
// virtual
uno::Any SAL_CALL Content::execute(
const ucb::Command& aCommand,
sal_Int32
/*CommandId*/,
const uno::Reference< ucb::XCommandEnvironment >& Environment )
{
SAL_INFO(
"ucb.ucp.webdav",
">>>>> Content::execute: start: command: " << aCommand.Nam
e
<< ", env: " << (Environment.is() ? "present" : "missing") );
uno::Any aRet;
if ( aCommand.Name == "getPropertyValues" )
{
// getPropertyValues
uno::Sequence< beans::Property > Properties;
if ( !( aCommand.Argument >>= Properties ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
aRet <<= getPropertyValues( Properties, Environment );
}
else if ( aCommand.Name == "setPropertyValues" )
{
// setPropertyValues
uno::Sequence< beans::PropertyValue > aProperties;
if ( !( aCommand.Argument >>= aProperties ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
if ( !aProperties.getLength() )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"No properties!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
aRet <<= setPropertyValues( aProperties, Environment );
}
else if ( aCommand.Name == "getPropertySetInfo" )
{
// getPropertySetInfo
// Note: Implemented by base class.
aRet <<= getPropertySetInfo( Environment,
false /* don't cache data */ );
}
else if ( aCommand.Name == "getCommandInfo" )
{
// getCommandInfo
// Note: Implemented by base class.
aRet <<= getCommandInfo( Environment, false );
}
else if ( aCommand.Name == "open" )
{
// open
ucb::OpenCommandArgument3 aOpenCommand;
ucb::OpenCommandArgument2 aTmp;
if ( !( aCommand.Argument >>= aTmp ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
if ( !( aCommand.Argument >>= aOpenCommand ) )
{
// compat mode, extract Arg2 info into newer structure
aOpenCommand.Mode = aTmp.Mode;
aOpenCommand.Priority = aTmp.Priority;
aOpenCommand.Sink = aTmp.Sink;
aOpenCommand.Properties = aTmp.Properties;
aOpenCommand.SortingInfo = aTmp.SortingInfo;
}
aRet = open( aOpenCommand, Environment );
}
else if ( aCommand.Name == "insert" )
{
// insert
ucb::InsertCommandArgument arg;
if ( !( aCommand.Argument >>= arg ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
insert( arg.Data, arg.ReplaceExisting, Environment );
}
else if ( aCommand.Name == "delete" )
{
// delete
bool bDeletePhysical = false;
aCommand.Argument >>= bDeletePhysical;
// KSO: Ignore parameter and destroy the content, if you don't support
// putting objects into trashcan. ( Since we do not have a trash can
// service yet (src603), you actually have no other choice. )
// if ( bDeletePhysical )
// {
try
{
std::unique_ptr< DAVResourceAccess > xResAccess;
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
}
aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
// clean cached value of PROPFIND property names
removeCachedPropertyNames( xResAccess->getURL() );
xResAccess->DESTROY( Environment );
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
}
}
catch ( DAVException const & e )
{
cancelCommandExecution( e, Environment, true );
// Unreachable
}
// }
// Propagate destruction.
destroy( bDeletePhysical );
// Remove own and all children's Additional Core Properties.
removeAdditionalPropertySet();
}
else if ( aCommand.Name == "transfer" && isFolder( Environment ) )
{
// transfer
// ( Not available at documents )
ucb::TransferInfo transferArgs;
if ( !( aCommand.Argument >>= transferArgs ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
transfer( transferArgs, Environment );
}
else if ( aCommand.Name == "post" )
{
// post
ucb::PostCommandArgument2 aArg;
if ( !( aCommand.Argument >>= aArg ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
post( aArg, Environment );
}
else if ( aCommand.Name == "lock" )
{
// lock
ResourceType eType = resourceTypeForLocks( Environment );
// when the resource is not yet present the lock is used to create it
// see: http://tools.ietf.org/html/rfc4918#section-7.3
// If the resource doesn't exists and the lock is not enabled (DAV with
// no lock or a simple web) the error will be dealt with inside lock() method
if ( eType == NOT_FOUND ||
eType == DAV )
{
lock( Environment );
if ( eType == NOT_FOUND )
{
m_eResourceType = UNKNOWN; // lock may have created it, need to check again
m_eResourceTypeForLocks = UNKNOWN;
}
}
}
else if ( aCommand.Name == "unlock" )
{
// unlock
// do not check for a DAV resource
// the lock store will be checked before sending
unlock( Environment );
}
else if ( aCommand.Name == "createNewContent" &&
isFolder( Environment ) )
{
// createNewContent
ucb::ContentInfo aArg;
if ( !( aCommand.Argument >>= aArg ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
// Unreachable
}
aRet <<= createNewContent( aArg );
}
else if ( aCommand.Name == "addProperty" )
{
ucb::PropertyCommandArgument aPropArg;
if ( !( aCommand.Argument >>= aPropArg ))
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
}
// TODO when/if XPropertyContainer is removed,
// the command execution can be canceled in addProperty
try
{
addProperty( aPropArg, Environment );
}
catch ( const beans::PropertyExistException &e )
{
ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
}
catch ( const beans::IllegalTypeException&e )
{
ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
}
catch ( const lang::IllegalArgumentException&e )
{
ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
}
}
else if ( aCommand.Name == "removeProperty" )
{
OUString sPropName;
if ( !( aCommand.Argument >>= sPropName ) )
{
ucbhelper::cancelCommandExecution(
uno::Any( lang::IllegalArgumentException(
u"Wrong argument type!"_ustr,
getXWeak(),
-1 ) ),
Environment );
}
// TODO when/if XPropertyContainer is removed,
// the command execution can be canceled in removeProperty
try
{
removeProperty( sPropName, Environment );
}
catch( const beans::UnknownPropertyException &e )
{
ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
}
catch( const beans::NotRemoveableException &e )
{
ucbhelper::cancelCommandExecution( uno::Any( e ), Environment );
}
}
else
{
// Unsupported command
ucbhelper::cancelCommandExecution(
uno::Any( ucb::UnsupportedCommandException(
aCommand.Name,
getXWeak() ) ),
Environment );
// Unreachable
}
SAL_INFO("ucb.ucp.webdav", "<<<<< Content::execute: end: command: " << aCommand.Name);
return aRet;
}
// virtual
void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
{
try
{
std::unique_ptr< DAVResourceAccess > xResAccess;
{
osl::MutexGuard aGuard( m_aMutex );
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
}
xResAccess->abort();
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
}
}
catch ( DAVException const & )
{
// abort failed!
}
}
// XPropertyContainer methods.
void Content::addProperty( const css::ucb::PropertyCommandArgument &aCmdArg,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
// if ( m_bTransient )
// @@@ ???
const beans::Property aProperty = aCmdArg.Property;
const uno::Any aDefaultValue = aCmdArg.DefaultValue;
// check property Name
if ( !aProperty.Name.getLength() )
throw lang::IllegalArgumentException(
u"\"addProperty\" with empty Property.Name"_ustr,
getXWeak(),
-1 );
// Check property type.
if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) )
throw beans::IllegalTypeException(
u"\"addProperty\" unsupported Property.Type"_ustr,
getXWeak() );
// check default value
if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type )
throw beans::IllegalTypeException(
u"\"addProperty\" DefaultValue does not match Property.Type"_ustr,
getXWeak() );
// Make sure a property with the requested name does not already
// exist in dynamic and static(!) properties.
// Take into account special properties with custom namespace
// using <prop:the_propname xmlns:prop="the_namespace">
OUString aSpecialName;
bool bIsSpecial = DAVProperties::isUCBSpecialProperty( aProperty.Name, aSpecialName );
// Note: This requires network access!
if ( getPropertySetInfo( xEnv, false /* don't cache data */ )
->hasPropertyByName( bIsSpecial ? aSpecialName : aProperty.Name ) )
{
// Property does already exist.
throw beans::PropertyExistException();
}
// Add a new dynamic property.
ProppatchValue aValue( PROPSET, aProperty.Name, aDefaultValue );
std::vector< ProppatchValue > aProppatchValues;
aProppatchValues.push_back( aValue );
try
{
// Set property value at server.
std::unique_ptr< DAVResourceAccess > xResAccess;
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
}
aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
// clean cached value of PROPFIND property names
// PROPPATCH can change them
removeCachedPropertyNames( xResAccess->getURL() );
xResAccess->PROPPATCH( aProppatchValues, xEnv );
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
}
// Notify propertyset info change listeners.
beans::PropertySetInfoChangeEvent evt(
getXWeak(),
bIsSpecial ? aSpecialName : aProperty.Name,
-1, // No handle available
beans::PropertySetInfoChange::PROPERTY_INSERTED );
notifyPropertySetInfoChange( evt );
}
catch ( DAVException const & e )
{
if ( e.getStatus() == SC_FORBIDDEN )
{
// Support for setting arbitrary dead properties is optional!
// Store property locally.
ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
aProperty.Attributes,
aDefaultValue );
}
else
{
if ( shouldAccessNetworkAfterException( e ) )
{
try
{
const ResourceType eType = getResourceType( xEnv );
switch ( eType )
{
case UNKNOWN:
case DAV:
throw lang::IllegalArgumentException();
case NON_DAV:
// Store property locally.
ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
aProperty.Attributes,
aDefaultValue );
break;
default:
SAL_WARN( "ucb.ucp.webdav",
"Content::addProperty - "
"Unsupported resource type!" );
break;
}
}
catch ( uno::Exception const & )
{
SAL_WARN( "ucb.ucp.webdav",
"Content::addProperty - "
"Unable to determine resource type!" );
}
}
else
{
SAL_WARN( "ucb.ucp.webdav",
"Content::addProperty - "
"Unable to determine resource type!" );
}
}
}
}
void Content::removeProperty( const OUString& Name,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
#if 0
// @@@ REMOVABLE at the moment not properly set in the PropSetInfo
try
{
beans::Property aProp
= getPropertySetInfo( xEnv, false /* don't cache data */ )
->getPropertyByName( Name );
if ( !( aProp.Attributes & beans::PropertyAttribute::REMOVABLE ) )
{
// Not removable!
throw beans::NotRemoveableException();
}
}
catch ( beans::UnknownPropertyException const & )
{
//SAL_WARN( "ucb.ucp.webdav", "removeProperty - Unknown property!" );
throw;
}
#endif
// Try to remove property from server.
try
{
std::vector< ProppatchValue > aProppatchValues;
ProppatchValue aValue( PROPREMOVE, Name, uno::Any() );
aProppatchValues.push_back( aValue );
// Remove property value from server.
std::unique_ptr< DAVResourceAccess > xResAccess;
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
}
aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() );
// clean cached value of PROPFIND property names
// PROPPATCH can change them
removeCachedPropertyNames( xResAccess->getURL() );
xResAccess->PROPPATCH( aProppatchValues, xEnv );
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
}
// Notify propertyset info change listeners.
beans::PropertySetInfoChangeEvent evt(
getXWeak(),
Name,
-1, // No handle available
beans::PropertySetInfoChange::PROPERTY_REMOVED );
notifyPropertySetInfoChange( evt );
}
catch ( DAVException const & e )
{
if ( e.getStatus() == SC_FORBIDDEN )
{
// Support for setting arbitrary dead properties is optional!
// Try to remove property from local store.
ContentImplHelper::removeProperty( Name );
}
else
{
if ( shouldAccessNetworkAfterException( e ) )
{
try
{
const ResourceType eType = getResourceType( xEnv );
switch ( eType )
{
case UNKNOWN:
case DAV:
throw beans::UnknownPropertyException(Name);
case NON_DAV:
// Try to remove property from local store.
ContentImplHelper::removeProperty( Name );
break;
default:
SAL_WARN( "ucb.ucp.webdav",
"Content::removeProperty - "
"Unsupported resource type!" );
break;
}
}
catch ( uno::Exception const & )
{
SAL_WARN( "ucb.ucp.webdav",
"Content::removeProperty - "
"Unable to determine resource type!" );
}
}
else
{
SAL_WARN( "ucb.ucp.webdav",
"Content::removeProperty - "
"Unable to determine resource type!" );
// throw beans::UnknownPropertyException();
}
}
}
}
// virtual
void SAL_CALL Content::addProperty( const OUString& Name,
sal_Int16 Attributes,
const uno::Any& DefaultValue )
{
beans::Property aProperty;
aProperty.Name = Name;
aProperty.Type = DefaultValue.getValueType();
aProperty.Attributes = Attributes;
aProperty.Handle = -1;
addProperty( ucb::PropertyCommandArgument( aProperty, DefaultValue ),
uno::Reference< ucb::XCommandEnvironment >());
}
// virtual
void SAL_CALL Content::removeProperty( const OUString& Name )
{
removeProperty( Name,
uno::Reference< ucb::XCommandEnvironment >() );
}
// XContentCreator methods.
// virtual
uno::Sequence< ucb::ContentInfo > SAL_CALL
Content::queryCreatableContentsInfo()
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
uno::Sequence< ucb::ContentInfo > aSeq( 2 );
// document.
aSeq.getArray()[ 0 ].Type = WEBDAV_CONTENT_TYPE;
aSeq.getArray()[ 0 ].Attributes
= ucb::ContentInfoAttribute::INSERT_WITH_INPUTSTREAM
| ucb::ContentInfoAttribute::KIND_DOCUMENT;
beans::Property aProp;
m_pProvider->getProperty( u"Title"_ustr, aProp );
uno::Sequence< beans::Property > aDocProps( 1 );
aDocProps.getArray()[ 0 ] = aProp;
aSeq.getArray()[ 0 ].Properties = std::move(aDocProps);
// folder.
aSeq.getArray()[ 1 ].Type = WEBDAV_COLLECTION_TYPE;
aSeq.getArray()[ 1 ].Attributes
= ucb::ContentInfoAttribute::KIND_FOLDER;
uno::Sequence< beans::Property > aFolderProps( 1 );
aFolderProps.getArray()[0] = std::move(aProp);
aSeq.getArray()[ 1 ].Properties = std::move(aFolderProps);
return aSeq;
}
// virtual
uno::Reference< ucb::XContent > SAL_CALL
Content::createNewContent( const ucb::ContentInfo& Info )
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
if ( !Info.Type.getLength() )
return uno::Reference< ucb::XContent >();
if ( ( Info.Type != WEBDAV_COLLECTION_TYPE )
&&
( Info.Type != WEBDAV_CONTENT_TYPE ) )
return uno::Reference< ucb::XContent >();
OUString aURL = m_xIdentifier->getContentIdentifier();
SAL_WARN_IF( aURL.isEmpty(), "ucb.ucp.webdav",
"WebdavContent::createNewContent - empty identifier!" );
if ( ( aURL.lastIndexOf( '/' ) + 1 ) != aURL.getLength() )
aURL += "/";
bool isCollection;
if ( Info.Type == WEBDAV_COLLECTION_TYPE )
{
aURL += "New_Collection";
isCollection = true;
}
else
{
aURL += "New_Content";
isCollection = false;
}
uno::Reference< ucb::XContentIdentifier > xId(
new ::ucbhelper::ContentIdentifier( aURL ) );
// create the local content
try
{
return new ::http_dav_ucp::Content( m_xContext,
m_pProvider,
xId,
m_xResAccess->getSessionFactory(),
isCollection );
}
catch ( ucb::ContentCreationException & )
{
return uno::Reference< ucb::XContent >();
}
}
// virtual
OUString Content::getParentURL()
{
// <scheme>:// -> ""
// <scheme>://foo -> ""
// <scheme>://foo/ -> ""
// <scheme>://foo/bar -> <scheme>://foo/
// <scheme>://foo/bar/ -> <scheme>://foo/
// <scheme>://foo/bar/abc -> <scheme>://foo/bar/
OUString aURL = m_xIdentifier->getContentIdentifier();
sal_Int32 nPos = aURL.lastIndexOf( '/' );
if ( nPos == ( aURL.getLength() - 1 ) )
{
// Trailing slash found. Skip.
nPos = aURL.lastIndexOf( '/', nPos );
}
sal_Int32 nPos1 = aURL.lastIndexOf( '/', nPos );
if ( nPos1 != -1 )
nPos1 = aURL.lastIndexOf( '/', nPos1 );
if ( nPos1 == -1 )
return OUString();
return aURL.copy( 0, nPos + 1 );
}
// Non-interface methods.
// static
uno::Reference< sdbc::XRow > Content::getPropertyValues(
const uno::Reference< uno::XComponentContext >& rxContext,
const uno::Sequence< beans::Property >& rProperties,
const ContentProperties& rData,
const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider,
const OUString& rContentId )
{
// Note: Empty sequence means "get values of all supported properties".
rtl::Reference< ::ucbhelper::PropertyValueSet > xRow
= new ::ucbhelper::PropertyValueSet( rxContext );
sal_Int32 nCount = rProperties.getLength();
if ( nCount )
{
uno::Reference< beans::XPropertySet > xAdditionalPropSet;
bool bTriedToGetAdditionalPropSet = false;
const beans::Property* pProps = rProperties.getConstArray();
for ( sal_Int32 n = 0; n < nCount; ++n )
{
const beans::Property& rProp = pProps[ n ];
// Process standard UCB, DAV and HTTP properties.
const uno::Any & rValue = rData.getValue( rProp.Name );
if ( rValue.hasValue() )
{
xRow->appendObject( rProp, rValue );
}
else
{
// Process local Additional Properties.
if ( !bTriedToGetAdditionalPropSet && !xAdditionalPropSet.is() )
{
xAdditionalPropSet =
rProvider->getAdditionalPropertySet( rContentId,
false );
bTriedToGetAdditionalPropSet = true;
}
if ( !xAdditionalPropSet.is() ||
!xRow->appendPropertySetValue(
xAdditionalPropSet, rProp ) )
{
// Append empty entry.
xRow->appendVoid( rProp );
}
}
}
}
else
{
// Append all standard UCB, DAV and HTTP properties.
const std::unique_ptr< PropertyValueMap > & xProps = rData.getProperties();
ContentProvider * pProvider
= static_cast< ContentProvider * >( rProvider.get() );
beans::Property aProp;
for ( const auto& rProp : *xProps )
{
if ( pProvider->getProperty( rProp.first, aProp ) )
xRow->appendObject( aProp, rProp.second.value() );
}
// Append all local Additional Properties.
uno::Reference< beans::XPropertySet > xSet =
rProvider->getAdditionalPropertySet( rContentId, false );
xRow->appendPropertySet( xSet );
}
return uno::Reference<sdbc::XRow>(xRow);
}
namespace {
void GetPropsUsingHeadRequest(DAVResource& resource,
const std::unique_ptr< DAVResourceAccess >& xResAccess,
const std::vector< OUString >& aHTTPNames,
const uno::Reference< ucb::XCommandEnvironment >& xEnv)
{
if (!aHTTPNames.empty())
{
DAVOptions aDAVOptions;
OUString aTargetURL = xResAccess->getURL();
// retrieve the cached options if any
aStaticDAVOptionsCache.getDAVOptions(aTargetURL, aDAVOptions);
// clean cached value of PROPFIND property names
// PROPPATCH can change them
Content::removeCachedPropertyNames(aTargetURL);
// test if HEAD allowed, if not, throw, should be caught immediately
// SC_GONE used internally by us, see comment in Content::getPropertyValues
// in the catch scope
if (aDAVOptions.getHttpResponseStatusCode() != SC_GONE &&
!aDAVOptions.isHeadAllowed())
{
throw DAVException(DAVException::DAV_HTTP_ERROR, u"405 Not Implemented"_ustr, SC_METHOD_NOT_ALLOWED);
}
// if HEAD is enabled on this site
// check if there is a relevant HTTP response status code cached
if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE)
{
// throws exception as if there was a server error, a DAV exception
throw DAVException(DAVException::DAV_HTTP_ERROR,
aDAVOptions.getHttpResponseStatusText(),
aDAVOptions.getHttpResponseStatusCode());
// Unreachable
}
xResAccess->HEAD(aHTTPNames, resource, xEnv);
}
}
}
uno::Reference< sdbc::XRow > Content::getPropertyValues(
const uno::Sequence< beans::Property >& rProperties,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
std::unique_ptr< ContentProperties > xProps;
std::unique_ptr< ContentProperties > xCachedProps;
std::unique_ptr< DAVResourceAccess > xResAccess;
OUString aUnescapedTitle;
bool bHasAll = false;
uno::Reference< uno::XComponentContext > xContext;
uno::Reference< ucb::XContentIdentifier > xIdentifier;
rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider;
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
aUnescapedTitle = DecodeURI(m_aEscapedTitle);
xContext.set( m_xContext );
xIdentifier.set( m_xIdentifier );
xProvider = m_xProvider;
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
// First, ask cache...
if (m_xCachedProps)
{
xCachedProps.reset( new ContentProperties( *m_xCachedProps ) );
std::vector< OUString > aMissingProps;
if ( xCachedProps->containsAllNames( rProperties, aMissingProps ) )
{
// All properties are already in cache! No server access needed.
bHasAll = true;
}
// use the cached ContentProperties instance
xProps.reset( new ContentProperties( *xCachedProps ) );
}
}
bool bNetworkAccessAllowed = true;
if ( !m_bTransient && !bHasAll )
{
// Obtain values from server...
// First, identify whether resource is DAV or not
const ResourceType eType = getResourceType(
xEnv, xResAccess, &bNetworkAccessAllowed );
if ( eType == DAV )
{
// cache lookup... getResourceType may fill the props cache via
// PROPFIND!
if (m_xCachedProps)
{
xCachedProps.reset(
new ContentProperties( *m_xCachedProps ) );
std::vector< OUString > aMissingProps;
if ( xCachedProps->containsAllNames(
rProperties, aMissingProps ) )
{
// All properties are already in cache! No server access
// needed.
bHasAll = true;
}
// use the cached ContentProperties instance
xProps.reset( new ContentProperties( *xCachedProps ) );
}
if ( !bHasAll )
{
// Only DAV resources support PROPFIND
std::vector< OUString > aPropNames;
const uno::Sequence< beans::Property >& aProperties(rProperties);
if ( aProperties.getLength() > 0 )
ContentProperties::UCBNamesToDAVNames(
aProperties, aPropNames );
if ( !aPropNames.empty() )
{
std::vector< DAVResource > resources;
try
{
xResAccess->PROPFIND(
DAVZERO, aPropNames, resources, xEnv );
if ( 1 == resources.size() )
{
#if defined SAL_LOG_INFO
{//debug
// print received resources
std::vector< DAVPropertyValue >::const_iterator it = resources[0].properties.begin();
std::vector< DAVPropertyValue >::const_iterator end = resources[0].properties.end();
while ( it != end )
{
OUString aPropValue;
bool bValue;
uno::Sequence< ucb::LockEntry > aSupportedLocks;
if( (*it).Value >>= aPropValue )
SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" << aPropValue );
else if( (*it).Value >>= bValue )
SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" <<
( bValue ? "true" : "false" ) );
else if( (*it).Value >>= aSupportedLocks )
{
SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << (*it).Name << ":" );
for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n )
{
SAL_INFO( "ucb.ucp.webdav"," scope: "
<< (aSupportedLocks[n].Scope == ucb::LockScope_SHARED ? "shared" : "exclusive")
<< ", type: "
<< (aSupportedLocks[n].Type != ucb::LockType_WRITE ? "" : "write") );
}
}
++it;
}
}
#endif
if (xProps)
xProps->addProperties(
aPropNames,
ContentProperties( resources[ 0 ] ));
else
xProps.reset(
new ContentProperties( resources[ 0 ] ) );
}
}
catch ( DAVException const & e )
{
bNetworkAccessAllowed = bNetworkAccessAllowed
&& shouldAccessNetworkAfterException( e );
if ( !bNetworkAccessAllowed )
{
cancelCommandExecution( e, xEnv );
// unreachable
}
}
}
}
}
if ( bNetworkAccessAllowed )
{
// All properties obtained already?
std::vector< OUString > aMissingProps;
if ( !( xProps
&& xProps->containsAllNames(rProperties, aMissingProps))
// i#121922 for non-DAV, uncacheable properties must be fetched
// regardless of m_bDidGetOrHead.
// But SharePoint may do weird things on HEAD so for DAV
// only do this if required.
&& (eType != DAV || !m_bDidGetOrHead))
{
// Possibly the missing props can be obtained using a HEAD
// request.
std::vector< OUString > aHeaderNames;
ContentProperties::UCBNamesToHTTPNames(
rProperties,
aHeaderNames );
if( eType != DAV )
{
// in case of not DAV PROFIND (previously in program flow) failed
// so we need to add the only prop that's common
// to DAV and NON_DAV: MediaType, that maps to Content-Type
aHeaderNames.emplace_back("Content-Type");
}
if (!aHeaderNames.empty()) try
{
DAVResource resource;
GetPropsUsingHeadRequest(resource, xResAccess, aHeaderNames, xEnv);
m_bDidGetOrHead = true;
if (xProps)
xProps->addProperties(
aMissingProps,
ContentProperties( resource ) );
else
xProps.reset ( new ContentProperties( resource ) );
if (m_eResourceType == NON_DAV)
xProps->addProperties(aMissingProps,
ContentProperties(
aUnescapedTitle,
false));
}
catch ( DAVException const & e )
{
// non "general-purpose servers" may not support HEAD requests
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1
// In this case, perform a partial GET only to get the header info
// vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
// WARNING if the server does not support partial GETs,
// the GET will transfer the whole content
bool bError = true;
DAVException aLastException = e;
OUString aTargetURL = xResAccess->getURL();
if ( e.getError() == DAVException::DAV_HTTP_ERROR )
{
// According to the spec. the origin server SHOULD return
// * 405 (Method Not Allowed):
// the method is known but not allowed for the requested resource
// * 501 (Not Implemented):
// the method is unrecognized or not implemented
// * 404 (SC_NOT_FOUND)
// is for google-code server and for MS IIS 10.0 Web server
// when only GET is enabled
if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED ||
aLastException.getStatus() == SC_METHOD_NOT_ALLOWED ||
aLastException.getStatus() == SC_NOT_FOUND )
{
SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" );
aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false );
lcl_sendPartialGETRequest( bError,
aLastException,
aMissingProps,
aHeaderNames,
xResAccess,
xProps,
xEnv );
m_bDidGetOrHead = !bError;
}
}
if ( bError )
{
DAVOptions aDAVOptionsException;
aDAVOptionsException.setURL( aTargetURL );
// check if the error was SC_NOT_FOUND, meaning that the
// GET fall back didn't succeeded and the element is really missing
// we will consider the resource SC_GONE (410) for some time
// we use SC_GONE because has the same meaning of SC_NOT_FOUND (404)
// see:
// <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09)
// apparently it's not used to mark the missing HEAD method (so far...)
sal_uInt16 ResponseStatusCode =
( aLastException.getStatus() == SC_NOT_FOUND ) ?
SC_GONE :
aLastException.getStatus();
aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode );
aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() );
aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException,
m_nOptsCacheLifeNotFound );
if ( !shouldAccessNetworkAfterException( aLastException ) )
{
cancelCommandExecution( aLastException, xEnv );
// unreachable
}
}
}
}
}
// might trigger HTTP redirect.
// Therefore, title must be updated here.
CurlUri const aUri( xResAccess->getURL() );
aUnescapedTitle = aUri.GetPathBaseNameUnescaped();
if ( eType == UNKNOWN )
{
xProps.reset( new ContentProperties( aUnescapedTitle ) );
}
// For DAV resources we only know the Title, for non-DAV
// resources we additionally know that it is a document.
else if ( eType == DAV )
{
if (!xProps)
xProps.reset(new ContentProperties(aUnescapedTitle));
else
xProps->addProperty(u"Title"_ustr, uno::Any(aUnescapedTitle), true);
}
else
{
if (!xProps)
xProps.reset( new ContentProperties( aUnescapedTitle, false ) );
else
xProps->addProperty(
u"Title"_ustr,
uno::Any( aUnescapedTitle ),
true );
xProps->addProperty(
u"IsFolder"_ustr,
uno::Any( false ),
true );
xProps->addProperty(
u"IsDocument"_ustr,
uno::Any( true ),
true );
xProps->addProperty(
u"ContentType"_ustr,
uno::Any( WEBDAV_CONTENT_TYPE ),
true );
}
}
else
{
// No server access for just created (not yet committed) objects.
// Only a minimal set of properties supported at this stage.
if (m_bTransient)
xProps.reset( new ContentProperties( aUnescapedTitle,
m_bCollection ) );
}
// Add a default for the properties requested but not found.
// Determine still missing properties, add a default.
// Some client function doesn't expect a void uno::Any,
// but instead wants some sort of default.
std::vector< OUString > aMissingProps;
if ( !xProps->containsAllNames(
rProperties, aMissingProps ) )
{
//
for ( std::vector< rtl::OUString >::const_iterator it = aMissingProps.begin();
it != aMissingProps.end(); ++it )
{
// For the time being only a couple of properties need to be added
if ( (*it) == "DateModified" || (*it) == "DateCreated" )
{
util::DateTime aDate;
xProps->addProperty(
(*it),
uno::Any( aDate ),
true );
}
else if (bNetworkAccessAllowed) // don't set these if connection failed
{
// If WebDAV didn't return the resource type, assume default
// This happens e.g. for lists exported by SharePoint
if ((*it) == "IsFolder")
{
xProps->addProperty(
(*it),
uno::Any( false ),
true );
}
else if ((*it) == "IsDocument")
{
xProps->addProperty(
(*it),
uno::Any( true ),
true );
}
}
}
}
sal_Int32 nCount = rProperties.getLength();
for ( sal_Int32 n = 0; n < nCount; ++n )
{
const OUString rName = rProperties[ n ].Name;
if ( rName == "BaseURI" )
{
// Add BaseURI property, if requested.
xProps->addProperty(
u"BaseURI"_ustr,
uno::Any( getBaseURI( xResAccess ) ),
true );
}
else if ( rName == "CreatableContentsInfo" )
{
// Add CreatableContentsInfo property, if requested.
bool bFolder = false;
xProps->getValue( u"IsFolder"_ustr )
>>= bFolder;
xProps->addProperty(
u"CreatableContentsInfo"_ustr,
uno::Any( bFolder
? queryCreatableContentsInfo()
: uno::Sequence< ucb::ContentInfo >() ),
true );
}
}
uno::Reference< sdbc::XRow > xResultRow
= getPropertyValues( xContext,
rProperties,
*xProps,
xProvider,
xIdentifier->getContentIdentifier() );
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
if (!m_xCachedProps)
m_xCachedProps.reset( new CachableContentProperties( *xProps ) );
else
m_xCachedProps->addProperties( *xProps );
m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) );
m_aEscapedTitle = EncodeSegment(aUnescapedTitle);
}
return xResultRow;
}
uno::Sequence< uno::Any > Content::setPropertyValues(
const uno::Sequence< beans::PropertyValue >& rValues,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
{
uno::Reference< ucb::XContentIdentifier > xIdentifier;
rtl::Reference< ContentProvider > xProvider;
bool bTransient;
std::unique_ptr< DAVResourceAccess > xResAccess;
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
xProvider.set( m_pProvider );
xIdentifier.set( m_xIdentifier );
bTransient = m_bTransient;
xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) );
}
uno::Sequence< uno::Any > aRet( rValues.getLength() );
auto aRetRange = asNonConstRange(aRet);
uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() );
sal_Int32 nChanged = 0;
beans::PropertyChangeEvent aEvent;
aEvent.Source = getXWeak();
aEvent.Further = false;
// aEvent.PropertyName =
aEvent.PropertyHandle = -1;
// aEvent.OldValue =
// aEvent.NewValue =
std::vector< ProppatchValue > aProppatchValues;
std::vector< sal_Int32 > aProppatchPropsPositions;
uno::Reference< ucb::XPersistentPropertySet > xAdditionalPropSet;
bool bTriedToGetAdditionalPropSet = false;
bool bExchange = false;
OUString aNewTitle;
OUString aOldTitle;
sal_Int32 nTitlePos = -1;
uno::Reference< beans::XPropertySetInfo > xInfo;
const beans::PropertyValue* pValues = rValues.getConstArray();
sal_Int32 nCount = rValues.getLength();
for ( sal_Int32 n = 0; n < nCount; ++n )
{
const beans::PropertyValue& rValue = pValues[ n ];
const OUString & rName = rValue.Name;
beans::Property aTmpProp;
xProvider->getProperty( rName, aTmpProp );
if ( aTmpProp.Attributes & beans::PropertyAttribute::READONLY )
{
// Read-only property!
aRetRange[ n ] <<= lang::IllegalAccessException(
u"Property is read-only!"_ustr,
getXWeak() );
continue;
}
// Mandatory props.
if ( rName == "ContentType" )
{
// Read-only property!
aRetRange[ n ] <<= lang::IllegalAccessException(
u"Property is read-only!"_ustr,
getXWeak() );
}
else if ( rName == "IsDocument" )
{
// Read-only property!
aRetRange[ n ] <<= lang::IllegalAccessException(
u"Property is read-only!"_ustr,
getXWeak() );
}
else if ( rName == "IsFolder" )
{
// Read-only property!
aRetRange[ n ] <<= lang::IllegalAccessException(
u"Property is read-only!"_ustr,
getXWeak() );
}
else if ( rName == "Title" )
{
OUString aNewValue;
if ( rValue.Value >>= aNewValue )
{
// No empty titles!
if ( aNewValue.getLength() > 0 )
{
try
{
CurlUri const aURI(xIdentifier->getContentIdentifier());
aOldTitle = aURI.GetPathBaseNameUnescaped();
if ( aNewValue != aOldTitle )
{
// modified title -> modified URL -> exchange !
if ( !bTransient )
bExchange = true;
// new value will be set later...
aNewTitle = aNewValue;
// remember position within sequence of values (for
// error handling).
nTitlePos = n;
}
}
catch ( DAVException const & )
{
aRetRange[ n ] <<= lang::IllegalArgumentException(
u"Invalid content identifier!"_ustr,
getXWeak(),
-1 );
}
}
else
{
aRetRange[ n ] <<= lang::IllegalArgumentException(
u"Empty title not allowed!"_ustr,
getXWeak(),
-1 );
}
}
else
{
aRetRange[ n ] <<= beans::IllegalTypeException(
u"Property value has wrong type!"_ustr,
getXWeak() );
}
}
else
{
// Optional props.
OUString aSpecialName;
bool bIsSpecial = DAVProperties::isUCBSpecialProperty( rName, aSpecialName );
if ( !xInfo.is() )
xInfo = getPropertySetInfo( xEnv,
--> --------------------
--> maximum size reached
--> --------------------