diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /ucb/source/ucp/webdav | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
126 files changed, 32869 insertions, 0 deletions
diff --git a/ucb/source/ucp/webdav-neon/ContentProperties.cxx b/ucb/source/ucp/webdav-neon/ContentProperties.cxx new file mode 100644 index 000000000..12bbf4f74 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ContentProperties.cxx @@ -0,0 +1,558 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ +#include <memory> +#include <com/sun/star/util/DateTime.hpp> +#include "NeonUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + +/* +============================================================================= + + Property Mapping + +============================================================================= +HTTP (entity header) WebDAV (property) UCB (property) +============================================================================= + +Allow +Content-Encoding +Content-Language getcontentlanguage +Content-Length getcontentlength Size +Content-Location +Content-MD5 +Content-Range +Content-Type getcontenttype MediaType +Expires +Last-Modified getlastmodified DateModified + creationdate DateCreated + resourcetype IsFolder,IsDocument,ContentType + displayname +ETag (actually getetag +a response header ) + lockdiscovery + supportedlock + source + Title (always taken from URI) + +============================================================================= + +Important: HTTP headers will not be mapped to DAV properties; only to UCB + properties. (Content-Length,Content-Type,Last-Modified) +*/ + + +// ContentProperties Implementation. + + +// static member! +uno::Any ContentProperties::m_aEmptyAny; + +ContentProperties::ContentProperties( const DAVResource& rResource ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + assert( !rResource.uri.isEmpty() && "ContentProperties ctor - Empty resource URI!" ); + + // Title + try + { + NeonUri aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString("Title") ] + = PropertyValue( + uno::makeAny( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString("Title") ] + = PropertyValue( + uno::makeAny( + OUString( + "*** unknown ***" ) ), + true ); + } + + for ( const auto& rProp : rResource.properties ) + { + addProperty( rProp ); + } + + if ( rResource.uri.endsWith("/") ) + m_bTrailingSlash = true; +} + + +ContentProperties::ContentProperties( + const OUString & rTitle, bool bFolder ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString("Title") ] + = PropertyValue( uno::makeAny( rTitle ), true ); + (*m_xProps)[ OUString("IsFolder") ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString("IsDocument") ] + = PropertyValue( uno::makeAny( !bFolder ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString("Title") ] + = PropertyValue( uno::makeAny( rTitle ), true ); +} + + +ContentProperties::ContentProperties() +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ +} + +ContentProperties::ContentProperties(const ContentProperties& rOther) + : m_aEscapedTitle(rOther.m_aEscapedTitle) + , m_xProps(rOther.m_xProps ? new PropertyValueMap(*rOther.m_xProps) : new PropertyValueMap) + , m_bTrailingSlash(rOther.m_bTrailingSlash) +{ +} + + +bool ContentProperties::contains( const OUString & rName ) const +{ + return get( rName ) != nullptr; +} + + +const uno::Any & ContentProperties::getValue( + const OUString & rName ) const +{ + const PropertyValue * pProp = get( rName ); + if ( pProp ) + return pProp->value(); + else + return m_aEmptyAny; +} + + +const PropertyValue * ContentProperties::get( + const OUString & rName ) const +{ + PropertyValueMap::const_iterator it = m_xProps->find( rName ); + const PropertyValueMap::const_iterator end = m_xProps->end(); + + if ( it == end ) + { + it = std::find_if(m_xProps->cbegin(), end, + [&rName](const PropertyValueMap::value_type& rEntry) { + return rEntry.first.equalsIgnoreAsciiCase( rName ); + }); + if ( it != end ) + return &(*it).second; + + return nullptr; + } + else + return &(*it).second; +} + + +// static +void ContentProperties::UCBNamesToDAVNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of DAV properties to obtain from server. + // Append DAV properties needed to obtain requested UCB props. + + + // DAV UCB + // creationdate <- DateCreated + // getlastmodified <- DateModified + // getcontenttype <- MediaType + // getcontentlength <- Size + // resourcetype <- IsFolder, IsDocument, ContentType + // (taken from URI) <- Title + + bool bCreationDate = false; + bool bLastModified = false; + bool bContentType = false; + bool bContentLength = false; + bool bResourceType = false; + + for ( const beans::Property & rProp : rProps ) + { + if ( rProp.Name == "Title" ) + { + // Title is always obtained from resource's URI. + continue; + } + else if ( rProp.Name == "DateCreated" || rProp.Name == DAVProperties::CREATIONDATE ) + { + if ( !bCreationDate ) + { + propertyNames.push_back( DAVProperties::CREATIONDATE ); + bCreationDate = true; + } + } + else if ( rProp.Name == "DateModified" || rProp.Name == DAVProperties::GETLASTMODIFIED ) + { + if ( !bLastModified ) + { + propertyNames.push_back( DAVProperties::GETLASTMODIFIED ); + bLastModified = true; + } + } + else if ( rProp.Name == "MediaType" || rProp.Name == DAVProperties::GETCONTENTTYPE ) + { + if ( !bContentType ) + { + propertyNames.push_back( DAVProperties::GETCONTENTTYPE ); + bContentType = true; + } + } + else if ( rProp.Name == "Size" || rProp.Name == DAVProperties::GETCONTENTLENGTH ) + { + if ( !bContentLength ) + { + propertyNames.push_back( DAVProperties::GETCONTENTLENGTH ); + bContentLength = true; + } + } + else if ( rProp.Name == "ContentType" || rProp.Name == "IsDocument" || rProp.Name == "IsFolder" || rProp.Name == DAVProperties::RESOURCETYPE ) + { + if ( !bResourceType ) + { + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + bResourceType = true; + } + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +// static +void ContentProperties::UCBNamesToHTTPNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames ) +{ + + // Assemble list of HTTP header names to obtain from server. + // Append HTTP headers needed to obtain requested UCB props. + + + // HTTP UCB + // Last-Modified <- DateModified + // Content-Type <- MediaType + // Content-Length <- Size + + for ( const beans::Property & rProp : rProps ) + { + if ( rProp.Name == "DateModified" ) + { + propertyNames.emplace_back("Last-Modified" ); + } + else if ( rProp.Name == "MediaType" ) + { + propertyNames.emplace_back("Content-Type" ); + } + else if ( rProp.Name == "Size" ) + { + propertyNames.emplace_back("Content-Length" ); + } + else + { + propertyNames.push_back( rProp.Name ); + } + } +} + + +bool ContentProperties::containsAllNames( + const uno::Sequence< beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const +{ + rNamesNotContained.clear(); + + for ( const auto& rProp : rProps ) + { + const OUString & rName = rProp.Name; + if ( !contains( rName ) ) + { + // Not found. + rNamesNotContained.push_back( rName ); + } + } + + return rNamesNotContained.empty(); +} + + +void ContentProperties::addProperties( + const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ) +{ + for ( const OUString & rName : rProps ) + { + if ( !contains( rName ) ) // ignore duplicates + { + const PropertyValue * pProp = rContentProps.get( rName ); + if ( pProp ) + { + // Add it. + addProperty( rName, pProp->value(), pProp->isCaseSensitive() ); + } + else + { + addProperty( rName, uno::Any(), false ); + } + } + } +} + + +void ContentProperties::addProperty( const DAVPropertyValue & rProp ) +{ + addProperty( rProp.Name, rProp.Value, rProp.IsCaseSensitive ); +} + + +void ContentProperties::addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ) +{ + if ( rName == DAVProperties::CREATIONDATE ) + { + // Map DAV:creationdate to UCP:DateCreated + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString("DateCreated") ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::DISPLAYNAME ) ) + // { + // } + // else if ( rName.equals( DAVProperties::GETCONTENTLANGUAGE ) ) + // { + // } + else if ( rName == DAVProperties::GETCONTENTLENGTH ) + { + // Map DAV:getcontentlength to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString("Size") ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Length" ) ) + { + // Do NOT map Content-length entity header to DAV:getcontentlength! + // Only DAV resources have this property. + + // Map Content-Length entity header to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString("Size") ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName == DAVProperties::GETCONTENTTYPE ) + { + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString("MediaType") ] + = PropertyValue( rValue, true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Content-Type" ) ) + { + // Do NOT map Content-Type entity header to DAV:getcontenttype! + // Only DAV resources have this property. + + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString("MediaType") ] + = PropertyValue( rValue, true ); + } + // else if ( rName.equals( DAVProperties::GETETAG ) ) + // { + // } + else if ( rName == DAVProperties::GETLASTMODIFIED ) + { + // Map the DAV:getlastmodified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString("DateModified") ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + else if ( rName.equalsIgnoreAsciiCase( "Last-Modified" ) ) + { + // Do not map Last-Modified entity header to DAV:getlastmodified! + // Only DAV resources have this property. + + // Map the Last-Modified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString("DateModified") ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::LOCKDISCOVERY ) ) + // { + // } + else if ( rName == DAVProperties::RESOURCETYPE ) + { + OUString aValue; + rValue >>= aValue; + + // Map DAV:resourcetype to UCP:IsFolder, UCP:IsDocument, UCP:ContentType + bool bFolder = aValue.equalsIgnoreAsciiCase( "collection" ); + + (*m_xProps)[ OUString("IsFolder") ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString("IsDocument") ] + = PropertyValue( uno::makeAny( !bFolder ), true ); + (*m_xProps)[ OUString("ContentType") ] + = PropertyValue( uno::makeAny( bFolder + ? OUString( WEBDAV_COLLECTION_TYPE ) + : OUString( WEBDAV_CONTENT_TYPE ) ), true ); + } + // else if ( rName.equals( DAVProperties::SOURCE ) ) + // { + // } + // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) ) + // { + // } + + // Save property. + (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive ); +} + + +// CachableContentProperties Implementation. + + +namespace +{ + bool isCachable( OUString const & rName, + bool isCaseSensitive ) + { + static const OUString aNonCachableProps [] = + { + DAVProperties::LOCKDISCOVERY, + + DAVProperties::GETETAG, + OUString( "ETag" ), + + OUString( "DateModified" ), + OUString( "Last-Modified" ), + DAVProperties::GETLASTMODIFIED, + + OUString( "Size" ), + OUString( "Content-Length" ), + DAVProperties::GETCONTENTLENGTH, + + OUString( "Date" ) + }; + + for (const auto & rNonCachableProp : aNonCachableProps) + { + if ( isCaseSensitive ) + { + if ( rName == rNonCachableProp ) + return false; + } + else + if ( rName.equalsIgnoreAsciiCase( rNonCachableProp ) ) + return false; + } + return true; + } + +} // namespace + + +CachableContentProperties::CachableContentProperties( + const ContentProperties & rProps ) +{ + addProperties( rProps ); +} + + +void CachableContentProperties::addProperties( + const ContentProperties & rProps ) +{ + const std::unique_ptr< PropertyValueMap > & props = rProps.getProperties(); + + for ( const auto& rProp : *props ) + { + if ( isCachable( rProp.first, rProp.second.isCaseSensitive() ) ) + m_aProps.addProperty( rProp.first, + rProp.second.value(), + rProp.second.isCaseSensitive() ); + } +} + + +void CachableContentProperties::addProperties( + const std::vector< DAVPropertyValue > & rProps ) +{ + for ( const auto& rProp : rProps ) + { + if ( isCachable( rProp.Name, rProp.IsCaseSensitive ) ) + m_aProps.addProperty( rProp ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/ContentProperties.hxx b/ucb/source/ucp/webdav-neon/ContentProperties.hxx new file mode 100644 index 000000000..8f11e8726 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ContentProperties.hxx @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#pragma once + +#include <config_lgpl.h> +#include <memory> +#include <unordered_map> +#include <vector> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "DAVResource.hxx" + +namespace com::sun::star::beans { + struct Property; +} + +namespace webdav_ucp +{ + +struct DAVResource; + +// PropertyValueMap. +class PropertyValue +{ +private: + css::uno::Any m_aValue; + bool m_bIsCaseSensitive; + +public: + PropertyValue() + : m_bIsCaseSensitive( true ) {} + + PropertyValue( const css::uno::Any & rValue, + bool bIsCaseSensitive ) + : m_aValue( rValue), + m_bIsCaseSensitive( bIsCaseSensitive ) {} + + bool isCaseSensitive() const { return m_bIsCaseSensitive; } + const css::uno::Any & value() const { return m_aValue; } + +}; + +typedef std::unordered_map< OUString, PropertyValue> PropertyValueMap; + +class ContentProperties +{ +public: + ContentProperties(); + + explicit ContentProperties( const DAVResource& rResource ); + + // Mini props for transient contents. + ContentProperties( const OUString & rTitle, bool bFolder ); + + // Micro props for non-existing contents. + explicit ContentProperties( const OUString & rTitle ); + + ContentProperties( const ContentProperties & rOther ); + + bool contains( const OUString & rName ) const; + + const css::uno::Any & + getValue( const OUString & rName ) const; + + // Maps the UCB property names contained in rProps with their DAV property + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::PROPFIND. The result from PROPFIND + // (vector< DAVResource >) can be used to create a ContentProperties + // instance which can map DAV properties back to UCB properties. + static void UCBNamesToDAVNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // Maps the UCB property names contained in rProps with their HTTP header + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::HEAD. The result from HEAD (vector< DAVResource >) + // can be used to create a ContentProperties instance which can map header + // names back to UCB properties. + static void UCBNamesToHTTPNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources ); + + // return true, if all properties contained in rProps are contained in + // this ContentProperties instance. Otherwise, false will be returned. + // rNamesNotContained contain the missing names. + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const; + + // adds all properties described by rProps that are actually contained in + // rContentProps to this instance. In case of duplicates the value + // already contained in this will left unchanged. + void addProperties( const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ); + + // overwrites probably existing entry. + void addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ); + + // overwrites probably existing entry. + void addProperty( const DAVPropertyValue & rProp ); + + bool isTrailingSlash() const { return m_bTrailingSlash; } + + const OUString & getEscapedTitle() const { return m_aEscapedTitle; } + + // Not good to expose implementation details, but this is actually an + // internal class. + const std::unique_ptr< PropertyValueMap > & getProperties() const + { return m_xProps; } + +private: + OUString m_aEscapedTitle; + std::unique_ptr< PropertyValueMap > m_xProps; + bool m_bTrailingSlash; + + static css::uno::Any m_aEmptyAny; + + ContentProperties & operator=( const ContentProperties & ) = delete; + + const PropertyValue * get( const OUString & rName ) const; +}; + +class CachableContentProperties +{ +private: + ContentProperties m_aProps; + + CachableContentProperties & operator=( const CachableContentProperties & ) = delete; + CachableContentProperties( const CachableContentProperties & ) = delete; + +public: + explicit CachableContentProperties( const ContentProperties & rProps ); + + void addProperties( const ContentProperties & rProps ); + + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const + { return m_aProps.containsAllNames( rProps, rNamesNotContained ); } + + const css::uno::Any & + getValue( const OUString & rName ) const + { return m_aProps.getValue( rName ); } + + operator const ContentProperties & () const { return m_aProps; } +}; + +} // namespace webdav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx b/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx new file mode 100644 index 000000000..39ca8f938 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVAuthListener.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX + +#include <config_lgpl.h> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ustring.hxx> + +namespace webdav_ucp +{ + +class DAVAuthListener : public salhelper::SimpleReferenceObject +{ + public: + virtual int authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) = 0; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx new file mode 100644 index 000000000..a92720b50 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVAuthListenerImpl.hxx @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENERIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVAUTHLISTENERIMPL_HXX + +#include <config_lgpl.h> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + + +namespace webdav_ucp +{ + + + + + class DAVAuthListener_Impl : public DAVAuthListener + { + public: + + DAVAuthListener_Impl( + const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv, + const OUString & inURL ) + : m_xEnv( xEnv ), m_aURL( inURL ) + { + } + + virtual int authenticate( const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) override; + private: + + const css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + const OUString m_aURL; + + OUString m_aPrevPassword; + OUString m_aPrevUsername; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVException.hxx b/ucb/source/ucp/webdav-neon/DAVException.hxx new file mode 100644 index 000000000..3e1046ce2 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVException.hxx @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX + +#include <config_lgpl.h> +#include <rtl/ustring.hxx> + +namespace webdav_ucp +{ + + +// HTTP/WebDAV status codes + + +const sal_uInt16 SC_NONE = 0; + +// 1xx (Informational - no errors) +const sal_uInt16 SC_CONTINUE = 100; +const sal_uInt16 SC_SWITCHING_PROTOCOLS = 101; +// DAV extensions +const sal_uInt16 SC_PROCESSING = 102; + +//2xx (Successful - no errors) +const sal_uInt16 SC_OK = 200; +const sal_uInt16 SC_CREATED = 201; +const sal_uInt16 SC_ACCEPTED = 202; +const sal_uInt16 SC_NON_AUTHORITATIVE_INFORMATION = 203; +const sal_uInt16 SC_NO_CONTENT = 204; +const sal_uInt16 SC_RESET_CONTENT = 205; +const sal_uInt16 SC_PARTIAL_CONTENT = 206; +// DAV extensions +const sal_uInt16 SC_MULTISTATUS = 207; + +//3xx (Redirection) +const sal_uInt16 SC_MULTIPLE_CHOICES = 300; +const sal_uInt16 SC_MOVED_PERMANENTLY = 301; +const sal_uInt16 SC_MOVED_TEMPORARILY = 302; +const sal_uInt16 SC_SEE_OTHER = 303; +const sal_uInt16 SC_NOT_MODIFIED = 304; +const sal_uInt16 SC_USE_PROXY = 305; +const sal_uInt16 SC_TEMPORARY_REDIRECT = 307; + +//4xx (Client error) +const sal_uInt16 SC_BAD_REQUEST = 400; +const sal_uInt16 SC_UNAUTHORIZED = 401; +const sal_uInt16 SC_PAYMENT_REQUIRED = 402; +const sal_uInt16 SC_FORBIDDEN = 403; +const sal_uInt16 SC_NOT_FOUND = 404; +const sal_uInt16 SC_METHOD_NOT_ALLOWED = 405; +const sal_uInt16 SC_NOT_ACCEPTABLE = 406; +const sal_uInt16 SC_PROXY_AUTHENTICATION_REQUIRED = 407; +const sal_uInt16 SC_REQUEST_TIMEOUT = 408; +const sal_uInt16 SC_CONFLICT = 409; +const sal_uInt16 SC_GONE = 410; +const sal_uInt16 SC_LENGTH_REQUIRED = 411; +const sal_uInt16 SC_PRECONDITION_FAILED = 412; +const sal_uInt16 SC_REQUEST_ENTITY_TOO_LARGE = 413; +const sal_uInt16 SC_REQUEST_URI_TOO_LONG = 414; +const sal_uInt16 SC_UNSUPPORTED_MEDIA_TYPE = 415; +const sal_uInt16 SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; +const sal_uInt16 SC_EXPECTATION_FAILED = 417; +// DAV extensions +const sal_uInt16 SC_UNPROCESSABLE_ENTITY = 422; +const sal_uInt16 SC_LOCKED = 423; +const sal_uInt16 SC_FAILED_DEPENDENCY = 424; + +//5xx (Server error, general <https://tools.ietf.org/html/rfc7231#section-6.6>) +const sal_uInt16 SC_INTERNAL_SERVER_ERROR = 500; +const sal_uInt16 SC_NOT_IMPLEMENTED = 501; +const sal_uInt16 SC_BAD_GATEWAY = 502; +const sal_uInt16 SC_SERVICE_UNAVAILABLE = 503; +const sal_uInt16 SC_GATEWAY_TIMEOUT = 504; +const sal_uInt16 SC_HTTP_VERSION_NOT_SUPPORTED = 505; +// DAV extensions (<https://tools.ietf.org/html/rfc4918#section-11>) +const sal_uInt16 SC_INSUFFICIENT_STORAGE = 507; + +// unofficial status codes only used internally by LO +// used to cache the connection time out event +const sal_uInt16 USC_CONNECTION_TIMED_OUT = 908; + // name resolution failed +const sal_uInt16 USC_LOOKUP_FAILED = 909; +const sal_uInt16 USC_AUTH_FAILED = 910; +const sal_uInt16 USC_AUTHPROXY_FAILED = 911; + + + +class DAVException : public std::exception +{ + public: + enum ExceptionCode { + DAV_HTTP_ERROR = 0, // Generic error, + // mData = server error message, + // mStatusCode = HTTP status code + DAV_HTTP_LOOKUP, // Name lookup failed, + // mData = server[:port] + DAV_HTTP_AUTH, // User authentication failed on server, + // mData = server[:port] + DAV_HTTP_AUTHPROXY, // User authentication failed on proxy, + // mData = proxy server[:port] + DAV_HTTP_CONNECT, // Could not connect to server, + // mData = server[:port] + DAV_HTTP_TIMEOUT, // Connection timed out + // mData = server[:port] + DAV_HTTP_FAILED, // The precondition failed + // mData = server[:port] + DAV_HTTP_RETRY, // Retry request + // mData = server[:port] + DAV_HTTP_REDIRECT, // Request was redirected, + // mData = new URL + DAV_SESSION_CREATE, // session creation error, + // mData = server[:port] + DAV_INVALID_ARG, // invalid argument + + DAV_LOCK_EXPIRED, // DAV lock expired + + DAV_NOT_LOCKED, // not locked + + DAV_LOCKED_SELF, // locked by this OOo session + + DAV_LOCKED // locked by third party + }; + + private: + ExceptionCode mExceptionCode; + OUString mData; + sal_uInt16 mStatusCode; + + public: + explicit DAVException( ExceptionCode inExceptionCode ) : + mExceptionCode( inExceptionCode ), mStatusCode( SC_NONE ) {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData ) : + mExceptionCode( inExceptionCode ), mData( rData ), + mStatusCode( SC_NONE ) {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData, + sal_uInt16 nStatusCode ) : + mExceptionCode( inExceptionCode ), mData( rData ), + mStatusCode( nStatusCode ) {}; + + const ExceptionCode & getError() const { return mExceptionCode; } + const OUString & getData() const { return mData; } + sal_uInt16 getStatus() const { return mStatusCode; } +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVEXCEPTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVProperties.cxx b/ucb/source/ucp/webdav-neon/DAVProperties.cxx new file mode 100644 index 000000000..5d5634936 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVProperties.cxx @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <string.h> +#include "DAVProperties.hxx" + +using namespace webdav_ucp; + +const OUString DAVProperties::CREATIONDATE("DAV:creationdate"); +const OUString DAVProperties::DISPLAYNAME("DAV:displayname"); +const OUString DAVProperties::GETCONTENTLANGUAGE("DAV:getcontentlanguage"); +const OUString DAVProperties::GETCONTENTLENGTH("DAV:getcontentlength"); +const OUString DAVProperties::GETCONTENTTYPE("DAV:getcontenttype"); +const OUString DAVProperties::GETETAG("DAV:getetag"); +const OUString DAVProperties::GETLASTMODIFIED("DAV:getlastmodified"); +const OUString DAVProperties::LOCKDISCOVERY("DAV:lockdiscovery"); +const OUString DAVProperties::RESOURCETYPE("DAV:resourcetype"); +const OUString DAVProperties::SOURCE("DAV:source"); +const OUString DAVProperties::SUPPORTEDLOCK("DAV:supportedlock"); + +const OUString DAVProperties::EXECUTABLE("http://apache.org/dav/props/executable"); + +void DAVProperties::createNeonPropName( const OUString & rFullName, + NeonPropName & rName ) +{ + if ( rFullName.startsWith( "DAV:" ) ) + { + rName.nspace = "DAV:"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( RTL_CONSTASCII_LENGTH( "DAV:" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + rName.nspace = "http://apache.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://apache.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "<prop:" ) ) + { + // Support for 3rd party namespaces/props + + OString aFullName + = OUStringToOString( rFullName, RTL_TEXTENCODING_UTF8 ); + + // Format: <prop:the_propname xmlns:prop="the_namespace"> + + sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" ); + sal_Int32 nLen = aFullName.indexOf( ' ' ) - nStart; + rName.name = strdup( aFullName.copy( nStart, nLen ).getStr() ); + + nStart = aFullName.indexOf( '=', nStart + nLen ) + 2; // after =" + nLen = aFullName.getLength() - RTL_CONSTASCII_LENGTH( "\">" ) - nStart; + rName.nspace = strdup( aFullName.copy( nStart, nLen ).getStr() ); + } + else + { + // Add our namespace to our own properties. + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( rFullName, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } +} + +void DAVProperties::createUCBPropName( const char * nspace, + const char * name, + OUString & rFullName ) +{ + OUString aNameSpace + = OStringToOUString( nspace, RTL_TEXTENCODING_UTF8 ); + OUString aName + = OStringToOUString( name, RTL_TEXTENCODING_UTF8 ); + + if ( aNameSpace.isEmpty() ) + { + // Some servers send XML without proper namespaces. Assume "DAV:" + // in this case, if name is a well-known dav property name. + // Although this is not 100% correct, it solves many problems. + + if ( DAVProperties::RESOURCETYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SUPPORTEDLOCK.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::LOCKDISCOVERY.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::CREATIONDATE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::DISPLAYNAME.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLANGUAGE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLENGTH.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTTYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETETAG.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETLASTMODIFIED.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SOURCE.matchIgnoreAsciiCase( aName, 4 ) ) + aNameSpace = "DAV:"; + } + + // Note: Concatenating strings BEFORE comparing against known namespaces + // is important. See RFC 2815 ( 23.4.2 Meaning of Qualified Names ). + rFullName = aNameSpace; + rFullName += aName; + + if ( rFullName.startsWith( "DAV:" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + // Remove namespace from our own properties. + rFullName = rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ); + } + else + { + // Create property name that encodes, namespace and name ( XML ). + rFullName = "<prop:" + aName + " xmlns:prop=\"" + aNameSpace + "\">"; + } +} + +bool DAVProperties::isUCBDeadProperty( const NeonPropName & rName ) +{ + return ( rName.nspace && + ( rtl_str_compareIgnoreAsciiCase( + rName.nspace, "http://ucb.openoffice.org/dav/props/" ) + == 0 ) ); +} + +bool DAVProperties::isUCBSpecialProperty( + const OUString& rFullName, OUString& rParsedName) +{ + if ( !rFullName.startsWith( "<prop:" ) || !rFullName.endsWith( "\">" ) ) + return false; + + sal_Int32 nStart = strlen( "<prop:" ); + sal_Int32 nEnd = rFullName.indexOf( ' ', nStart ); + if ( nEnd <= nStart ) // incl. -1 for "not found" + return false; + + OUString sPropName( rFullName.copy( nStart, nEnd - nStart ) ); + + // TODO skip whitespaces? + if ( !rFullName.match( "xmlns:prop=\"", ++nEnd ) ) + return false; + + nStart = nEnd + strlen( "xmlns:prop=\"" ); + nEnd = rFullName.indexOf( '"', nStart ); + if ( nEnd != rFullName.getLength() - sal_Int32( strlen( "\">" ) ) + || nEnd == nStart ) + { + return false; + } + + rParsedName = rFullName.copy( nStart, nEnd - nStart ); + if ( !rParsedName.endsWith( "/" ) ) + rParsedName += "/"; + rParsedName += sPropName; + + return rParsedName.getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVProperties.hxx b/ucb/source/ucp/webdav-neon/DAVProperties.hxx new file mode 100644 index 000000000..2064a83b4 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVProperties.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX + +#include <config_lgpl.h> +#include <rtl/ustring.hxx> +#include "NeonTypes.hxx" + +namespace webdav_ucp +{ + +struct DAVProperties +{ + static const OUString CREATIONDATE; + static const OUString DISPLAYNAME; + static const OUString GETCONTENTLANGUAGE; + static const OUString GETCONTENTLENGTH; + static const OUString GETCONTENTTYPE; + static const OUString GETETAG; + static const OUString GETLASTMODIFIED; + static const OUString LOCKDISCOVERY; + static const OUString RESOURCETYPE; + static const OUString SOURCE; + static const OUString SUPPORTEDLOCK; + static const OUString EXECUTABLE; + + static void createNeonPropName( const OUString & rFullName, + NeonPropName & rName ); + static void createUCBPropName ( const char * nspace, + const char * name, + OUString & rFullName ); + + static bool isUCBDeadProperty( const NeonPropName & rName ); + static bool isUCBSpecialProperty( const OUString & rFullName, + OUString & rParsedName ); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx new file mode 100644 index 000000000..98e974d61 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVRequestEnvironment.hxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX + +#include <config_lgpl.h> +#include <vector> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + +namespace webdav_ucp +{ + typedef std::pair< OUString, OUString > DAVRequestHeader; + typedef std::vector< DAVRequestHeader > DAVRequestHeaders; + +struct DAVRequestEnvironment +{ + OUString m_aRequestURI; + rtl::Reference< DAVAuthListener > m_xAuthListener; +// rtl::Reference< DAVStatusListener > m_xStatusListener; +// rtl::Reference< DAVProgressListener > m_xStatusListener; + DAVRequestHeaders m_aRequestHeaders; + uno::Reference< ucb::XCommandEnvironment > m_xEnv; + +DAVRequestEnvironment( const OUString & rRequestURI, + const rtl::Reference< DAVAuthListener > & xListener, + const DAVRequestHeaders & rRequestHeaders, + const uno::Reference< ucb::XCommandEnvironment > & xEnv) + : m_aRequestURI( rRequestURI ), + m_xAuthListener( xListener ), + m_aRequestHeaders( rRequestHeaders ), + m_xEnv( xEnv ){} + + DAVRequestEnvironment() {} +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVREQUESTENVIRONMENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVResource.hxx b/ucb/source/ucp/webdav-neon/DAVResource.hxx new file mode 100644 index 000000000..219b06444 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResource.hxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX + +#include <config_lgpl.h> +#include <vector> + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_ucp +{ + +struct DAVPropertyValue +{ + OUString Name; + css::uno::Any Value; + bool IsCaseSensitive; + + DAVPropertyValue() : IsCaseSensitive( true ) {} +}; + +struct DAVResource +{ + OUString uri; + std::vector< DAVPropertyValue > properties; + + DAVResource() {} + explicit DAVResource( const OUString & inUri ) : uri( inUri ) {} +}; + +struct DAVResourceInfo +{ + std::vector < OUString > properties; + + bool operator==( const struct DAVResourceInfo& a ) const + { + return (properties == a.properties ); + } +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx b/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx new file mode 100644 index 000000000..f0576e049 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResourceAccess.cxx @@ -0,0 +1,1192 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/ucb/XWebDAVCommandEnvironment.hpp> + +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <comphelper/seekableinput.hxx> + +#include "DAVAuthListenerImpl.hxx" +#include "DAVResourceAccess.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + + +// DAVAuthListener_Impl Implementation. + +static constexpr sal_uInt32 g_nRedirectLimit = 5; + +// virtual +int DAVAuthListener_Impl::authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials ) +{ + if ( m_xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = m_xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + // #102871# - Supply username and password from previous try. + // Password container service depends on this! + if ( inoutUserName.isEmpty() ) + inoutUserName = m_aPrevUsername; + + if ( outPassWord.isEmpty() ) + outPassWord = m_aPrevPassword; + + rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest + = new ucbhelper::SimpleAuthenticationRequest( + m_aURL, inHostName, inRealm, inoutUserName, + outPassWord, bCanUseSystemCredentials ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( !xAbort.is() ) + { + const rtl::Reference< + ucbhelper::InteractionSupplyAuthentication > & xSupp + = xRequest->getAuthenticationSupplier(); + + bool bUseSystemCredentials = false; + + if ( bCanUseSystemCredentials ) + bUseSystemCredentials + = xSupp->getUseSystemCredentials(); + + if ( bUseSystemCredentials ) + { + // This is the (strange) way to tell neon to use + // system credentials. + inoutUserName.clear(); + outPassWord.clear(); + } + else + { + inoutUserName = xSupp->getUserName(); + outPassWord = xSupp->getPassword(); + } + + // #102871# - Remember username and password. + m_aPrevUsername = inoutUserName; + m_aPrevPassword = outPassWord; + + // go on. + return 0; + } + } + } + } + // Abort. + return -1; +} + + +// DAVResourceAccess Implementation. + + +DAVResourceAccess::DAVResourceAccess( + const uno::Reference< uno::XComponentContext > & rxContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ) +: m_aURL( rURL ), + m_xSessionFactory( rSessionFactory ), + m_xContext( rxContext ) +{ +} + + +DAVResourceAccess::DAVResourceAccess( const DAVResourceAccess & rOther ) +: m_aURL( rOther.m_aURL ), + m_aPath( rOther.m_aPath ), + m_aFlags( rOther.m_aFlags ), + m_xSession( rOther.m_xSession ), + m_xSessionFactory( rOther.m_xSessionFactory ), + m_xContext( rOther.m_xContext ), + m_aRedirectURIs( rOther.m_aRedirectURIs ) +{ +} + + +DAVResourceAccess & DAVResourceAccess::operator=( + const DAVResourceAccess & rOther ) +{ + m_aURL = rOther.m_aURL; + m_aPath = rOther.m_aPath; + m_aFlags = rOther.m_aFlags; + m_xSession = rOther.m_xSession; + m_xSessionFactory = rOther.m_xSessionFactory; + m_xContext = rOther.m_xContext; + m_aRedirectURIs = rOther.m_aRedirectURIs; + + return *this; +} + +void DAVResourceAccess::OPTIONS( + DAVOptions & rOptions, + const css::uno::Reference< + css::ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + css::ucb::WebDAVHTTPMethod_OPTIONS, + aHeaders ); + + m_xSession->OPTIONS( getRequestURI(), + rOptions, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rPropertyNames, + rResources, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rResInfo, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ) ; + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPPATCH( + const std::vector< ProppatchValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPPATCH, + aHeaders ); + + m_xSession->PROPPATCH( getRequestURI(), + rValues, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::HEAD( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_HEAD, + aHeaders ); + + m_xSession->HEAD( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + +// used as HEAD substitute when HEAD is not implemented on server +void DAVResourceAccess::GET0( + DAVRequestHeaders &rRequestHeaders, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + rRequestHeaders ); + + m_xSession->GET0( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + rRequestHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + bool bRetry; + int errorCount = 0; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::abort() +{ + initialize(); + m_xSession->abort(); +} + + +namespace { + + /// @throws DAVException + void resetInputStream( const uno::Reference< io::XInputStream > & rStream ) + { + try + { + uno::Reference< io::XSeekable > xSeekable( + rStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + xSeekable->seek( 0 ); + return; + } + } + catch ( lang::IllegalArgumentException const & ) + { + } + catch ( io::IOException const & ) + { + } + + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + +} // namespace + + +void DAVResourceAccess::PUT( + const uno::Reference< io::XInputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + resetInputStream( xSeekableStream ); + + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PUT, + aHeaders ); + + m_xSession->PUT( getRequestURI(), + xSeekableStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + xStream = m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + return GET( xEnv ); + } + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + uno::Reference< io::XOutputStream > & rOutputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + rOutputStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + GET( rOutputStream, xEnv ); + return; + } + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MKCOL( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MKCOL, + aHeaders ); + + m_xSession->MKCOL( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::COPY( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_COPY, + aHeaders ); + + m_xSession->COPY( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MOVE( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MOVE, + aHeaders ); + + m_xSession->MOVE( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::DESTROY( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_DELETE, + aHeaders ); + + m_xSession->DESTROY( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +// set new lock. +void DAVResourceAccess::LOCK( + ucb::Lock & inLock, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_LOCK, + aHeaders ); + + m_xSession->LOCK( getRequestURI(), + inLock, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::UNLOCK( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_UNLOCK, + aHeaders ); + + m_xSession->UNLOCK( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( const DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::setFlags( const uno::Sequence< beans::NamedValue >& rFlags ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_aFlags = rFlags; +} + + +void DAVResourceAccess::setURL( const OUString & rNewURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_aURL = rNewURL; + m_aPath.clear(); // Next initialize() will create new session. +} + + +// init dav session and path +void DAVResourceAccess::initialize() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( m_aPath.isEmpty() ) + { + NeonUri aURI( m_aURL ); + const OUString& aPath( aURI.GetPath() ); + + /* #134089# - Check URI */ + if ( aPath.isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + /* #134089# - Check URI */ + if ( aURI.GetHost().isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + if ( !m_xSession.is() || !m_xSession->CanUse( m_aURL, m_aFlags ) ) + { + m_xSession.clear(); + + // create new webdav session + m_xSession + = m_xSessionFactory->createDAVSession( m_aURL, m_aFlags, m_xContext ); + + if ( !m_xSession.is() ) + return; + } + + // Own URI is needed to redirect cycle detection. + m_aRedirectURIs.push_back( aURI ); + + // Success. + m_aPath = aPath; + + // Not only the path has to be encoded + m_aURL = aURI.GetURI(); + } +} + + +const OUString & DAVResourceAccess::getRequestURI() const +{ + assert( m_xSession.is() && "DAVResourceAccess::getRequestURI - Not initialized!" ); + + // In case a proxy is used we have to use the absolute URI for a request. + if ( m_xSession->UsesProxy() ) + return m_aURL; + + return m_aPath; +} + + +// static +void DAVResourceAccess::getUserRequestHeaders( + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ) +{ + if ( xEnv.is() ) + { + uno::Reference< ucb::XWebDAVCommandEnvironment > xDAVEnv( + xEnv, uno::UNO_QUERY ); + + if ( xDAVEnv.is() ) + { + const uno::Sequence< beans::StringPair > aRequestHeaders + = xDAVEnv->getUserRequestHeaders( rURI, eMethod ); + + for ( const auto& rRequestHeader : aRequestHeaders ) + { + rRequestHeaders.emplace_back( + rRequestHeader.First, + rRequestHeader.Second ); + } + } + } + + // Make sure a User-Agent header is always included, as at least + // en.wikipedia.org:80 forces back 403 "Scripts should use an informative + // User-Agent string with contact information, or they may be IP-blocked + // without notice" otherwise: + if ( std::any_of(rRequestHeaders.begin(), rRequestHeaders.end(), + [](const DAVRequestHeader& rHeader) { return rHeader.first.equalsIgnoreAsciiCase( "User-Agent" ); }) ) + { + return; + } + rRequestHeaders.emplace_back( "User-Agent", "LibreOffice" ); +} + +// This function member implements the control on cyclical redirections +bool DAVResourceAccess::detectRedirectCycle( + const OUString& rRedirectURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + NeonUri aUri( rRedirectURL ); + + // Check for maximum number of redirections + // according to <https://tools.ietf.org/html/rfc7231#section-6.4>. + // A practical limit may be 5, due to earlier specifications: + // <https://tools.ietf.org/html/rfc2068#section-10.3> + // it can be raised keeping in mind the added net activity. + if( static_cast< size_t >( g_nRedirectLimit ) <= m_aRedirectURIs.size() ) + return true; + + // try to detect a cyclical redirection + return std::any_of(m_aRedirectURIs.begin(), m_aRedirectURIs.end(), + [&aUri](const NeonUri& rUri) { + // if equal, cyclical redirection detected + return aUri == rUri; }); +} + + +void DAVResourceAccess::resetUri() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( !m_aRedirectURIs.empty() ) + { + std::vector< NeonUri >::const_iterator it = m_aRedirectURIs.begin(); + + NeonUri aUri( *it ); + m_aRedirectURIs.clear(); + setURL ( aUri.GetURI() ); + initialize(); + } +} + + +bool DAVResourceAccess::handleException( const DAVException & e, int errorCount ) +{ + switch ( e.getError() ) + { + case DAVException::DAV_HTTP_REDIRECT: + if ( !detectRedirectCycle( e.getData() ) ) + { + // set new URL and path. + setURL( e.getData() ); + initialize(); + return true; + } + return false; + // #67048# copy & paste images doesn't display. This bug refers + // to an old OOo problem about getting resources from sites with a bad connection. + // If we have a bad connection try again. Up to three times. + case DAVException::DAV_HTTP_ERROR: + // retry up to three times, if not a client-side error (4xx error codes) + if ( e.getStatus() < SC_BAD_REQUEST && errorCount < 3 ) + return true; + // check the server side errors + switch( e.getStatus() ) + { + // the HTTP server side response status codes that can be retried + case SC_BAD_GATEWAY: // retry, can be an excessive load + case SC_GATEWAY_TIMEOUT: // retry, may be we get lucky + case SC_SERVICE_UNAVAILABLE: // retry, the service may become available + case SC_INSUFFICIENT_STORAGE: // space may be freed, retry + { + return errorCount < 3; + } + break; + // all the other HTTP server response status codes are NOT retry + default: + return false; + } + break; + // if connection has said retry then retry! + case DAVException::DAV_HTTP_RETRY: + return true; + default: + return false; // Abort + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVResourceAccess.hxx b/ucb/source/ucp/webdav-neon/DAVResourceAccess.hxx new file mode 100644 index 000000000..30ec5256f --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVResourceAccess.hxx @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX + +#include <config_lgpl.h> +#include <vector> +#include <rtl/ustring.hxx> +#include <rtl/ref.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/WebDAVHTTPMethod.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" +#include "DAVException.hxx" +#include "DAVSession.hxx" +#include "DAVResource.hxx" +#include "DAVTypes.hxx" +#include "NeonUri.hxx" + +namespace webdav_ucp +{ + +class DAVSessionFactory; + +class DAVResourceAccess +{ + osl::Mutex m_aMutex; + OUString m_aURL; + OUString m_aPath; + css::uno::Sequence< css::beans::NamedValue > m_aFlags; + rtl::Reference< DAVSession > m_xSession; + rtl::Reference< DAVSessionFactory > m_xSessionFactory; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::vector< NeonUri > m_aRedirectURIs; + +public: + DAVResourceAccess( const css::uno::Reference< css::uno::XComponentContext > & rxContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ); + DAVResourceAccess( const DAVResourceAccess & rOther ); + + DAVResourceAccess & operator=( const DAVResourceAccess & rOther ); + + /// @throws DAVException + void setFlags( const css::uno::Sequence< css::beans::NamedValue >& rFlags ); + + /// @throws DAVException + void setURL( const OUString & rNewURL ); + + void resetUri(); + + const OUString & getURL() const { return m_aURL; } + + const rtl::Reference< DAVSessionFactory >& getSessionFactory() const + { return m_xSessionFactory; } + + // DAV methods + + /// @throws DAVException + void + OPTIONS( DAVOptions & rOptions, + const css::uno::Reference< + css::ucb::XCommandEnvironment > & xEnv ); + + // allprop & named + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // propnames + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + PROPPATCH( const std::vector< ProppatchValue > & rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + HEAD( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::RuntimeException + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // used as HEAD substitute when HEAD is not implemented on server + /// @throws DAVException + void + GET0( DAVRequestHeaders & rRequestHeaders, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::RuntimeException + /// @throws DAVException + void + PUT( const css::uno::Reference< css::io::XInputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws css::uno::RuntimeException + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::RuntimeException + /// @throws DAVException + void + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + css::uno::Reference< css::io::XOutputStream > & rOutputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + MKCOL( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + COPY( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + MOVE( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + DESTROY( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // set new lock. + /// @throws DAVException + void + LOCK( css::ucb::Lock & inLock, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + UNLOCK( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + abort(); + + // helper + static void + getUserRequestHeaders( + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + css::ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ); + + /// @throws DAVException + bool handleException( const DAVException & e, int errorCount ); + +private: + const OUString & getRequestURI() const; + /// @throws DAVException + bool detectRedirectCycle( const OUString& rRedirectURL ); + /// @throws DAVException + void initialize(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVRESOURCEACCESS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVSession.hxx b/ucb/source/ucp/webdav-neon/DAVSession.hxx new file mode 100644 index 000000000..72eaf39bd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSession.hxx @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX + +#include <config_lgpl.h> +#include <memory> +#include <rtl/ustring.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include "DAVProperties.hxx" +#include "DAVResource.hxx" +#include "DAVSessionFactory.hxx" +#include "DAVTypes.hxx" +#include "DAVRequestEnvironment.hxx" + +namespace com::sun::star::beans { + struct NamedValue; +} + +namespace com::sun::star::ucb { + struct Lock; +} + +namespace webdav_ucp +{ + +class DAVSession +{ +public: + void acquire() + { + osl_atomic_increment( &m_nRefCount ); + } + + void release() + { + if ( osl_atomic_decrement( &m_nRefCount ) == 0 ) + { + m_xFactory->releaseElement( this ); + delete this; + } + } + + virtual bool CanUse( const OUString & inPath, + const css::uno::Sequence< css::beans::NamedValue >& rFlags ) = 0; + + virtual bool UsesProxy() = 0; + + // DAV methods + + /// @throws std::exception + virtual void OPTIONS( const OUString & inPath, + DAVOptions& rOptions, + const DAVRequestEnvironment & rEnv ) = 0; + + // allprop & named + /// @throws std::exception + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropertyNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) = 0; + + // propnames + /// @throws std::exception + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + // used as HEAD substitute when HEAD is not implemented on server + /// @throws std::exception + virtual void + GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream >& s, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual css::uno::Reference< css::io::XInputStream > + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + css::uno::Reference< css::io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void COPY( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite ) = 0; + + /// @throws std::exception + virtual void MOVE( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite ) = 0; + + /// @throws std::exception + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + // set new lock. + /// @throws std::exception + virtual void LOCK( const OUString & inPath, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws std::exception + virtual void abort() = 0; + +protected: + rtl::Reference< DAVSessionFactory > m_xFactory; + + explicit DAVSession( rtl::Reference< DAVSessionFactory > const & rFactory ) + : m_xFactory( rFactory ), m_nRefCount( 0 ) {} + + virtual ~DAVSession() {} + +private: + DAVSessionFactory::Map::iterator m_aContainerIt; + oslInterlockedCount m_nRefCount; + + friend class DAVSessionFactory; + friend struct std::default_delete< DAVSession >; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx b/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx new file mode 100644 index 000000000..fd6fca081 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSessionFactory.cxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <memory> +#include "DAVSessionFactory.hxx" +#include "NeonSession.hxx" +#include "NeonUri.hxx" +#include <osl/diagnose.h> + +using namespace webdav_ucp; +using namespace com::sun::star; + +DAVSessionFactory::~DAVSessionFactory() +{ +} + +rtl::Reference< DAVSession > DAVSessionFactory::createDAVSession( + const OUString & inUri, + const uno::Sequence< beans::NamedValue >& rFlags, + const uno::Reference< uno::XComponentContext > & rxContext ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_xContext = rxContext; + + if (!m_xProxyDecider) + m_xProxyDecider.reset( new ucbhelper::InternetProxyDecider( rxContext ) ); + + Map::iterator aIt = std::find_if(m_aMap.begin(), m_aMap.end(), + [&inUri, &rFlags](const Map::value_type& rEntry) { return rEntry.second->CanUse( inUri, rFlags ); }); + + if ( aIt == m_aMap.end() ) + { + NeonUri aURI( inUri ); + + std::unique_ptr<DAVSession> xElement( + new NeonSession(this, inUri, rFlags, *m_xProxyDecider)); + + aIt = m_aMap.emplace( inUri, xElement.get() ).first; + aIt->second->m_aContainerIt = aIt; + xElement.release(); + return aIt->second; + } + else if ( osl_atomic_increment( &aIt->second->m_nRefCount ) > 1 ) + { + rtl::Reference< DAVSession > xElement( aIt->second ); + osl_atomic_decrement( &aIt->second->m_nRefCount ); + return xElement; + } + else + { + osl_atomic_decrement( &aIt->second->m_nRefCount ); + aIt->second->m_aContainerIt = m_aMap.end(); + + // If URL scheme is different from http or https we definitely + // have to use a proxy and therefore can optimize the getProxy + // call a little: + NeonUri aURI( inUri ); + + aIt->second = new NeonSession(this, inUri, rFlags, *m_xProxyDecider); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + +void DAVSessionFactory::releaseElement( DAVSession const * pElement ) +{ + OSL_ASSERT( pElement ); + osl::MutexGuard aGuard( m_aMutex ); + if ( pElement->m_aContainerIt != m_aMap.end() ) + m_aMap.erase( pElement->m_aContainerIt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVSessionFactory.hxx b/ucb/source/ucp/webdav-neon/DAVSessionFactory.hxx new file mode 100644 index 000000000..d74e05aeb --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVSessionFactory.hxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX + +#ifdef min +#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min... +#endif +#include <config_lgpl.h> +#include <map> +#include <memory> +#include <osl/mutex.hxx> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ref.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <ucbhelper/proxydecider.hxx> + +using namespace com::sun::star; + +namespace com::sun::star::beans { + struct NamedValue; +} + +namespace com::sun::star::lang { + class XMultiServiceFactory; +} + +namespace webdav_ucp +{ + +class DAVSession; + +class DAVSessionFactory : public salhelper::SimpleReferenceObject +{ +public: + virtual ~DAVSessionFactory() override; + + /// @throws DAVException + rtl::Reference< DAVSession > + createDAVSession( const OUString & inUri, + const ::uno::Sequence< css::beans::NamedValue >& rFlags, + const ::uno::Reference< ::uno::XComponentContext >& rxContext ); + + const ::uno::Reference< ::uno::XComponentContext >& getComponentContext() const { return m_xContext; } +private: + typedef std::map< OUString, DAVSession * > Map; + + Map m_aMap; + osl::Mutex m_aMutex; + std::unique_ptr< ucbhelper::InternetProxyDecider > m_xProxyDecider; + + ::uno::Reference< ::uno::XComponentContext > m_xContext; + + void releaseElement( DAVSession const * pElement ); + + friend class DAVSession; +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVSESSIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DAVTypes.cxx b/ucb/source/ucp/webdav-neon/DAVTypes.cxx new file mode 100644 index 000000000..6cf4a01b3 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVTypes.cxx @@ -0,0 +1,193 @@ +/* -*- 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/. + */ + + +#include <osl/time.h> + +#include "DAVTypes.hxx" +#include "../inc/urihelper.hxx" +#include "NeonUri.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + +// DAVOptions implementation + +DAVOptions::DAVOptions() : + m_isClass1( false ), + m_isClass2( false ), + m_isClass3( false ), + m_isHeadAllowed( true ), + m_isLocked( false ), + m_aAllowedMethods(), + m_nStaleTime( 0 ), + m_nRequestedTimeLife( 0 ), + m_sURL(), + m_sRedirectedURL(), + m_nHttpResponseStatusCode( 0 ), + m_sHttpResponseStatusText() +{ +} + +DAVOptions::DAVOptions( const DAVOptions & rOther ) : + m_isClass1( rOther.m_isClass1 ), + m_isClass2( rOther.m_isClass2 ), + m_isClass3( rOther.m_isClass3 ), + m_isHeadAllowed( rOther.m_isHeadAllowed ), + m_isLocked( rOther.m_isLocked ), + m_aAllowedMethods( rOther.m_aAllowedMethods ), + m_nStaleTime( rOther.m_nStaleTime ), + m_nRequestedTimeLife( rOther.m_nRequestedTimeLife ), + m_sURL( rOther.m_sURL ), + m_sRedirectedURL( rOther.m_sRedirectedURL), + m_nHttpResponseStatusCode( rOther.m_nHttpResponseStatusCode ), + m_sHttpResponseStatusText( rOther.m_sHttpResponseStatusText ) +{ +} + +DAVOptions::~DAVOptions() +{ +} + +DAVOptions & DAVOptions::operator=( const DAVOptions& rOpts ) +{ + m_isClass1 = rOpts.m_isClass1; + m_isClass2 = rOpts.m_isClass2; + m_isClass3 = rOpts.m_isClass3; + m_isLocked = rOpts.m_isLocked; + m_isHeadAllowed = rOpts.m_isHeadAllowed; + m_aAllowedMethods = rOpts.m_aAllowedMethods; + m_nStaleTime = rOpts.m_nStaleTime; + m_nRequestedTimeLife = rOpts.m_nRequestedTimeLife; + m_sURL = rOpts.m_sURL; + m_sRedirectedURL = rOpts.m_sRedirectedURL; + m_nHttpResponseStatusCode = rOpts.m_nHttpResponseStatusCode; + m_sHttpResponseStatusText = rOpts.m_sHttpResponseStatusText; + return *this; +} + +bool DAVOptions::operator==( const DAVOptions& rOpts ) const +{ + return + m_isClass1 == rOpts.m_isClass1 && + m_isClass2 == rOpts.m_isClass2 && + m_isClass3 == rOpts.m_isClass3 && + m_isLocked == rOpts.m_isLocked && + m_isHeadAllowed == rOpts.m_isHeadAllowed && + m_aAllowedMethods == rOpts.m_aAllowedMethods && + m_nStaleTime == rOpts.m_nStaleTime && + m_nRequestedTimeLife == rOpts.m_nRequestedTimeLife && + m_sURL == rOpts.m_sURL && + m_sRedirectedURL == rOpts.m_sRedirectedURL && + m_nHttpResponseStatusCode == rOpts.m_nHttpResponseStatusCode && + m_sHttpResponseStatusText == rOpts.m_sHttpResponseStatusText; +} + + +// DAVOptionsCache implementation + +DAVOptionsCache::DAVOptionsCache() +{ +} + +DAVOptionsCache::~DAVOptionsCache() +{ +} + +bool DAVOptionsCache::getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + // search the URL in the static map + DAVOptionsMap::iterator it = m_aTheCache.find( aEncodedUrl ); + if ( it == m_aTheCache.end() ) + return false; + else + { + // check if the capabilities are stale, before restoring + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( (*it).second.getStaleTime() < t1.Seconds ) + { + // if stale, remove from cache, do not restore + m_aTheCache.erase( it ); + return false; + // return false instead + } + rDAVOptions = (*it).second; + return true; + } +} + +void DAVOptionsCache::removeDAVOptions( const OUString & rURL ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } +} + +void DAVOptionsCache::addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aURL( rDAVOptions.getURL() ); + + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( aURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + rDAVOptions.setURL( aEncodedUrl ); + +// unchanged, it may be used to access a server + OUString aRedirURL( rDAVOptions.getRedirectedURL() ); + rDAVOptions.setRedirectedURL( aRedirURL ); + + // check if already cached + DAVOptionsMap::iterator it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { // already in cache, check LifeTime + if ( (*it).second.getRequestedTimeLife() == nLifeTime ) + return; // same lifetime, do nothing + } + // not in cache, add it + TimeValue t1; + osl_getSystemTime( &t1 ); + rDAVOptions.setStaleTime( t1.Seconds + nLifeTime ); + + m_aTheCache[ aEncodedUrl ] = rDAVOptions; +} + +void DAVOptionsCache::setHeadAllowed( const OUString & rURL, const bool HeadAllowed ) +{ + osl::MutexGuard aGuard( m_aMutex ); + OUString aEncodedUrl( ucb_impl::urihelper::encodeURI( NeonUri::unescape( rURL ) ) ); + normalizeURLLastChar( aEncodedUrl ); + + DAVOptionsMap::iterator it = m_aTheCache.find( aEncodedUrl ); + if ( it != m_aTheCache.end() ) + { + // first check for stale + TimeValue t1; + osl_getSystemTime( &t1 ); + if( (*it).second.getStaleTime() < t1.Seconds ) + { + m_aTheCache.erase( it ); + return; + } + // check if the resource was present on server + (*it).second.setHeadAllowed( HeadAllowed ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-neon/DAVTypes.hxx b/ucb/source/ucp/webdav-neon/DAVTypes.hxx new file mode 100644 index 000000000..0f6a34a3d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DAVTypes.hxx @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX + +#include <config_lgpl.h> +#include <map> +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_ucp +{ +/* Excerpt from RFC 4918 + <https://tools.ietf.org/html/rfc4918#section-18> + + 18.1 Class 1 + + A class 1 compliant resource MUST meet all "MUST" requirements in all + sections of this document. + + Class 1 compliant resources MUST return, at minimum, the value "1" in + the DAV header on all responses to the OPTIONS method. + + 18.2 Class 2 + + A class 2 compliant resource MUST meet all class 1 requirements and + support the LOCK method, the DAV:supportedlock property, the DAV: + lockdiscovery property, the Time-Out response header and the Lock- + Token request header. A class 2 compliant resource SHOULD also + support the Timeout request header and the 'owner' XML element. + + Class 2 compliant resources MUST return, at minimum, the values "1" + and "2" in the DAV header on all responses to the OPTIONS method. + + 18.3. Class 3 + + A resource can explicitly advertise its support for the revisions to + [RFC2518] made in this document. Class 1 MUST be supported as well. + Class 2 MAY be supported. Advertising class 3 support in addition to + class 1 and 2 means that the server supports all the requirements in + this specification. Advertising class 3 and class 1 support, but not + class 2, means that the server supports all the requirements in this + specification except possibly those that involve locking support. + +*/ + + class DAVOptions final + { + private: + bool m_isClass1; + bool m_isClass2; + bool m_isClass3; + /// for server that do not implement it + bool m_isHeadAllowed; + /// Internally used to maintain the locked state of the resource, only if it's a Class 2 resource + bool m_isLocked; + /// contains the methods allowed on this resource + OUString m_aAllowedMethods; + + /// target time when this capability becomes stale + sal_uInt32 m_nStaleTime; + sal_uInt32 m_nRequestedTimeLife; + OUString m_sURL; + OUString m_sRedirectedURL; + + /// The cached HTT response status code. It's 0 if the code was dealt with and there is no need to cache it + sal_uInt16 m_nHttpResponseStatusCode; + /// The cached string with the server returned HTTP response status code string, corresponds to m_nHttpResponseStatusCode. + OUString m_sHttpResponseStatusText; + + public: + DAVOptions(); + + DAVOptions( const DAVOptions & rOther ); + + ~DAVOptions(); + + bool isClass1() const { return m_isClass1; }; + void setClass1( bool Class1 = true ) { m_isClass1 = Class1; }; + + bool isClass2() const { return m_isClass2; }; + void setClass2( bool Class2 = true ) { m_isClass2 = Class2; }; + + bool isClass3() const { return m_isClass3; }; + void setClass3( bool Class3 = true ) { m_isClass3 = Class3; }; + + bool isHeadAllowed() const { return m_isHeadAllowed; }; + void setHeadAllowed( bool HeadAllowed = true ) { m_isHeadAllowed = HeadAllowed; }; + + sal_uInt32 getStaleTime() const { return m_nStaleTime ; }; + void setStaleTime( const sal_uInt32 nStaleTime ) { m_nStaleTime = nStaleTime; }; + + sal_uInt32 getRequestedTimeLife() const { return m_nRequestedTimeLife; }; + void setRequestedTimeLife( const sal_uInt32 nRequestedTimeLife ) { m_nRequestedTimeLife = nRequestedTimeLife; }; + + const OUString & getURL() const { return m_sURL; }; + void setURL( const OUString & sURL ) { m_sURL = sURL; }; + + const OUString & getRedirectedURL() const { return m_sRedirectedURL; }; + void setRedirectedURL( const OUString & sRedirectedURL ) { m_sRedirectedURL = sRedirectedURL; }; + + void setAllowedMethods( const OUString & aAllowedMethods ) { m_aAllowedMethods = aAllowedMethods; } ; + const OUString & getAllowedMethods() const { return m_aAllowedMethods; } ; + bool isLockAllowed() const { return ( m_aAllowedMethods.indexOf( "LOCK" ) != -1 ); }; + + void setLocked( bool locked = true ) { m_isLocked = locked; } ; + bool isLocked() const { return m_isLocked; }; + + sal_uInt16 getHttpResponseStatusCode() const { return m_nHttpResponseStatusCode; }; + void setHttpResponseStatusCode( const sal_uInt16 nHttpResponseStatusCode ) { m_nHttpResponseStatusCode = nHttpResponseStatusCode; }; + + const OUString & getHttpResponseStatusText() const { return m_sHttpResponseStatusText; }; + void setHttpResponseStatusText( const OUString & rHttpResponseStatusText ) { m_sHttpResponseStatusText = rHttpResponseStatusText; }; + + void init() { + m_isClass1 = false; + m_isClass2 = false; + m_isClass3 = false; + m_isHeadAllowed = true; + m_isLocked = false; + m_aAllowedMethods.clear(); + m_nStaleTime = 0; + m_nRequestedTimeLife = 0; + m_sURL.clear(); + m_sRedirectedURL.clear(); + m_nHttpResponseStatusCode = 0; + m_sHttpResponseStatusText.clear(); + }; + + DAVOptions & operator=( const DAVOptions& rOpts ); + bool operator==( const DAVOptions& rOpts ) const; + + }; + + // TODO: the OUString key element in std::map needs to be changed with a URI representation + // along with a specific compare (std::less) implementation, as suggested in + // <https://tools.ietf.org/html/rfc3986#section-6>, to find by URI and not by string comparison + typedef std::map< OUString, DAVOptions, + std::less< OUString > > DAVOptionsMap; + + class DAVOptionsCache + { + DAVOptionsMap m_aTheCache; + osl::Mutex m_aMutex; + public: + explicit DAVOptionsCache(); + ~DAVOptionsCache(); + + bool getDAVOptions( const OUString & rURL, DAVOptions & rDAVOptions ); + void removeDAVOptions( const OUString & rURL ); + void addDAVOptions( DAVOptions & rDAVOptions, const sal_uInt32 nLifeTime ); + + void setHeadAllowed( const OUString & rURL, bool HeadAllowed = true ); + + private: + + /// remove the last '/' in aUrl, if it exists + static void normalizeURLLastChar( OUString& aUrl ) { + if ( aUrl.getLength() > 1 && + ( ( aUrl.lastIndexOf( '/' ) + 1 ) == aUrl.getLength() ) ) + aUrl = aUrl.copy(0, aUrl.getLength() - 1 ); + }; + }; + + enum Depth { DAVZERO = 0, DAVONE = 1, DAVINFINITY = -1 }; + + enum ProppatchOperation { PROPSET = 0, PROPREMOVE = 1 }; + + struct ProppatchValue + { + ProppatchOperation operation; + OUString name; + css::uno::Any value; + + ProppatchValue( const ProppatchOperation o, + const OUString & n, + const css::uno::Any & v ) + : operation( o ), name( n ), value( v ) {} + }; +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DAVTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx b/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx new file mode 100644 index 000000000..88ca23f16 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DateTimeHelper.cxx @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <osl/time.h> +#include <com/sun/star/util/DateTime.hpp> +#include "DateTimeHelper.hxx" + +using namespace com::sun::star::util; + +using namespace webdav_ucp; + + +bool DateTimeHelper::ISO8601_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + int year, month, day, hours, minutes, off_hours, off_minutes, fix; + double seconds; + + // 2001-01-01T12:30:00Z + int n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lfZ", + &year, &month, &day, &hours, &minutes, &seconds ); + if ( n == 6 ) + { + fix = 0; + } + else + { + // 2001-01-01T12:30:00+03:30 + n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d", + &year, &month, &day, &hours, &minutes, &seconds, + &off_hours, &off_minutes ); + if ( n == 8 ) + { + fix = - off_hours * 3600 - off_minutes * 60; + } + else + { + // 2001-01-01T12:30:00-03:30 + n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d", + &year, &month, &day, &hours, &minutes, &seconds, + &off_hours, &off_minutes ); + if ( n == 8 ) + { + fix = off_hours * 3600 + off_minutes * 60; + } + else + { + return false; + } + } + } + + // Convert to local time... + + oslDateTime aDateTime; + aDateTime.NanoSeconds = 0; + aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds); // 0-59 + aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes); // 0-59 + aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours); // 0-23 + aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day); // 1-31 + aDateTime.DayOfWeek = 0; // 0-6, 0 = Sunday + aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month); // 1-12 + aDateTime.Year = sal::static_int_cast< sal_Int16 >(year); + + TimeValue aTimeValue; + if ( osl_getTimeValueFromDateTime( &aDateTime, &aTimeValue ) ) + { + aTimeValue.Seconds += fix; + + if ( osl_getLocalTimeFromSystemTime( &aTimeValue, &aTimeValue ) ) + { + if ( osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime ) ) + { + dateTime.Year = aDateTime.Year; + dateTime.Month = aDateTime.Month; + dateTime.Day = aDateTime.Day; + dateTime.Hours = aDateTime.Hours; + dateTime.Minutes = aDateTime.Minutes; + dateTime.Seconds = aDateTime.Seconds; + + return true; + } + } + } + + return false; +} + +sal_Int32 DateTimeHelper::convertMonthToInt (const OUString& month) +{ + if (month == "Jan") + return 1; + else if (month == "Feb") + return 2; + else if (month == "Mar") + return 3; + else if (month == "Apr") + return 4; + else if (month == "May") + return 5; + else if (month == "Jun") + return 6; + else if (month == "Jul") + return 7; + else if (month == "Aug") + return 8; + else if (month == "Sep") + return 9; + else if (month == "Oct") + return 10; + else if (month == "Nov") + return 11; + else if (month == "Dec") + return 12; + else + return 0; +} + +bool DateTimeHelper::RFC2068_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + int year; + int day; + int hours; + int minutes; + int seconds; + char string_month[3 + 1]; + char string_day[3 + 1]; + + sal_Int32 found = s.indexOf (','); + if (found != -1) + { + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + // RFC 1123 + found = sscanf (aDT.getStr(), "%3s, %2d %3s %4d %2d:%2d:%2d GMT", + string_day, &day, string_month, &year, &hours, &minutes, &seconds); + if (found != 7) + { + // RFC 1036 + found = sscanf (aDT.getStr(), "%3s, %2d-%3s-%2d %2d:%2d:%2d GMT", + string_day, &day, string_month, &year, &hours, &minutes, &seconds); + } + found = (found == 7) ? 1 : 0; + } + else + { + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + // ANSI C's asctime () format + found = sscanf (aDT.getStr(), "%3s %3s %d %2d:%2d:%2d %4d", + string_day, string_month, + &day, &hours, &minutes, &seconds, &year); + found = (found == 7) ? 1 : 0; + } + + if (found) + { + found = 0; + + int month = DateTimeHelper::convertMonthToInt ( + OUString::createFromAscii (string_month)); + if (month) + { + // Convert to local time... + + oslDateTime aDateTime; + aDateTime.NanoSeconds = 0; + aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds); + // 0-59 + aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes); + // 0-59 + aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours); + // 0-23 + aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day); + // 1-31 + aDateTime.DayOfWeek = 0; //dayofweek; // 0-6, 0 = Sunday + aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month); + // 1-12 + aDateTime.Year = sal::static_int_cast< sal_Int16 >(year); + + TimeValue aTimeValue; + if ( osl_getTimeValueFromDateTime( &aDateTime, + &aTimeValue ) ) + { + if ( osl_getLocalTimeFromSystemTime( &aTimeValue, + &aTimeValue ) ) + { + if ( osl_getDateTimeFromTimeValue( &aTimeValue, + &aDateTime ) ) + { + dateTime.Year = aDateTime.Year; + dateTime.Month = aDateTime.Month; + dateTime.Day = aDateTime.Day; + dateTime.Hours = aDateTime.Hours; + dateTime.Minutes = aDateTime.Minutes; + dateTime.Seconds = aDateTime.Seconds; + + found = 1; + } + } + } + } + } + + return found != 0; +} + +bool DateTimeHelper::convert (const OUString& s, DateTime& dateTime) +{ + if (ISO8601_To_DateTime (s, dateTime)) + return true; + else if (RFC2068_To_DateTime (s, dateTime)) + return true; + else + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/DateTimeHelper.hxx b/ucb/source/ucp/webdav-neon/DateTimeHelper.hxx new file mode 100644 index 000000000..5abf6d400 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/DateTimeHelper.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DATETIMEHELPER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_DATETIMEHELPER_HXX + +#include <config_lgpl.h> +#include <sal/types.h> +#include <rtl/ustring.hxx> + +namespace com::sun::star::util { + struct DateTime; +} + + +namespace webdav_ucp +{ + +class DateTimeHelper +{ +private: + static sal_Int32 convertMonthToInt (const OUString& ); + + static bool ISO8601_To_DateTime (const OUString&, + css::util::DateTime& ); + + static bool RFC2068_To_DateTime (const OUString&, + css::util::DateTime& ); + +public: + static bool convert (const OUString&, + css::util::DateTime& ); +}; + +} // namespace webdav_ucp + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LinkSequence.cxx b/ucb/source/ucp/webdav-neon/LinkSequence.cxx new file mode 100644 index 000000000..ea49352d9 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LinkSequence.cxx @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include <memory> + +#include "LinkSequence.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LinkSequenceParseContext +{ + std::unique_ptr<ucb::Link> pLink; + bool hasSource; + bool hasDestination; + + LinkSequenceParseContext() + : hasSource( false ), hasDestination( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_LINK (STATE_TOP) +#define STATE_DST (STATE_TOP + 1) +#define STATE_SRC (STATE_TOP + 2) + + +extern "C" { + +static int LinkSequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "link" ) == 0 ) + return STATE_LINK; + break; + + case STATE_LINK: + if ( strcmp( name, "dst" ) == 0 ) + return STATE_DST; + else if ( strcmp( name, "src" ) == 0 ) + return STATE_SRC; + break; + } + } + return NE_XML_DECLINE; +} + + +static int LinkSequence_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + LinkSequenceParseContext * pCtx + = static_cast< LinkSequenceParseContext * >( userdata ); + if ( !pCtx->pLink ) + pCtx->pLink.reset( new ucb::Link ); + + switch ( state ) + { + case STATE_DST: + pCtx->pLink->Destination + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasDestination = true; + break; + + case STATE_SRC: + pCtx->pLink->Source + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasSource = true; + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LinkSequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LinkSequenceParseContext * pCtx + = static_cast< LinkSequenceParseContext * >( userdata ); + if ( !pCtx->pLink ) + pCtx->pLink.reset( new ucb::Link ); + + switch ( state ) + { + case STATE_LINK: + if ( !pCtx->hasDestination || !pCtx->hasSource ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LinkSequence::createFromXML( const OString & rInData, + uno::Sequence< ucb::Link > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 7; // </link> + bool success = true; + + // rInData may contain multiple <link>...</link> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</link>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LinkSequenceParseContext aCtx; + ne_xml_push_handler( parser, + LinkSequence_startelement_callback, + LinkSequence_chardata_callback, + LinkSequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pLink ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 1 ); + + rOutData[ nCount - 1 ] = *aCtx.pLink; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</link>", nStart ); + } + + return success; +} + + +// static +bool LinkSequence::toXML( const uno::Sequence< ucb::Link > & rInData, + OUString & rOutData ) +{ + // <link><src>value</src><dst>value</dst></link><link><src>... + + for ( const auto& rLink : rInData ) + { + rOutData += "<link><src>"; + rOutData += rLink.Source; + rOutData += "</src><dst>"; + rOutData += rLink.Destination; + rOutData += "</dst></link>"; + } + return rInData.hasElements(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LinkSequence.hxx b/ucb/source/ucp/webdav-neon/LinkSequence.hxx new file mode 100644 index 000000000..8c88610dd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LinkSequence.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/Link.hpp> + +namespace webdav_ucp +{ + +class LinkSequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::Link > & rOutData ); + static bool toXML( const css::uno::Sequence< css::ucb::Link > & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LINKSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx b/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx new file mode 100644 index 000000000..61be4420c --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockEntrySequence.cxx @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include "LockEntrySequence.hxx" +#include <memory> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LockEntrySequenceParseContext +{ + std::unique_ptr<ucb::LockEntry> pEntry; + bool hasScope; + bool hasType; + + LockEntrySequenceParseContext() + : hasScope( false ), hasType( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_LOCKENTRY (STATE_TOP) +#define STATE_LOCKSCOPE (STATE_TOP + 1) +#define STATE_EXCLUSIVE (STATE_TOP + 2) +#define STATE_SHARED (STATE_TOP + 3) +#define STATE_LOCKTYPE (STATE_TOP + 4) +#define STATE_WRITE (STATE_TOP + 5) + + +extern "C" { + +static int LockEntrySequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "lockentry" ) == 0 ) + return STATE_LOCKENTRY; + break; + + case STATE_LOCKENTRY: + if ( strcmp( name, "lockscope" ) == 0 ) + return STATE_LOCKSCOPE; + else if ( strcmp( name, "locktype" ) == 0 ) + return STATE_LOCKTYPE; + +#define IIS_BUGS_WORKAROUND + +#ifdef IIS_BUGS_WORKAROUND + /* IIS (6) returns XML violating RFC 4918 + for DAV:supportedlock property value. + + <lockentry> + <write></write> + <shared></shared> + </lockentry> + <lockentry> + <write></write> + <exclusive></exclusive> + </lockentry> + + Bother... + */ + else if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + else if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; +#endif + break; + + case STATE_LOCKSCOPE: + if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + break; + + case STATE_LOCKTYPE: + if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; + break; + } + } + return NE_XML_DECLINE; +} + + +static int LockEntrySequence_chardata_callback( + void *, + int, + const char *, + size_t ) +{ + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LockEntrySequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LockEntrySequenceParseContext * pCtx + = static_cast< LockEntrySequenceParseContext * >( userdata ); + if ( !pCtx->pEntry ) + pCtx->pEntry.reset( new ucb::LockEntry ); + + switch ( state ) + { + case STATE_EXCLUSIVE: + pCtx->pEntry->Scope = ucb::LockScope_EXCLUSIVE; + pCtx->hasScope = true; + break; + + case STATE_SHARED: + pCtx->pEntry->Scope = ucb::LockScope_SHARED; + pCtx->hasScope = true; + break; + + case STATE_WRITE: + pCtx->pEntry->Type = ucb::LockType_WRITE; + pCtx->hasType = true; + break; + + case STATE_LOCKSCOPE: + if ( !pCtx->hasScope ) + return 1; // abort + break; + + case STATE_LOCKTYPE: + if ( !pCtx->hasType ) + return 1; // abort + break; + + case STATE_LOCKENTRY: + if ( !pCtx->hasType || !pCtx->hasScope ) + return 1; // abort + break; + + default: + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LockEntrySequence::createFromXML( const OString & rInData, + uno::Sequence< + ucb::LockEntry > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 12; // </lockentry> + bool success = true; + + // rInData may contain multiple <lockentry>...</lockentry> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</lockentry>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LockEntrySequenceParseContext aCtx; + ne_xml_push_handler( parser, + LockEntrySequence_startelement_callback, + LockEntrySequence_chardata_callback, + LockEntrySequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pEntry ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 2 ); + + rOutData[ nCount - 1 ] = *aCtx.pEntry; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</lockentry>", nStart ); + } + + rOutData.realloc( nCount ); + return success; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx b/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx new file mode 100644 index 000000000..87f173203 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockEntrySequence.hxx @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/LockEntry.hpp> + +namespace webdav_ucp +{ + +class LockEntrySequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::LockEntry > & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKENTRYSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockSequence.cxx b/ucb/source/ucp/webdav-neon/LockSequence.cxx new file mode 100644 index 000000000..b9399c60d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockSequence.cxx @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include "LockSequence.hxx" +#include <memory> +#include <sal/log.hxx> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct LockSequenceParseContext +{ + std::unique_ptr<ucb::Lock> pLock; + bool hasLockScope; + bool hasLockType; + bool hasDepth; + bool hasHREF; + bool hasTimeout; + + LockSequenceParseContext() + : hasLockScope( false ), hasLockType( false ), + hasDepth( false ), hasHREF( false ), hasTimeout( false ) {} +}; + +} + +#define STATE_TOP (1) + +#define STATE_ACTIVELOCK (STATE_TOP) +#define STATE_LOCKSCOPE (STATE_TOP + 1) +#define STATE_LOCKTYPE (STATE_TOP + 2) +#define STATE_DEPTH (STATE_TOP + 3) +#define STATE_OWNER (STATE_TOP + 4) +#define STATE_TIMEOUT (STATE_TOP + 5) +#define STATE_LOCKTOKEN (STATE_TOP + 6) +#define STATE_EXCLUSIVE (STATE_TOP + 7) +#define STATE_SHARED (STATE_TOP + 8) +#define STATE_WRITE (STATE_TOP + 9) +#define STATE_HREF (STATE_TOP + 10) + + +extern "C" { + +static int LockSequence_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "activelock" ) == 0 ) + return STATE_ACTIVELOCK; + break; + + case STATE_ACTIVELOCK: + if ( strcmp( name, "lockscope" ) == 0 ) + return STATE_LOCKSCOPE; + else if ( strcmp( name, "locktype" ) == 0 ) + return STATE_LOCKTYPE; + else if ( strcmp( name, "depth" ) == 0 ) + return STATE_DEPTH; + else if ( strcmp( name, "owner" ) == 0 ) + return STATE_OWNER; + else if ( strcmp( name, "timeout" ) == 0 ) + return STATE_TIMEOUT; + else if ( strcmp( name, "locktoken" ) == 0 ) + return STATE_LOCKTOKEN; + break; + + case STATE_LOCKSCOPE: + if ( strcmp( name, "exclusive" ) == 0 ) + return STATE_EXCLUSIVE; + else if ( strcmp( name, "shared" ) == 0 ) + return STATE_SHARED; + break; + + case STATE_LOCKTYPE: + if ( strcmp( name, "write" ) == 0 ) + return STATE_WRITE; + break; + + case STATE_LOCKTOKEN: + if ( strcmp( name, "href" ) == 0 ) + return STATE_HREF; + break; + + case STATE_OWNER: + // owner elem contains ANY. Accept anything; no state change. + return STATE_OWNER; + } + } + return NE_XML_DECLINE; +} + + +static int LockSequence_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + LockSequenceParseContext * pCtx + = static_cast< LockSequenceParseContext * >( userdata ); + if ( !pCtx->pLock ) + pCtx->pLock.reset( new ucb::Lock ); + + // Beehive sends XML values containing trailing newlines. + if ( buf[ len - 1 ] == 0x0a ) + len--; + + switch ( state ) + { + case STATE_DEPTH: + if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "0", 1 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_ZERO; + pCtx->hasDepth = true; + } + else if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "1", 1 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_ONE; + pCtx->hasDepth = true; + } + else if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "infinity", 8 ) == 0 ) + { + pCtx->pLock->Depth = ucb::LockDepth_INFINITY; + pCtx->hasDepth = true; + } + else + SAL_WARN( "ucb.ucp.webdav", "LockSequence_chardata_callback - Unknown depth!" ); + break; + + case STATE_OWNER: + { + // collect raw XML data... (owner contains ANY) + OUString aValue; + pCtx->pLock->Owner >>= aValue; + aValue += OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->pLock->Owner <<= aValue; + break; + } + + case STATE_TIMEOUT: + + // RFC2518, RFC2616: + + // TimeType = ("Second-" DAVTimeOutVal | "Infinite" | Other) + // DAVTimeOutVal = 1*digit + // Other = "Extend" field-value + // field-value = *( field-content | LWS ) + // field-content = <the OCTETs making up the field-value + // and consisting of either *TEXT or combinations + // of token, separators, and quoted-string> + // + // RFC4918, <http://tools.ietf.org/html/rfc4918#section-10.7> + // "The timeout value for TimeType "Second" MUST + // NOT be greater than 2^32-1." + + if ( rtl_str_compareIgnoreAsciiCase_WithLength( + buf, len, "Infinite", 8 ) == 0 ) + { + pCtx->pLock->Timeout = sal_Int64( -1 ); + pCtx->hasTimeout = true; + } + else if ( rtl_str_shortenedCompareIgnoreAsciiCase_WithLength( + buf, len, "Second-", 7, 7 ) == 0 ) + { + pCtx->pLock->Timeout + = OString( buf + 7, len - 7 ).toInt64(); + pCtx->hasTimeout = true; + } +// else if ( rtl_str_shortenedCompareIgnoreCase_WithLength( +// buf, len, "Extend", 6, 6 ) == 0 ) +// { +// @@@ +// } + else + { + pCtx->pLock->Timeout = sal_Int64( -1 ); + pCtx->hasTimeout = true; + SAL_WARN( "ucb.ucp.webdav", "LockSequence_chardata_callback - Unknown timeout!" ); + } + break; + + case STATE_HREF: + { + // collect hrefs. + sal_Int32 nPos = pCtx->pLock->LockTokens.getLength(); + pCtx->pLock->LockTokens.realloc( nPos + 1 ); + pCtx->pLock->LockTokens[ nPos ] + = OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + pCtx->hasHREF = true; + break; + } + + } + + return 0; // zero to continue, non-zero to abort parsing +} + + +static int LockSequence_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + LockSequenceParseContext * pCtx + = static_cast< LockSequenceParseContext * >( userdata ); + if ( !pCtx->pLock ) + pCtx->pLock.reset( new ucb::Lock ); + + switch ( state ) + { + case STATE_EXCLUSIVE: + pCtx->pLock->Scope = ucb::LockScope_EXCLUSIVE; + pCtx->hasLockScope = true; + break; + + case STATE_SHARED: + pCtx->pLock->Scope = ucb::LockScope_SHARED; + pCtx->hasLockScope = true; + break; + + case STATE_WRITE: + pCtx->pLock->Type = ucb::LockType_WRITE; + pCtx->hasLockType = true; + break; + + case STATE_DEPTH: + if ( !pCtx->hasDepth ) + return 1; // abort + break; + + case STATE_HREF: + if ( !pCtx->hasHREF ) + return 1; // abort + break; + + case STATE_TIMEOUT: + if ( !pCtx->hasTimeout ) + return 1; // abort + break; + + case STATE_LOCKSCOPE: + if ( !pCtx->hasLockScope ) + return 1; // abort + break; + + case STATE_LOCKTYPE: + if ( !pCtx->hasLockType ) + return 1; // abort + break; + + case STATE_ACTIVELOCK: + if ( !pCtx->hasLockType || !pCtx->hasDepth ) + return 1; // abort + break; + + default: + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +// static +bool LockSequence::createFromXML( const OString & rInData, + uno::Sequence< ucb::Lock > & rOutData ) +{ + const sal_Int32 TOKEN_LENGTH = 13; // </activelock> + bool success = true; + + // rInData may contain multiple <activelock>...</activelock> tags. + sal_Int32 nCount = 0; + sal_Int32 nStart = 0; + sal_Int32 nEnd = rInData.indexOf( "</activelock>" ); + while ( nEnd > -1 ) + { + ne_xml_parser * parser = ne_xml_create(); + if ( !parser ) + { + success = false; + break; + } + + LockSequenceParseContext aCtx; + ne_xml_push_handler( parser, + LockSequence_startelement_callback, + LockSequence_chardata_callback, + LockSequence_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, + rInData.getStr() + nStart, + nEnd - nStart + TOKEN_LENGTH ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( !success ) + break; + + if ( aCtx.pLock ) + { + nCount++; + if ( nCount > rOutData.getLength() ) + rOutData.realloc( rOutData.getLength() + 1 ); + + rOutData[ nCount - 1 ] = *aCtx.pLock; + } + + nStart = nEnd + TOKEN_LENGTH; + nEnd = rInData.indexOf( "</activelock>", nStart ); + } + + return success; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/LockSequence.hxx b/ucb/source/ucp/webdav-neon/LockSequence.hxx new file mode 100644 index 000000000..a35ddee99 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/LockSequence.hxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX + +#include <config_lgpl.h> +#include <rtl/string.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/ucb/Lock.hpp> + +namespace webdav_ucp +{ + +class LockSequence +{ +public: + static bool createFromXML( const OString & rInData, + css::uno::Sequence< css::ucb::Lock > & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_LOCKSEQUENCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx b/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx new file mode 100644 index 000000000..68f24f440 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonHeadRequest.cxx @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include "NeonHeadRequest.hxx" +#include "NeonSession.hxx" + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +void process_headers( ne_request * req, + DAVResource & rResource, + const std::vector< OUString > & rHeaderNames ) +{ + void * cursor = nullptr; + const char * name, *value; + +#if defined SAL_LOG_INFO + { + for ( const auto& rHeader : rHeaderNames ) + { + SAL_INFO( "ucb.ucp.webdav", "HEAD - requested header: " << rHeader ); + } + } +#endif + while ( ( cursor = ne_response_header_iterate( req, cursor, + &name, &value ) ) != nullptr ) { + // The HTTP header `field-name` must be a `token`, which can only contain a subset of ASCII; + // assume that Neon will already have rejected any invalid data, so that it is guaranteed + // that `name` is ASCII-only: + OUString aHeaderName( OUString::createFromAscii( name ) ); + // The HTTP header `field-value` may contain obsolete (as per RFC 7230) `obs-text` non-ASCII + // %x80-FF octets, lets preserve them as individual characters in `aHeaderValue` by treating + // `value` as ISO 8859-1: + OUString aHeaderValue(value, strlen(value), RTL_TEXTENCODING_ISO_8859_1); + + SAL_INFO( "ucb.ucp.webdav", "HEAD - received header: " << aHeaderName << ":" << aHeaderValue); + + // Note: Empty vector means that all headers are requested. + bool bIncludeIt = rHeaderNames.empty(); + + if ( !bIncludeIt ) + { + // Check whether this header was requested. + auto it = std::find_if(rHeaderNames.begin(), rHeaderNames.end(), + [&aHeaderName](const OUString& rName) { + // header names are case insensitive + return rName.equalsIgnoreAsciiCase( aHeaderName ); + }); + + if ( it != rHeaderNames.end() ) + { + aHeaderName = *it; + bIncludeIt = true; + } + } + + if ( bIncludeIt ) + { + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + // header names are case insensitive, so are the + // corresponding property names + thePropertyValue.Name = aHeaderName.toAsciiLowerCase(); + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Value <<= aHeaderValue; + + // Add the newly created PropertyValue + rResource.properties.push_back( thePropertyValue ); + } + } +} + +} // namespace + +NeonHeadRequest::NeonHeadRequest( HttpSession * inSession, + const OUString & inPath, + const std::vector< OUString > & + inHeaderNames, + DAVResource & ioResource, + int & nError ) +{ + ioResource.uri = inPath; + ioResource.properties.clear(); + + // Create and dispatch HEAD request. Install catcher for all response + // header fields. + ne_request * req = ne_request_create( inSession, + "HEAD", + OUStringToOString( + inPath, + RTL_TEXTENCODING_UTF8 ).getStr() ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_request_dispatch( req ); + } + + process_headers( req, ioResource, inHeaderNames ); + + if ( nError == NE_OK && ne_get_status( req )->klass != 2 ) + nError = NE_ERROR; + + ne_request_destroy( req ); +} + +NeonHeadRequest::~NeonHeadRequest() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx b/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx new file mode 100644 index 000000000..06a28af03 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonHeadRequest.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX + +#include <config_lgpl.h> +#include <vector> +#include "NeonTypes.hxx" +#include "DAVResource.hxx" + +namespace webdav_ucp +{ + +class NeonHeadRequest +{ +public: + // named / allprop + NeonHeadRequest( HttpSession* inSession, + const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + int & nError ); + ~NeonHeadRequest(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONHEADREQUEST_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonInputStream.cxx b/ucb/source/ucp/webdav-neon/NeonInputStream.cxx new file mode 100644 index 000000000..848dc2b81 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonInputStream.cxx @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "NeonInputStream.hxx" + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <cppuhelper/queryinterface.hxx> + +#include <string.h> + +using namespace cppu; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace webdav_ucp; + +NeonInputStream::NeonInputStream() +: mLen( 0 ), + mPos( 0 ) +{ +} + +NeonInputStream::~NeonInputStream() +{ +} + +// Allows the caller to add some data to the "end" of the stream +void NeonInputStream::AddToStream( const char * inBuf, sal_Int32 inLen ) +{ + mInputBuffer.realloc( sal::static_int_cast<sal_Int32>(mLen) + inLen ); + memcpy( mInputBuffer.getArray() + mLen, inBuf, inLen ); + mLen += inLen; +} + +Any NeonInputStream::queryInterface( const Type &type ) +{ + Any aRet = ::cppu::queryInterface( type, + static_cast< XInputStream * >( this ), + static_cast< XSeekable * >( this ) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( type ); +} + +// "Reads" the specified number of bytes from the stream +sal_Int32 SAL_CALL NeonInputStream::readBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + // Work out how much we're actually going to write + sal_Int32 theBytes2Read = nBytesToRead; + sal_Int32 theBytesLeft = sal::static_int_cast<sal_Int32>(mLen - mPos); + if ( theBytes2Read > theBytesLeft ) + theBytes2Read = theBytesLeft; + + // Realloc buffer. + aData.realloc( theBytes2Read ); + + // Write the data + memcpy( + aData.getArray(), mInputBuffer.getConstArray() + mPos, theBytes2Read ); + + // Update our stream position for next time + mPos += theBytes2Read; + + return theBytes2Read; +} + +sal_Int32 SAL_CALL NeonInputStream::readSomeBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + // Warning: What should this be doing ? + return readBytes( aData, nMaxBytesToRead ); +} + +// Moves the current stream position forward +void SAL_CALL NeonInputStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + mPos += nBytesToSkip; + if ( mPos >= mLen ) + mPos = mLen; +} + +// Returns the number of unread bytes currently remaining on the stream +sal_Int32 SAL_CALL NeonInputStream::available( ) +{ + return std::min<sal_Int64>(SAL_MAX_INT32, mLen - mPos); +} + +void SAL_CALL NeonInputStream::closeInput() +{ +} + +void SAL_CALL NeonInputStream::seek( sal_Int64 location ) +{ + if ( location < 0 ) + throw css::lang::IllegalArgumentException(); + + if ( location > mLen ) + throw css::lang::IllegalArgumentException(); + + mPos = location; +} + +sal_Int64 SAL_CALL NeonInputStream::getPosition() +{ + return mPos; +} + +sal_Int64 SAL_CALL NeonInputStream::getLength() +{ + return mLen; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonInputStream.hxx b/ucb/source/ucp/webdav-neon/NeonInputStream.hxx new file mode 100644 index 000000000..e496d72bd --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonInputStream.hxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX + +#include <config_lgpl.h> +#include <sal/types.h> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> + + +namespace webdav_ucp +{ + +// A simple XInputStream implementation provided specifically for use +// by the DAVSession::GET method. +class NeonInputStream : public css::io::XInputStream, + public css::io::XSeekable, + public ::cppu::OWeakObject +{ + private: + css::uno::Sequence< sal_Int8 > mInputBuffer; + sal_Int64 mLen; + sal_Int64 mPos; + + public: + NeonInputStream(); + virtual ~NeonInputStream() override; + + // Add some data to the end of the stream + void AddToStream( const char * inBuf, sal_Int32 inLen ); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & type ) override; + + virtual void SAL_CALL acquire() + throw () override + { OWeakObject::acquire(); } + + virtual void SAL_CALL release() + throw() override + { OWeakObject::release(); } + + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( + css::uno::Sequence< sal_Int8 > & aData, + sal_Int32 nBytesToRead ) override; + + virtual sal_Int32 SAL_CALL readSomeBytes( + css::uno::Sequence< sal_Int8 > & aData, + sal_Int32 nMaxBytesToRead ) override; + + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + + virtual sal_Int32 SAL_CALL available() override; + + virtual void SAL_CALL closeInput() override; + + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + + virtual sal_Int64 SAL_CALL getPosition() override; + + virtual sal_Int64 SAL_CALL getLength() override; +}; + +} // namespace webdav_ucp +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONINPUTSTREAM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonLockStore.cxx b/ucb/source/ucp/webdav-neon/NeonLockStore.cxx new file mode 100644 index 000000000..a4d25b4b1 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonLockStore.cxx @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <ne_uri.h> +#include <rtl/ustring.hxx> +#include <osl/time.h> +#include <osl/thread.hxx> +#include <sal/log.hxx> +#include <salhelper/thread.hxx> +#include "NeonSession.hxx" +#include "NeonLockStore.hxx" + +using namespace webdav_ucp; + +namespace webdav_ucp { + +class TickerThread : public salhelper::Thread +{ + bool m_bFinish; + NeonLockStore & m_rLockStore; + +public: + + explicit TickerThread( NeonLockStore & rLockStore ) + : Thread( "NeonTickerThread" ), m_bFinish( false ) + , m_rLockStore( rLockStore ) + { + } + + void finish() { m_bFinish = true; } + +private: + + virtual void execute() override; +}; + +} // namespace webdav_ucp + +void TickerThread::execute() +{ + SAL_INFO( "ucb.ucp.webdav", "TickerThread: start." ); + + // we have to go through the loop more often to be able to finish ~quickly + const int nNth = 25; + + int nCount = nNth; + while ( !m_bFinish ) + { + if (--nCount < 0) + { + m_rLockStore.refreshLocks(); + nCount = nNth; + } + + salhelper::Thread::wait(TimeValue(0, 1000000000 / nNth)); + } + + SAL_INFO( "ucb.ucp.webdav", "TickerThread: stop." ); +} + +NeonLockStore::NeonLockStore() + : m_pNeonLockStore( ne_lockstore_create() ) +{ + /* + * ne_lockstore_create() never returns a NULL; neon calls abort() in case of an out-of-memory + * situation. + * Please see: + * <http://www.webdav.org/neon/doc/html/refneon.html> + * topic title "Memory handling", copied here verbatim: + * + * "neon does not attempt to cope gracefully with an out-of-memory situation; + * instead, by default, the abort function is called to immediately terminate + * the process. An application may register a custom function which will be + * called before abort in such a situation; see ne_oom_callback." + */ +} + +NeonLockStore::~NeonLockStore() +{ + { + osl::ClearableMutexGuard aGuard(m_aMutex); + stopTicker(aGuard); + } // actually no threads should even try to access members now + + // release active locks, if any. + SAL_WARN_IF( !m_aLockInfoMap.empty(), "ucb.ucp.webdav", "NeonLockStore::~NeonLockStore - Releasing active locks!" ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + NeonLock * pLock = rLockInfo.first; + rLockInfo.second.xSession->UNLOCK( pLock ); + + ne_lockstore_remove( m_pNeonLockStore, pLock ); + ne_lock_destroy( pLock ); + } + + ne_lockstore_destroy( m_pNeonLockStore ); +} + +void NeonLockStore::startTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_pTickerThread.is() ) + { + m_pTickerThread = new TickerThread( *this ); + m_pTickerThread->launch(); + } +} + +void NeonLockStore::stopTicker(osl::ClearableMutexGuard & rGuard) +{ + rtl::Reference<TickerThread> pTickerThread; + + if (m_pTickerThread.is()) + { + m_pTickerThread->finish(); // needs mutex + // the TickerThread may run refreshLocks() at most once after this + pTickerThread = m_pTickerThread; + m_pTickerThread.clear(); + } + + rGuard.clear(); + + if (pTickerThread.is() && pTickerThread->getIdentifier() != osl::Thread::getCurrentIdentifier()) + pTickerThread->join(); // without m_aMutex locked (to prevent deadlock) +} + +void NeonLockStore::registerSession( HttpSession * pHttpSession ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_lockstore_register( m_pNeonLockStore, pHttpSession ); +} + +NeonLock * NeonLockStore::findByUri( OUString const & rUri ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_uri aUri; + ne_uri_parse( OUStringToOString( + rUri, RTL_TEXTENCODING_UTF8 ).getStr(), &aUri ); + return ne_lockstore_findbyuri( m_pNeonLockStore, &aUri ); +} + +void NeonLockStore::addLock( NeonLock * pLock, + rtl::Reference< NeonSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + ne_lockstore_add( m_pNeonLockStore, pLock ); + m_aLockInfoMap[ pLock ] + = LockInfo( xSession, nLastChanceToSendRefreshRequest ); + + startTicker(); +} + +void NeonLockStore::removeLock( NeonLock * pLock ) +{ + osl::ClearableMutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap.erase( pLock ); + ne_lockstore_remove( m_pNeonLockStore, pLock ); + + if ( m_aLockInfoMap.empty() ) + stopTicker(aGuard); +} + +void NeonLockStore::removeLockDeferred(NeonLock* pLock) +{ + osl::MutexGuard aGuard(m_aMutex); + + m_aRemoveDeferred.push_back(pLock); +} + +void NeonLockStore::refreshLocks() +{ + osl::MutexGuard aGuard( m_aMutex ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + LockInfo & rInfo = rLockInfo.second; + if ( rInfo.nLastChanceToSendRefreshRequest != -1 ) + { + // 30 seconds or less remaining until lock expires? + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( rInfo.nLastChanceToSendRefreshRequest - 30 + <= sal_Int32( t1.Seconds ) ) + { + // refresh the lock. + sal_Int32 nlastChanceToSendRefreshRequest = -1; + if ( rInfo.xSession->LOCK( + rLockInfo.first, + /* out param */ nlastChanceToSendRefreshRequest ) ) + { + rInfo.nLastChanceToSendRefreshRequest + = nlastChanceToSendRefreshRequest; + } + else + { + // refresh failed. stop auto-refresh. + rInfo.nLastChanceToSendRefreshRequest = -1; + } + } + } + } + // removeLock will not need to actually release the lock, because this is run from TickerThread + for (auto pLock : m_aRemoveDeferred) + removeLock(pLock); + m_aRemoveDeferred.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonLockStore.hxx b/ucb/source/ucp/webdav-neon/NeonLockStore.hxx new file mode 100644 index 000000000..fa64cb320 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonLockStore.hxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX + +#include <config_lgpl.h> +#include <map> + +#include <ne_locks.h> + +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include "NeonTypes.hxx" + +namespace webdav_ucp +{ + +class TickerThread; +class NeonSession; + +struct LockInfo +{ + rtl::Reference< NeonSession > xSession; + sal_Int32 nLastChanceToSendRefreshRequest; + + LockInfo() + : nLastChanceToSendRefreshRequest( -1 ) {} + + LockInfo( rtl::Reference< NeonSession > const & _xSession, + sal_Int32 _nLastChanceToSendRefreshRequest ) + : xSession( _xSession ), + nLastChanceToSendRefreshRequest( _nLastChanceToSendRefreshRequest ) {} + +}; + +typedef std::map< NeonLock *, LockInfo > LockInfoMap; + +class NeonLockStore +{ + osl::Mutex m_aMutex; + ne_lock_store * m_pNeonLockStore; + rtl::Reference< TickerThread > m_pTickerThread; + LockInfoMap m_aLockInfoMap; + std::vector<NeonLock*> m_aRemoveDeferred; + +public: + NeonLockStore(); + ~NeonLockStore(); + + void registerSession( HttpSession * pHttpSession ); + + NeonLock * findByUri( OUString const & rUri ); + + void addLock( NeonLock * pLock, + rtl::Reference< NeonSession > const & xSession, + // time in seconds since Jan 1 1970 + // -1: infinite lock, no refresh + sal_Int32 nLastChanceToSendRefreshRequest ); + + void removeLock( NeonLock * pLock ); + void removeLockDeferred(NeonLock* pLock); + + void refreshLocks(); + +private: + void startTicker(); + void stopTicker(osl::ClearableMutexGuard & rGuard); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONLOCKSTORE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx new file mode 100644 index 000000000..cf03274c5 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.cxx @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <osl/mutex.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include "NeonSession.hxx" +#include "NeonTypes.hxx" +#include "DAVProperties.hxx" +#include "NeonPropFindRequest.hxx" +#include "LinkSequence.hxx" +#include "LockSequence.hxx" +#include "LockEntrySequence.hxx" +#include "UCBDeadPropertyValue.hxx" +#include <memory> + +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace std; +using namespace webdav_ucp; + + +namespace +{ + // strip "DAV:" namespace from XML snippets to avoid + // parser error (undeclared namespace) later on. + OString stripDavNamespace( const OString & in ) + { + const OString inXML( in.toAsciiLowerCase() ); + + OStringBuffer buf; + sal_Int32 start = 0; + sal_Int32 end = inXML.indexOf( "dav:" ); + while ( end != -1 ) + { + if ( inXML[ end - 1 ] == '<' || + inXML[ end - 1 ] == '/' ) + { + // copy from original buffer - preserve case. + buf.append( in.copy( start, end - start ) ); + } + else + { + // copy from original buffer - preserve case. + buf.append( in.copy( start, end - start + 4 ) ); + } + start = end + 4; + end = inXML.indexOf( "dav:", start ); + } + buf.append( inXML.copy( start ) ); + + return buf.makeStringAndClear(); + } +} + +extern "C" { + +static int NPFR_propfind_iter( void* userdata, + const NeonPropName* pname, + const char* value, + const HttpStatus* status ) +{ + /* + HTTP Response Status Classes: + + - 1: Informational - Request received, continuing process + + - 2: Success - The action was successfully received, + understood, and accepted + + - 3: Redirection - Further action must be taken in order to + complete the request + + - 4: Client Error - The request contains bad syntax or cannot + be fulfilled + + - 5: Server Error - The server failed to fulfill an apparently + valid request + */ + + if ( status->klass > 2 ) + return 0; // Error getting this property. Go on. + + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = true; + + SAL_WARN_IF( !pname->nspace, "ucb.ucp.webdav", "NPFR_propfind_iter - No XML namespace!" ); + + DAVProperties::createUCBPropName( pname->nspace, + pname->name, + thePropertyValue.Name ); + bool bHasValue = false; + if ( DAVProperties::isUCBDeadProperty( *pname ) ) + { + // DAV dead property added by WebDAV UCP? + if ( UCBDeadPropertyValue::createFromXML( + value, thePropertyValue.Value ) ) + { + SAL_WARN_IF( !thePropertyValue.Value.hasValue(), + "ucb.ucp.webdav", "NPFR_propfind_iter - No value for UCBDeadProperty!" ); + bHasValue = true; + } + } + + if ( !bHasValue ) + { + if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "resourcetype" ) == 0 ) + { + OString aValue( value ); + aValue = aValue.trim(); // #107358# remove leading/trailing spaces + if ( !aValue.isEmpty() ) + { + aValue = stripDavNamespace( aValue ).toAsciiLowerCase(); + if ( aValue.startsWith("<collection") ) + { + thePropertyValue.Value + <<= OUString("collection"); + } + } + + if ( !thePropertyValue.Value.hasValue() ) + { + // Take over the value exactly as supplied by the server. + thePropertyValue.Value <<= OUString::createFromAscii( value ); + } + } + else if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "supportedlock" ) == 0 ) + { + Sequence< LockEntry > aEntries; + LockEntrySequence::createFromXML( + stripDavNamespace( value ), aEntries ); + thePropertyValue.Value <<= aEntries; + } + else if ( rtl_str_compareIgnoreAsciiCase( + pname->name, "lockdiscovery" ) == 0 ) + { + Sequence< Lock > aLocks; + LockSequence::createFromXML( + stripDavNamespace( value ), aLocks ); + thePropertyValue.Value <<= aLocks; + } + else if ( rtl_str_compareIgnoreAsciiCase( pname->name, "source" ) == 0 ) + { + Sequence< Link > aLinks; + LinkSequence::createFromXML( + stripDavNamespace( value ), aLinks ); + thePropertyValue.Value <<= aLinks; + } + else + { + thePropertyValue.Value + <<= OStringToOUString( value, RTL_TEXTENCODING_UTF8 ); + } + } + + // Add the newly created PropertyValue + DAVResource* theResource = static_cast< DAVResource * >( userdata ); + theResource->properties.push_back( thePropertyValue ); + + return 0; // Go on. +} + +static void NPFR_propfind_results( void* userdata, + const ne_uri* uri, + const NeonPropFindResultSet* set ) +{ + // @@@ href is not the uri! DAVResource ctor wants uri! + + DAVResource theResource( + OStringToOUString( uri->path, RTL_TEXTENCODING_UTF8 ) ); + + ne_propset_iterate( set, NPFR_propfind_iter, &theResource ); + + // Add entry to resources list. + vector< DAVResource > * theResources + = static_cast< vector< DAVResource > * >( userdata ); + theResources->push_back( theResource ); +} + +static int NPFR_propnames_iter( void* userdata, + const NeonPropName* pname, + const char* /*value*/, + const HttpStatus* /*status*/ ) +{ + OUString aFullName; + DAVProperties::createUCBPropName( pname->nspace, + pname->name, + aFullName ); + + DAVResourceInfo* theResource = static_cast< DAVResourceInfo * >( userdata ); + theResource->properties.push_back( aFullName ); + return 0; +} + +static void NPFR_propnames_results( void* userdata, + const ne_uri* /*uri*/, + const NeonPropFindResultSet* results ) +{ + // @@@ href is not the uri! DAVResourceInfo ctor wants uri! + // Create entry for the resource. + DAVResourceInfo theResource; + + // Fill entry. + ne_propset_iterate( results, NPFR_propnames_iter, &theResource ); + + // Add entry to resources list. + vector< DAVResourceInfo > * theResources + = static_cast< vector< DAVResourceInfo > * >( userdata ); + theResources->push_back( theResource ); +} + +} + +NeonPropFindRequest::NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + const vector< OUString >& inPropNames, + vector< DAVResource >& ioResources, + int & nError ) +{ + // Generate the list of properties we're looking for + int thePropCount = inPropNames.size(); + if ( thePropCount > 0 ) + { + std::unique_ptr<NeonPropName[]> thePropNames(new NeonPropName[ thePropCount + 1 ]); + int theIndex; + + for ( theIndex = 0; theIndex < thePropCount; theIndex ++ ) + { + // Split fullname into namespace and name! + DAVProperties::createNeonPropName( + inPropNames[ theIndex ], thePropNames[ theIndex ] ); + } + thePropNames[ theIndex ].nspace = nullptr; + thePropNames[ theIndex ].name = nullptr; + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_simple_propfind( inSession, + inPath, + inDepth, + thePropNames.get(), + NPFR_propfind_results, + &ioResources ); + } + + for ( theIndex = 0; theIndex < thePropCount; theIndex ++ ) + free( const_cast<char *>(thePropNames[ theIndex ].name) ); + } + else + { + // ALLPROP + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_simple_propfind( inSession, + inPath, + inDepth, + nullptr, // 0 == allprop + NPFR_propfind_results, + &ioResources ); + } + + // #87585# - Sometimes neon lies (because some servers lie). + if ( ( nError == NE_OK ) && ioResources.empty() ) + nError = NE_ERROR; +} + +NeonPropFindRequest::NeonPropFindRequest( + HttpSession* inSession, + const char* inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + int & nError ) +{ + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + nError = ne_propnames( inSession, + inPath, + inDepth, + NPFR_propnames_results, + &ioResInfo ); + } + + // #87585# - Sometimes neon lies (because some servers lie). + if ( ( nError == NE_OK ) && ioResInfo.empty() ) + nError = NE_ERROR; +} + +NeonPropFindRequest::~NeonPropFindRequest( ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx new file mode 100644 index 000000000..b435a80ba --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonPropFindRequest.hxx @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX + +#include <config_lgpl.h> +#include <vector> +#include <rtl/ustring.hxx> +#include "NeonTypes.hxx" +#include "DAVTypes.hxx" +#include "DAVResource.hxx" + +namespace webdav_ucp +{ + +class NeonPropFindRequest +{ +public: + // named / allprop + NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + int & nError ); + // propnames + NeonPropFindRequest( HttpSession* inSession, + const char* inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + int & nError ); + + ~NeonPropFindRequest(); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONPROPFINDREQUEST_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonSession.cxx b/ucb/source/ucp/webdav-neon/NeonSession.cxx new file mode 100644 index 000000000..7fac08345 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonSession.cxx @@ -0,0 +1,2361 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +#include <unordered_map> +#include <vector> +#include <string.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/time.h> +#include <ne_socket.h> +#include <ne_auth.h> +#include <ne_redirect.h> +#include <ne_ssl.h> + +// old neon versions forgot to set this +extern "C" { +#include <ne_compress.h> +} + +#include <libxml/parser.h> +#include <comphelper/sequence.hxx> +#include <ucbhelper/simplecertificatevalidationrequest.hxx> + +#include "DAVAuthListener.hxx" +#include "NeonTypes.hxx" +#include "NeonSession.hxx" +#include "NeonInputStream.hxx" +#include "NeonPropFindRequest.hxx" +#include "NeonHeadRequest.hxx" +#include "NeonUri.hxx" +#include "LinkSequence.hxx" +#include "UCBDeadPropertyValue.hxx" + +#include <officecfg/Inet.hxx> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/security/CertificateValidity.hpp> +#include <com/sun/star/security/CertificateContainerStatus.hpp> +#include <com/sun/star/security/CertificateContainer.hpp> +#include <com/sun/star/security/XCertificateContainer.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/xml/crypto/SEInitializer.hpp> + + +using namespace com::sun::star; +using namespace webdav_ucp; + +#ifndef EOL +# define EOL "\r\n" +#endif + +namespace { + +struct RequestData +{ + // POST + OUString aContentType; + OUString aReferer; + + RequestData() {} + RequestData( const OUString & rContentType, + const OUString & rReferer ) + : aContentType( rContentType ), aReferer( rReferer ) {} +}; + +struct equalPtr +{ + bool operator()( const ne_request* p1, const ne_request* p2 ) const + { + return p1 == p2; + } +}; + +struct hashPtr +{ + size_t operator()( const ne_request* p ) const + { + return reinterpret_cast<size_t>(p); + } +}; + +} + +typedef std::unordered_map +< + ne_request*, + RequestData, + hashPtr, + equalPtr +> +RequestDataMap; + +static sal_uInt16 makeStatusCode( const OUString & rStatusText ) +{ + // Extract status code from session error string. Unfortunately + // neon provides no direct access to the status code... + + if ( rStatusText.getLength() < 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "makeStatusCode - status text string to short!" ); + return 0; + } + + sal_Int32 nPos = rStatusText.indexOf( ' ' ); + if ( nPos == -1 ) + { + SAL_WARN( "ucb.ucp.webdav", "makeStatusCode - wrong status text format!" ); + return 0; + } + + return sal_uInt16( rStatusText.copy( 0, nPos ).toInt32() ); +} + +static bool noKeepAlive( const uno::Sequence< beans::NamedValue >& rFlags ) +{ + if ( !rFlags.hasElements() ) + return false; + + // find "KeepAlive" property + const beans::NamedValue* pValue( + std::find_if(rFlags.begin(), rFlags.end(), + [] (beans::NamedValue const& rNV) { return rNV.Name == "KeepAlive"; } )); + return pValue != rFlags.end() && !pValue->Value.get<bool>(); +} + +namespace { + +struct NeonRequestContext +{ + uno::Reference< io::XOutputStream > xOutputStream; + rtl::Reference< NeonInputStream > xInputStream; + const std::vector< OUString > * pHeaderNames; + DAVResource * pResource; + + explicit NeonRequestContext( uno::Reference< io::XOutputStream > const & xOutStrm ) + : xOutputStream( xOutStrm ), + pHeaderNames( nullptr ), pResource( nullptr ) {} + + explicit NeonRequestContext( const rtl::Reference< NeonInputStream > & xInStrm ) + : xInputStream( xInStrm ), + pHeaderNames( nullptr ), pResource( nullptr ) {} + + NeonRequestContext( uno::Reference< io::XOutputStream > const & xOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : xOutputStream( xOutStrm ), + pHeaderNames( &inHeaderNames ), pResource( &ioResource ) {} + + NeonRequestContext( const rtl::Reference< NeonInputStream > & xInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : xInputStream( xInStrm ), + pHeaderNames( &inHeaderNames ), pResource( &ioResource ) {} + + void ResponseBlockReader(const char * inBuf, size_t inLen) + { + if (xInputStream.is()) + xInputStream->AddToStream( inBuf, inLen ); + } + + void ResponseBlockWriter(const char * inBuf, size_t inLen) + { + if (xOutputStream.is()) + { + const uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(inBuf), inLen ); + xOutputStream->writeBytes( aSeq ); + } + } + +}; + +} + +// A simple Neon response_block_reader for use with an XInputStream +extern "C" { + +static int NeonSession_ResponseBlockReader(void * inUserData, + const char * inBuf, + size_t inLen ) +{ + // neon sometimes calls this function with (inLen == 0)... + if ( inLen > 0 ) + { + NeonRequestContext * pCtx = static_cast<NeonRequestContext*>(inUserData); + pCtx->ResponseBlockReader(inBuf, inLen); + } + return 0; +} + +// A simple Neon response_block_reader for use with an XOutputStream +static int NeonSession_ResponseBlockWriter( void * inUserData, + const char * inBuf, + size_t inLen ) +{ + // neon calls this function with (inLen == 0)... + if ( inLen > 0 ) + { + NeonRequestContext * pCtx = static_cast<NeonRequestContext*>(inUserData); + pCtx->ResponseBlockWriter(inBuf, inLen); + } + return 0; +} + +static int NeonSession_NeonAuth( void * inUserData, +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + const char * inAuthProtocol, +#endif + const char * inRealm, + int attempt, + char * inoutUserName, + char * inoutPassWord ) +{ +/* The callback used to request the username and password in the given + * realm. The username and password must be copied into the buffers + * which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero + * on the first call to the callback, and increases by one each time + * an attempt to authenticate fails. + * + * The callback must return zero to indicate that authentication + * should be attempted with the username/password, or non-zero to + * cancel the request. (if non-zero, username and password are + * ignored.) */ + + NeonSession * theSession = static_cast<NeonSession*>(inUserData); + const char * pAuthProtocol; +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + pAuthProtocol = inAuthProtocol; +#else + pAuthProtocol = nullptr; +#endif + return theSession->NeonAuth(pAuthProtocol, inRealm, attempt, inoutUserName, inoutPassWord); +} + +} + +int NeonSession::NeonAuth(const char* inAuthProtocol, const char* inRealm, + int attempt, char* inoutUserName, char * inoutPassWord) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +/* The callback used to request the username and password in the given + * realm. The username and password must be copied into the buffers + * which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero + * on the first call to the callback, and increases by one each time + * an attempt to authenticate fails. + * + * The callback must return zero to indicate that authentication + * should be attempted with the username/password, or non-zero to + * cancel the request. (if non-zero, username and password are + * ignored.) */ + + DAVAuthListener * pListener + = getRequestEnvironment().m_xAuthListener.get(); + if ( !pListener ) + { + // abort + return -1; + } + OUString theUserName; + OUString thePassWord; + + if ( attempt == 0 ) + { + // neon does not handle username supplied with request URI (for + // instance when doing FTP over proxy - last checked: 0.23.5 ) + + try + { + NeonUri uri( getRequestEnvironment().m_aRequestURI ); + const OUString& aUserInfo( uri.GetUserInfo() ); + if ( !aUserInfo.isEmpty() ) + { + sal_Int32 nPos = aUserInfo.indexOf( '@' ); + if ( nPos == -1 ) + { + theUserName = aUserInfo; + } + else + { + theUserName = aUserInfo.copy( 0, nPos ); + thePassWord = aUserInfo.copy( nPos + 1 ); + } + } + } + catch ( DAVException const & ) + { + // abort + return -1; + } + } + else + { + // username buffer is prefilled with user name from last attempt. + theUserName = OUString::createFromAscii( inoutUserName ); + // @@@ Neon does not initialize password buffer (last checked: 0.22.0). + //thePassWord = OUString::createFromAscii( inoutPassWord ); + } + +#if defined NE_FEATURE_SSPI && ! defined SYSTEM_NEON + const bool bCanUseSystemCreds + = (attempt == 0) && // avoid endless loops + ne_has_support( NE_FEATURE_SSPI ) && // Windows-only feature. + ( ( ne_strcasecmp( inAuthProtocol, "NTLM" ) == 0 ) || + ( ne_strcasecmp( inAuthProtocol, "Negotiate" ) == 0 ) ); +#else + (void)inAuthProtocol; + const bool bCanUseSystemCreds = false; +#endif + + int theRetVal = pListener->authenticate( + OUString::createFromAscii( inRealm ), + getHostName(), + theUserName, + thePassWord, + bCanUseSystemCreds); + + OString aUser( OUStringToOString( theUserName, RTL_TEXTENCODING_UTF8 ) ); + if ( aUser.getLength() > ( NE_ABUFSIZ - 1 ) ) + { + SAL_WARN( "ucb.ucp.webdav", "NeonSession_NeonAuth - username too long!" ); + return -1; + } + + OString aPass( OUStringToOString( thePassWord, RTL_TEXTENCODING_UTF8 ) ); + if ( aPass.getLength() > ( NE_ABUFSIZ - 1 ) ) + { + SAL_WARN( "ucb.ucp.webdav", "NeonSession_NeonAuth - password too long!" ); + return -1; + } + + // #100211# - checked + strcpy( inoutUserName, aUser.getStr() ); + strcpy( inoutPassWord, aPass.getStr() ); + + return theRetVal; +} + +namespace { + OUString GetHostnamePart( const OUString& _rRawString ) + { + OUString sPart; + OUString sPartId("CN="); + sal_Int32 nContStart = _rRawString.indexOf( sPartId ); + if ( nContStart != -1 ) + { + nContStart += sPartId.getLength(); + sal_Int32 nContEnd = _rRawString.indexOf( ',', nContStart ); + sPart = nContEnd != -1 ? + _rRawString.copy(nContStart, nContEnd - nContStart) : + _rRawString.copy(nContStart); + } + return sPart; + } +} // namespace + +extern "C" { + +static int NeonSession_CertificationNotify( void *userdata, + int, + const ne_ssl_certificate *cert ) +{ + NeonSession * pSession = static_cast< NeonSession * >( userdata ); + return pSession->CertificationNotify(cert); +} + +} + +int NeonSession::CertificationNotify(const ne_ssl_certificate *cert) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + OSL_ASSERT( cert ); + + uno::Reference< security::XCertificateContainer > xCertificateContainer; + try + { + xCertificateContainer = security::CertificateContainer::create( getComponentContext() ); + } + catch ( uno::Exception const & ) + { + } + + if ( !xCertificateContainer.is() ) + return 1; + + char * dn = ne_ssl_readable_dname( ne_ssl_cert_subject( cert ) ); + OUString cert_subject( dn, strlen( dn ), RTL_TEXTENCODING_UTF8, 0 ); + + ne_free( dn ); + + security::CertificateContainerStatus certificateContainer( + xCertificateContainer->hasCertificate( + getHostName(), cert_subject ) ); + + if ( certificateContainer != security::CertificateContainerStatus_NOCERT ) + return + certificateContainer == security::CertificateContainerStatus_TRUSTED + ? 0 + : 1; + + uno::Reference< xml::crypto::XSEInitializer > xSEInitializer; + try + { + xSEInitializer = xml::crypto::SEInitializer::create( getComponentContext() ); + } + catch ( uno::Exception const & ) + { + } + + if ( !xSEInitializer.is() ) + return 1; + + uno::Reference< xml::crypto::XXMLSecurityContext > xSecurityContext( + xSEInitializer->createSecurityContext( OUString() ) ); + + uno::Reference< xml::crypto::XSecurityEnvironment > xSecurityEnv( + xSecurityContext->getSecurityEnvironment() ); + + //The end entity certificate + char * eeCertB64 = ne_ssl_cert_export( cert ); + + OString sEECertB64( eeCertB64 ); + + uno::Reference< security::XCertificate > xEECert( + xSecurityEnv->createCertificateFromAscii( + OStringToOUString( sEECertB64, RTL_TEXTENCODING_ASCII_US ) ) ); + + ne_free( eeCertB64 ); + eeCertB64 = nullptr; + + std::vector< uno::Reference< security::XCertificate > > vecCerts; + const ne_ssl_certificate * issuerCert = cert; + do + { + //get the intermediate certificate + //the returned value is const ! Therefore it does not need to be freed + //with ne_ssl_cert_free, which takes a non-const argument + issuerCert = ne_ssl_cert_signedby( issuerCert ); + if ( nullptr == issuerCert ) + break; + + char * imCertB64 = ne_ssl_cert_export( issuerCert ); + OString sInterMediateCertB64( imCertB64 ); + ne_free( imCertB64 ); + + uno::Reference< security::XCertificate> xImCert( + xSecurityEnv->createCertificateFromAscii( + OStringToOUString( sInterMediateCertB64, RTL_TEXTENCODING_ASCII_US ) ) ); + if ( xImCert.is() ) + vecCerts.push_back( xImCert ); + } + while ( true ); + + sal_Int64 certValidity = xSecurityEnv->verifyCertificate( xEECert, + ::comphelper::containerToSequence( vecCerts ) ); + + if ( isDomainMatch( + GetHostnamePart( xEECert->getSubjectName() ) ) ) + { + // if host name matched with certificate then look if the + // certificate was ok + if( certValidity == security::CertificateValidity::VALID ) + return 0; + } + + const uno::Reference< ucb::XCommandEnvironment > xEnv( + getRequestEnvironment().m_xEnv ); + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH( + xEnv->getInteractionHandler() ); + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleCertificateValidationRequest > + xRequest( new ucbhelper::SimpleCertificateValidationRequest( + static_cast<sal_Int32>(certValidity), xEECert, getHostName() ) ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + uno::Reference< task::XInteractionApprove > xApprove( + xSelection.get(), uno::UNO_QUERY ); + if ( xApprove.is() ) + { + xCertificateContainer->addCertificate( + getHostName(), cert_subject, true ); + return 0; + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( + getHostName(), cert_subject, false ); + return 1; + } + } + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( + getHostName(), cert_subject, false ); + return 1; + } + } + return 1; +} + +extern "C" { + +static void NeonSession_PreSendRequest( ne_request * req, + void * userdata, + ne_buffer * headers ) +{ + // userdata -> value returned by 'create' + NeonSession * pSession = static_cast< NeonSession * >( userdata ); + if (!pSession) + return; + pSession->PreSendRequest(req, headers); +} + +} + +void NeonSession::PreSendRequest(ne_request* req, ne_buffer* headers) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + // If there is a proxy server in between, it shall never use + // cached data. We always want 'up-to-date' data. + ne_buffer_concat( headers, "Pragma: no-cache", EOL, nullptr ); + // alternative, but understood by HTTP 1.1 servers only: + // ne_buffer_concat( headers, "Cache-Control: max-age=0", EOL, NULL ); + + const RequestDataMap * pRequestData + = static_cast< const RequestDataMap* >( + getRequestData() ); + + RequestDataMap::const_iterator it = pRequestData->find( req ); + if ( it != pRequestData->end() ) + { + if ( !(*it).second.aContentType.isEmpty() ) + { + char * pData = headers->data; + if ( strstr( pData, "Content-Type:" ) == nullptr ) + { + OString aType + = OUStringToOString( (*it).second.aContentType, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, "Content-Type: ", + aType.getStr(), EOL, nullptr ); + } + } + + if ( !(*it).second.aReferer.isEmpty() ) + { + char * pData = headers->data; + if ( strstr( pData, "Referer:" ) == nullptr ) + { + OString aReferer + = OUStringToOString( (*it).second.aReferer, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, "Referer: ", + aReferer.getStr(), EOL, nullptr ); + } + } + } + + const DAVRequestHeaders & rHeaders + = getRequestEnvironment().m_aRequestHeaders; + + for ( const auto& rHeader : rHeaders ) + { + OString aHeader + = OUStringToOString( rHeader.first, + RTL_TEXTENCODING_UTF8 ); + OString aValue + = OUStringToOString( rHeader.second, + RTL_TEXTENCODING_UTF8 ); + ne_buffer_concat( headers, aHeader.getStr(), ": ", + aValue.getStr(), EOL, nullptr ); + } +} + +//See https://bugzilla.redhat.com/show_bug.cgi?id=544619#c4 +//neon is threadsafe, but uses gnutls which is only thread-safe +//if initialized to be thread-safe. cups, unfortunately, generally +//initializes it first, and as non-thread-safe, leaving the entire +//stack unsafe +namespace webdav_ucp +{ + osl::Mutex& getGlobalNeonMutex() + { + static osl::Mutex aMutex; + return aMutex; + } +} + +// static members +bool NeonSession::m_bGlobalsInited = false; +NeonLockStore NeonSession::m_aNeonLockStore; + +NeonSession::NeonSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const uno::Sequence< beans::NamedValue >& rFlags, + const ucbhelper::InternetProxyDecider & rProxyDecider ) + : DAVSession( rSessionFactory ) + , m_nProxyPort( 0 ) + , m_aFlags( rFlags ) + , m_pHttpSession( nullptr ) + , m_pRequestData( new RequestDataMap ) + , m_rProxyDecider( rProxyDecider ) +{ + NeonUri theUri( inUri ); + m_aScheme = theUri.GetScheme(); + m_aHostName = theUri.GetHost(); + m_nPort = theUri.GetPort(); + SAL_INFO( "ucb.ucp.webdav", "NeonSession ctor - URL <" << inUri << ">" ); +} + +NeonSession::~NeonSession( ) +{ + if ( m_pHttpSession ) + { + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ne_session_destroy( m_pHttpSession ); + } + m_pHttpSession = nullptr; + } + delete static_cast< RequestDataMap * >( m_pRequestData ); +} + +void NeonSession::Init( const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + m_aEnv = rEnv; + Init(); +} + +void NeonSession::Init() +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + bool bCreateNewSession = m_bNeedNewSession; + m_bNeedNewSession = false; + + if ( m_pHttpSession == nullptr ) + { + // Ensure that Neon sockets are initialized + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + if (!m_bGlobalsInited ) + { + if ( ne_sock_init() != 0 ) + throw DAVException( DAVException::DAV_SESSION_CREATE, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + // #122205# - libxml2 needs to be initialized once if used by + // multithreaded programs like OOo. + xmlInitParser(); +#if OSL_DEBUG_LEVEL > 0 + // for more debug flags see ne_utils.h; NE_DEBUGGING must be defined + // while compiling neon in order to actually activate neon debug + // output. + ne_debug_init( stderr, NE_DBG_FLUSH + | NE_DBG_HTTP + // | NE_DBG_HTTPBODY + // | NE_DBG_HTTPAUTH + // | NE_DBG_XML + // | NE_DBG_XMLPARSE + | NE_DBG_LOCKS + | NE_DBG_SSL + ); +#endif + m_bGlobalsInited = true; + } + + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // Not yet initialized. Create new session. + bCreateNewSession = true; + } + else + { + // #112271# Check whether proxy settings are still valid (They may + // change at any time). If not, create new Neon session. + + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + if ( ( rProxyCfg.aName != m_aProxyName ) + || ( rProxyCfg.nPort != m_nProxyPort ) ) + { + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + bCreateNewSession = true; + } + + if (bCreateNewSession) + { + // new session needed, destroy old first + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ne_session_destroy( m_pHttpSession ); + } + m_pHttpSession = nullptr; + } + } + + if ( !bCreateNewSession ) + return; + + const sal_Int32 nConnectTimeoutMax = 180; + const sal_Int32 nConnectTimeoutMin = 2; + const sal_Int32 nReadTimeoutMax = 180; + const sal_Int32 nReadTimeoutMin = 20; + + // @@@ For FTP over HTTP proxy inUserInfo is needed to be able to + // build the complete request URI (including user:pass), but + // currently (0.22.0) neon does not allow to pass the user info + // to the session + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + m_pHttpSession = ne_session_create( + OUStringToOString( m_aScheme, RTL_TEXTENCODING_UTF8 ).getStr(), + /* theUri.GetUserInfo(), + @@@ for FTP via HTTP proxy, but not supported by Neon */ + OUStringToOString( m_aHostName, RTL_TEXTENCODING_UTF8 ).getStr(), + m_nPort ); + } + + if ( m_pHttpSession == nullptr ) + throw DAVException( DAVException::DAV_SESSION_CREATE, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + // Register the session with the lock store + m_aNeonLockStore.registerSession( m_pHttpSession ); + + if ( m_aScheme.equalsIgnoreAsciiCase("https") ) + { + // Set a failure callback for certificate check + ne_ssl_set_verify( + m_pHttpSession, NeonSession_CertificationNotify, this); + + // Tell Neon to tell the SSL library used (OpenSSL or + // GnuTLS, I guess) to use a default set of root + // certificates. + ne_ssl_trust_default_ca(m_pHttpSession); + } + + // Add hooks (i.e. for adding additional headers to the request) + ne_hook_pre_send( m_pHttpSession, NeonSession_PreSendRequest, this ); + + if ( !m_aProxyName.isEmpty() ) + { + ne_session_proxy( m_pHttpSession, + OUStringToOString( + m_aProxyName, + RTL_TEXTENCODING_UTF8 ).getStr(), + m_nProxyPort ); + } + + // avoid KeepAlive? + if ( noKeepAlive(m_aFlags) ) + ne_set_session_flag( m_pHttpSession, NE_SESSFLAG_PERSIST, 0 ); + + // Register for redirects. + ne_redirect_register( m_pHttpSession ); + + // authentication callbacks. + ne_add_server_auth( m_pHttpSession, NE_AUTH_ALL, NeonSession_NeonAuth, this ); + ne_add_proxy_auth ( m_pHttpSession, NE_AUTH_ALL, NeonSession_NeonAuth, this ); + + // set timeout to connect + // if connect_timeout is not set, neon returns NE_CONNECT when the TCP socket default + // timeout elapses + // with connect_timeout set neon returns NE_TIMEOUT if elapsed when the connection + // didn't succeed + // grab it from configuration + uno::Reference< uno::XComponentContext > rContext = m_xFactory->getComponentContext(); + + // set the timeout (in seconds) used when making a connection + sal_Int32 nConnectTimeout = officecfg::Inet::Settings::ConnectTimeout::get( rContext ); + ne_set_connect_timeout( m_pHttpSession, + std::max( nConnectTimeoutMin, + std::min( nConnectTimeout, nConnectTimeoutMax ) ) ); + + // provides a read time out facility as well + // set the timeout (in seconds) used when reading from a socket. + sal_Int32 nReadTimeout = officecfg::Inet::Settings::ReadTimeout::get( rContext ); + ne_set_read_timeout( m_pHttpSession, + std::max( nReadTimeoutMin, + std::min( nReadTimeout, nReadTimeoutMax ) ) ); + +} + +bool NeonSession::CanUse( const OUString & inUri, + const uno::Sequence< beans::NamedValue >& rFlags ) +{ + try + { + NeonUri theUri( inUri ); + if ( ( theUri.GetPort() == m_nPort ) && + ( theUri.GetHost() == m_aHostName ) && + ( theUri.GetScheme() == m_aScheme ) && + ( rFlags == m_aFlags ) ) + return true; + } + catch ( DAVException const & ) + { + return false; + } + return false; +} + +bool NeonSession::UsesProxy() +{ + Init(); + return !m_aProxyName.isEmpty() ; +} + +void NeonSession::OPTIONS( const OUString & inPath, + DAVOptions & rOptions, // contains the name+values of every header + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + SAL_INFO( "ucb.ucp.webdav", "OPTIONS - relative URL <" << inPath << ">" ); + + rOptions.init(); + + Init( rEnv ); + int theRetVal; + + ne_request *req = ne_request_create(m_pHttpSession, "OPTIONS", OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr()); + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + theRetVal = ne_request_dispatch(req); + } + + //check if http error is in the 200 class (no error) + if (theRetVal == NE_OK && ne_get_status(req)->klass != 2) { + theRetVal = NE_ERROR; + } + + if ( theRetVal == NE_OK ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + OUString aHeaderName(OUString(name, strlen(name), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase()); + OUString aHeaderValue(value, strlen(value), RTL_TEXTENCODING_ASCII_US); + + // display the single header + SAL_INFO( "ucb.ucp.webdav", "OPTIONS - received header: " << aHeaderName << ":" << aHeaderValue ); + + if ( aHeaderName == "allow" ) + { + rOptions.setAllowedMethods( aHeaderValue ); + } + else if ( aHeaderName == "dav" ) + { + // check type of dav capability + // need to parse the value, token separator: "," + // see <http://tools.ietf.org/html/rfc4918#section-10.1>, + // <http://tools.ietf.org/html/rfc4918#section-18>, + // and <http://tools.ietf.org/html/rfc7230#section-3.2> + // we detect the class (1, 2 and 3), other elements (token, URL) + // are not used for now + // silly parser written using OUString, not very efficient + // but quick and easy to write... + sal_Int32 nFromIndex = 0; + sal_Int32 nNextIndex = 0; + while( ( nNextIndex = aHeaderValue.indexOf( ",", nFromIndex ) ) != -1 ) + { // found a comma + // try to convert from nFromIndex to nNextIndex -1 in a number + // if this is 1 or 2 or 3, use for class setting + sal_Int32 nClass = + aHeaderValue.copy( nFromIndex, nNextIndex - nFromIndex ).toInt32(); + switch( nClass ) + { + case 1: + rOptions.setClass1(); + break; + case 2: + rOptions.setClass2(); + break; + case 3: + rOptions.setClass3(); + break; + default: + ; + } + // next starting point + nFromIndex = nNextIndex + 1; + } + // check for last fragment + if ( nFromIndex < aHeaderValue.getLength() ) + { + sal_Int32 nClass = aHeaderValue.copy( nFromIndex ).toInt32(); + switch( nClass ) + { + case 1: + rOptions.setClass1(); + break; + case 2: + rOptions.setClass2(); + break; + case 3: + rOptions.setClass3(); + break; + default: + ; + } + } + } + } + // if applicable, check for lock state: + if( rOptions.isClass2() || rOptions.isClass3() ) + { + //dav with lock possible, check for locked state + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) != nullptr ) + { + // we own a lock for this URL, + // set locked state + rOptions.setLocked(); + } + } + } + + ne_request_destroy(req); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) +{ + + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { //debug + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - relative URL: <" << inPath << "> Depth: " << inDepth ); + for(const auto& rPropName : inPropNames) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - property requested: " << rPropName ); + } + } //debug +#endif + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonPropFindRequest theRequest( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + inDepth, + inPropNames, + ioResources, + theRetVal ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - relative URL: <" << inPath << "> Depth: " << inDepth ); + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonPropFindRequest theRequest( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + inDepth, + ioResInfo, + theRetVal ); + +#if defined SAL_LOG_INFO + { //debug + for ( const auto& rResInfo : ioResInfo ) + { + for ( const auto& rProp : rResInfo.properties ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - returned property (name only): " << rProp ); + } + } + } //debug +#endif + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) +{ + SAL_INFO( "ucb.ucp.webdav", "PROPPATCH - relative URL <" << inPath << ">" ); + + /* @@@ Which standard live properties can be set by the client? + This is a known WebDAV RFC issue ( verified: 04/10/2001 ) + --> http://www.ics.uci.edu/pub/ietf/webdav/protocol/issues.html + + mod_dav implementation: + + creationdate r ( File System prop ) + displayname w + getcontentlanguage r ( #ifdef DAV_DISABLE_WRITEABLE_PROPS ) + getcontentlength r ( File System prop ) + getcontenttype r ( #ifdef DAV_DISABLE_WRITEABLE_PROPS ) + getetag r ( File System prop ) + getlastmodified r ( File System prop ) + lockdiscovery r + resourcetype r + source w + supportedlock r + executable w ( #ifndef WIN32 ) + + All dead properties are of course writable. + */ + + int theRetVal = NE_OK; + + int n; // for the "for" loop + + // Generate the list of properties we want to set. + int nPropCount = inValues.size(); + std::unique_ptr<ne_proppatch_operation[]> pItems( + new ne_proppatch_operation[ nPropCount + 1 ]); + for ( n = 0; n < nPropCount; ++n ) + { + const ProppatchValue & rValue = inValues[ n ]; + + // Split fullname into namespace and name! + ne_propname * pName = new ne_propname; + DAVProperties::createNeonPropName( rValue.name, *pName ); + pItems[ n ].name = pName; + + if ( rValue.operation == PROPSET ) + { + pItems[ n ].type = ne_propset; + + OUString aStringValue; + if ( DAVProperties::isUCBDeadProperty( *pName ) ) + { + // DAV dead property added by WebDAV UCP? + if ( !UCBDeadPropertyValue::toXML( rValue.value, + aStringValue ) ) + { + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + else if ( !( rValue.value >>= aStringValue ) ) + { + // complex properties... + if ( rValue.name == DAVProperties::SOURCE ) + { + uno::Sequence< ucb::Link > aLinks; + if ( rValue.value >>= aLinks ) + { + LinkSequence::toXML( aLinks, aStringValue ); + } + else + { + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", "PROPPATCH - Unsupported type!" ); + // Error! + pItems[ n ].value = nullptr; + theRetVal = NE_ERROR; + nPropCount = n + 1; + break; + } + } + pItems[ n ].value + = strdup( OUStringToOString( aStringValue, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else + { + pItems[ n ].type = ne_propremove; + pItems[ n ].value = nullptr; + } + } + + if ( theRetVal == NE_OK ) + { + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + pItems[ n ].name = nullptr; + + theRetVal = ne_proppatch( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + pItems.get() ); + } + + for ( n = 0; n < nPropCount; ++n ) + { + free( const_cast<char *>(pItems[ n ].name->name) ); + delete pItems[ n ].name; + free( const_cast<char *>(pItems[ n ].value) ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "HEAD - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = NE_OK; + NeonHeadRequest theRequest( m_pHttpSession, + inPath, + inHeaderNames, + ioResource, + theRetVal ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockReader, + false, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + NeonRequestContext aCtx( ioOutputStream ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockWriter, + false, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream, inHeaderNames, ioResource ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockReader, + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream, inHeaderNames, ioResource ); + int theRetVal = GET0( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "GET - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + ioResource.uri = inPath; + ioResource.properties.clear(); + + NeonRequestContext aCtx( ioOutputStream, inHeaderNames, ioResource ); + int theRetVal = GET( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + NeonSession_ResponseBlockWriter, + true, + &aCtx ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::PUT( const OUString & inPath, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "PUT - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, false ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + int theRetVal = PUT( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + aDataToSend.getLength() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +uno::Reference< io::XInputStream > +NeonSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "POST - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + rtl::Reference< NeonInputStream > xInputStream( new NeonInputStream ); + NeonRequestContext aCtx( xInputStream ); + int theRetVal = POST( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + NeonSession_ResponseBlockReader, + &aCtx, + rContentType, + rReferer ); + + HandleError( theRetVal, inPath, rEnv ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + +void NeonSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + uno::Reference< io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "POST - relative URL <" << inPath << ">" ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + Init( rEnv ); + + NeonRequestContext aCtx( oOutputStream ); + int theRetVal = POST( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr(), + reinterpret_cast< const char * >( + aDataToSend.getConstArray() ), + NeonSession_ResponseBlockWriter, + &aCtx, + rContentType, + rReferer ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "MKCOL - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = ne_mkcol( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +void NeonSession::COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "COPY - inSourceURL: "<<inSourceURL<<" inDestinationURL: "<<inDestinationURL); + + Init( rEnv ); + + NeonUri theSourceUri( inSourceURL ); + NeonUri theDestinationUri( inDestinationURL ); + + int theRetVal = ne_copy( m_pHttpSession, + inOverWrite ? 1 : 0, + NE_DEPTH_INFINITE, + OUStringToOString( + theSourceUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( + theDestinationUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inSourceURL, rEnv ); +} + +void NeonSession::MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "MOVE - inSourceURL: "<<inSourceURL<<" inDestinationURL: "<<inDestinationURL); + + Init( rEnv ); + + NeonUri theSourceUri( inSourceURL ); + NeonUri theDestinationUri( inDestinationURL ); + int theRetVal = ne_move( m_pHttpSession, + inOverWrite ? 1 : 0, + OUStringToOString( + theSourceUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( + theDestinationUri.GetPath(), + RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inSourceURL, rEnv ); +} + +void NeonSession::DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "DESTROY - relative URL <" << inPath << ">" ); + + Init( rEnv ); + + int theRetVal = ne_delete( m_pHttpSession, + OUStringToOString( + inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + + HandleError( theRetVal, inPath, rEnv ); +} + +namespace +{ + sal_Int32 lastChanceToSendRefreshRequest( TimeValue const & rStart, + int timeout ) + { + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != NE_TIMEOUT_INFINITE ) + { + sal_Int32 calltime = aEnd.Seconds - rStart.Seconds; + if ( calltime <= timeout ) + { + lastChanceToSendRefreshRequest = rStart.Seconds + timeout; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "LOCK - no chance to refresh lock before timeout!" ); + } + } + return lastChanceToSendRefreshRequest; + } + +} // namespace + +// Set new lock +void NeonSession::LOCK( const OUString & inPath, + ucb::Lock & rLock, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - relative URL: <" << inPath << ">" ); + + // before issuing the lock command, + // better check first if we already have one on this href + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) != nullptr ) + { + // we already own a lock for this href + // no need to ask for another + // TODO: add a lockdiscovery request for confirmation + // checking the locktoken, the only item that's unique + return; + } + + Init( rEnv ); + + /* Create a depth zero, exclusive write lock, with default timeout + * (allowing a server to pick a default). token, owner and uri are + * unset. */ + NeonLock * theLock = ne_lock_create(); + + // Set the lock uri + ne_uri aUri; + ne_uri_parse( OUStringToOString( makeAbsoluteURL( inPath ), + RTL_TEXTENCODING_UTF8 ).getStr(), + &aUri ); + theLock->uri = aUri; + + // Set the lock depth + switch( rLock.Depth ) + { + case ucb::LockDepth_ZERO: + theLock->depth = NE_DEPTH_ZERO; + break; + case ucb::LockDepth_ONE: + theLock->depth = NE_DEPTH_ONE; + break; + case ucb::LockDepth_INFINITY: + theLock->depth = NE_DEPTH_INFINITE; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + // Set the lock scope + switch ( rLock.Scope ) + { + case ucb::LockScope_EXCLUSIVE: + theLock->scope = ne_lockscope_exclusive; + break; + case ucb::LockScope_SHARED: + theLock->scope = ne_lockscope_shared; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + // Set the lock timeout + theLock->timeout = static_cast<long>(rLock.Timeout); + + // Set the lock owner + OUString aValue; + rLock.Owner >>= aValue; + theLock->owner = + ne_strdup( OUStringToOString( aValue, + RTL_TEXTENCODING_UTF8 ).getStr() ); + TimeValue startCall; + osl_getSystemTime( &startCall ); + + int theRetVal = ne_lock( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aNeonLockStore.addLock( theLock, + this, + lastChanceToSendRefreshRequest( + startCall, theLock->timeout ) ); + + uno::Sequence< OUString > aTokens( 1 ); + aTokens[ 0 ] = OUString::createFromAscii( theLock->token ); + rLock.LockTokens = aTokens; + + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - Created lock for <" << makeAbsoluteURL( inPath ) + << "> token: <" << theLock->token << "> timeout: " << theLock->timeout << " sec."); + } + else + { + ne_lock_destroy( theLock ); + + SAL_INFO( "ucb.ucp.webdav", "LOCK (create) - Obtaining lock for <" + << makeAbsoluteURL( inPath ) << " failed!" ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +// Refresh existing lock +bool NeonSession::LOCK( NeonLock * pLock, + sal_Int32 & rlastChanceToSendRefreshRequest ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "LOCK (refresh) - relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); + } +#endif + + // refresh existing lock. + + TimeValue startCall; + osl_getSystemTime( &startCall ); + + // save the current requested timeout, because ne_lock_refresh uses + // pLock->timeout as an out parameter. This prevents a feedback-loop, + // where we would request a shorter timeout on each refresh. + long timeout = pLock->timeout; + const int theRetVal = ne_lock_refresh(m_pHttpSession, pLock); + if (theRetVal == NE_OK) + { + rlastChanceToSendRefreshRequest + = lastChanceToSendRefreshRequest( startCall, pLock->timeout ); + + SAL_INFO( "ucb.ucp.webdav", "LOCK (refresh) - Lock successfully refreshed." ); + pLock->timeout = timeout; + return true; + } + else + { +#if defined SAL_LOG_WARN + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_WARN( "ucb.ucp.webdav", "LOCK (refresh) - not refreshed! Relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); +#endif + if (theRetVal == NE_AUTH) + { + // tdf#126279: see handling of NE_AUTH in HandleError + m_bNeedNewSession = true; + m_aNeonLockStore.removeLockDeferred(pLock); + } + return false; + } +} + +void NeonSession::UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + // get the neon lock from lock store + NeonLock * theLock + = m_aNeonLockStore.findByUri( makeAbsoluteURL( inPath ) ); + if ( !theLock ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + SAL_INFO( "ucb.ucp.webdav", "UNLOCK - relative URL: <" << inPath << "> token: <" << theLock->token << ">" ); + Init( rEnv ); + + int theRetVal = ne_unlock( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aNeonLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + } + else + { + SAL_INFO( "ucb.ucp.webdav", "UNLOCK - Unlocking of <" + << makeAbsoluteURL( inPath ) << "> failed." ); + } + + HandleError( theRetVal, inPath, rEnv ); +} + +bool NeonSession::UNLOCK( NeonLock * pLock ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << ">" ); + ne_free( p ); + } +#endif + + const int theRetVal = ne_unlock(m_pHttpSession, pLock); + if (theRetVal == NE_OK) + { +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << "> succeeded." ); + ne_free( p ); + } +#endif + return true; + } + else + { +#if defined SAL_LOG_INFO + { + char * p = ne_uri_unparse( &(pLock->uri) ); + SAL_INFO( "ucb.ucp.webdav", "UNLOCK (from store) - relative URL: <" << p << "> token: <" << pLock->token << "> failed!" ); + ne_free( p ); + } +#endif + if (theRetVal == NE_AUTH) + { + // tdf#126279: see handling of NE_AUTH in HandleError + m_bNeedNewSession = true; + } + return false; + } +} + +void NeonSession::abort() +{ + SAL_INFO( "ucb.ucp.webdav", "neon commands cannot be aborted" ); +} + +ucbhelper::InternetProxyServer NeonSession::getProxySettings() const +{ + if ( m_aScheme == "http" || m_aScheme == "https" ) + { + return m_rProxyDecider.getProxy( m_aScheme, + m_aHostName, + m_nPort ); + } + else + { + return m_rProxyDecider.getProxy( m_aScheme, + OUString() /* not used */, + -1 /* not used */ ); + } +} + +namespace { + +bool containsLocktoken( const uno::Sequence< ucb::Lock > & rLocks, + const char * token ) +{ + return std::any_of(rLocks.begin(), rLocks.end(), [&token](const ucb::Lock& rLock) { + const uno::Sequence< OUString > & rTokens = rLock.LockTokens; + return std::any_of(rTokens.begin(), rTokens.end(), + [&token](const OUString& rToken) { return rToken.equalsAscii( token ); }); + }); +} + +} // namespace + +bool NeonSession::removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) +{ + NeonLock * theLock = m_aNeonLockStore.findByUri( inURL ); + if ( !theLock ) + return false; + + // do a lockdiscovery to check whether this lock is still valid. + try + { + // @@@ Alternative: use ne_lock_discover() => less overhead + + std::vector< DAVResource > aResources; + std::vector< OUString > aPropNames; + aPropNames.push_back( DAVProperties::LOCKDISCOVERY ); + + PROPFIND( rEnv.m_aRequestURI, DAVZERO, aPropNames, aResources, rEnv ); + + if ( aResources.empty() ) + return false; + + for ( const auto& rProp : aResources[ 0 ].properties ) + { + if ( rProp.Name == DAVProperties::LOCKDISCOVERY ) + { + uno::Sequence< ucb::Lock > aLocks; + if ( !( rProp.Value >>= aLocks ) ) + return false; + + if ( !containsLocktoken( aLocks, theLock->token ) ) + { + // expired! + break; + } + + // still valid. + return false; + } + } + + // No lockdiscovery prop in propfind result / locktoken not found + // in propfind result -> not locked + SAL_WARN( "ucb.ucp.webdav", "Removing expired lock token for <" << inURL + << "> token: " << theLock->token ); + + m_aNeonLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + return true; + } + catch ( DAVException const & ) + { + } + return false; +} + +// Common error handler +void NeonSession::HandleError( int nError, + const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + // Map error code to DAVException. + switch ( nError ) + { + case NE_OK: + return; + + case NE_ERROR: // Generic error + { + const char* sErr = ne_get_error(m_pHttpSession); + OUString aText(sErr, strlen(sErr), osl_getThreadTextEncoding()); + + sal_uInt16 code = makeStatusCode( aText ); + + SAL_WARN( "ucb.ucp.webdav", "Neon returned NE_ERROR, http response status code was: " << code << " '" << aText << "'" ); + if ( SC_BAD_REQUEST <= code && code < SC_INTERNAL_SERVER_ERROR ) + { + // error codes in the range 4xx + switch ( code ) + { + case SC_LOCKED: + { + if ( m_aNeonLockStore.findByUri( + makeAbsoluteURL( inPath ) ) == nullptr ) + { + // locked by 3rd party + throw DAVException( DAVException::DAV_LOCKED ); + } + else + { + // locked by ourself + throw DAVException( DAVException::DAV_LOCKED_SELF ); + } + } + break; + case SC_PRECONDITION_FAILED: + case SC_BAD_REQUEST: + { + // Special handling for 400 and 412 status codes, which may indicate + // that a lock previously obtained by us has been released meanwhile + // by the server. Unfortunately, RFC is not clear at this point, + // thus server implementations behave different... + if ( removeExpiredLocktoken( makeAbsoluteURL( inPath ), rEnv ) ) + throw DAVException( DAVException::DAV_LOCK_EXPIRED ); + } + break; + case SC_REQUEST_TIMEOUT: + { + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + } + break; + case SC_UNAUTHORIZED: // User authentication failed on server + { + throw DAVException( DAVException::DAV_HTTP_AUTH, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + } + break; + case SC_GONE: + case SC_LENGTH_REQUIRED: + case SC_REQUEST_ENTITY_TOO_LARGE: + case SC_REQUEST_URI_TOO_LONG: + case SC_UNSUPPORTED_MEDIA_TYPE: + case SC_REQUESTED_RANGE_NOT_SATISFIABLE: + case SC_EXPECTATION_FAILED: + case SC_UNPROCESSABLE_ENTITY: + case SC_FAILED_DEPENDENCY: + case SC_CONFLICT: + case SC_NOT_ACCEPTABLE: + case SC_PAYMENT_REQUIRED: + case SC_PROXY_AUTHENTICATION_REQUIRED: + default: + // set 400 error, if not one of others + code = SC_BAD_REQUEST; + [[fallthrough]]; + case SC_FORBIDDEN: + case SC_NOT_FOUND: + case SC_METHOD_NOT_ALLOWED: + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + break; + } + } + else if ( SC_INTERNAL_SERVER_ERROR <= code ) + { + // deal with HTTP response status codes higher then 500 + // error codes in the range 5xx, server errors + // but there exists unofficial code in the range 1000 and up + // for example see: + // <https://support.cloudflare.com/hc/en-us/sections/200820298-Error-Pages> (retrieved 2016-10-05) + switch ( code ) + { + // the error codes case before the default case are not actively + // managed by LO + case SC_BAD_GATEWAY: + case SC_SERVICE_UNAVAILABLE: + case SC_GATEWAY_TIMEOUT: + case SC_HTTP_VERSION_NOT_SUPPORTED: + case SC_INSUFFICIENT_STORAGE: + default: + // set 500 error, if not one of others + // expand the error code + code = SC_INTERNAL_SERVER_ERROR; + [[fallthrough]]; + case SC_INTERNAL_SERVER_ERROR: + case SC_NOT_IMPLEMENTED: + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + break; + } + } + else + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + } + break; + case NE_LOOKUP: // Name lookup failed. + SAL_WARN( "ucb.ucp.webdav", "Name lookup failed" ); + throw DAVException( DAVException::DAV_HTTP_LOOKUP, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_AUTH: // User authentication failed on server + // m_pHttpSession could get invalidated, e.g., as result of clean_session called in + // ah_post_send in case when auth_challenge failed, which invalidates the auth_session + // which we established in Init(): the auth_session's sspi_host gets disposed, and + // next attempt to authenticate would crash in continue_sspi trying to dereference it + m_bNeedNewSession = true; + throw DAVException( DAVException::DAV_HTTP_AUTH, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_PROXYAUTH: // User authentication failed on proxy + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_AUTHPROXY" ); + throw DAVException( DAVException::DAV_HTTP_AUTHPROXY, + NeonUri::makeConnectionEndPointString( + m_aProxyName, m_nProxyPort ) ); + + case NE_CONNECT: // Could not connect to server + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_CONNECT" ); + throw DAVException( DAVException::DAV_HTTP_CONNECT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_TIMEOUT: // Connection timed out + SAL_WARN( "ucb.ucp.webdav", "DAVException::DAV_HTTP_TIMEOUT" ); + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_FAILED: // The precondition failed + SAL_WARN( "ucb.ucp.webdav", "The precondition failed" ); + throw DAVException( DAVException::DAV_HTTP_FAILED, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_RETRY: // Retry request (ne_end_request ONLY) + throw DAVException( DAVException::DAV_HTTP_RETRY, + NeonUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_REDIRECT: + { + NeonUri aUri( ne_redirect_location( m_pHttpSession ) ); + SAL_INFO( "ucb.ucp.webdav", "DAVException::DAV_HTTP_REDIRECT: new URI: " << aUri.GetURI() ); + throw DAVException( + DAVException::DAV_HTTP_REDIRECT, aUri.GetURI() ); + } + default: + { + SAL_WARN( "ucb.ucp.webdav", "Unknown Neon error code!" ); + const char* sErr = ne_get_error(m_pHttpSession); + throw DAVException( DAVException::DAV_HTTP_ERROR, + OUString(sErr, strlen(sErr), osl_getThreadTextEncoding()) ); + } + } +} + +namespace { + +void runResponseHeaderHandler( void * userdata, + const char * value ) +{ + OUString aHeader(value, strlen(value), RTL_TEXTENCODING_ASCII_US); + sal_Int32 nPos = aHeader.indexOf( ':' ); + + if ( nPos != -1 ) + { + OUString aHeaderName( aHeader.copy( 0, nPos ) ); + + NeonRequestContext * pCtx + = static_cast< NeonRequestContext * >( userdata ); + + // Note: Empty vector means that all headers are requested. + bool bIncludeIt = pCtx->pHeaderNames->empty(); + + if ( !bIncludeIt ) + { + // Check whether this header was requested. + auto it = std::find_if(pCtx->pHeaderNames->cbegin(), pCtx->pHeaderNames->cend(), + [&aHeaderName](const OUString& rName) { + // header names are case insensitive + return rName.equalsIgnoreAsciiCase( aHeaderName ); }); + + if ( it != pCtx->pHeaderNames->end() ) + { + aHeaderName = *it; + bIncludeIt = true; + } + } + + if ( bIncludeIt ) + { + // Create & set the PropertyValue + DAVPropertyValue thePropertyValue; + // header names are case insensitive, so are the + // corresponding property names. + thePropertyValue.Name = aHeaderName.toAsciiLowerCase(); + thePropertyValue.IsCaseSensitive = false; + + if ( nPos < aHeader.getLength() ) + thePropertyValue.Value <<= aHeader.copy( nPos + 1 ).trim(); + + // Add the newly created PropertyValue + pCtx->pResource->properties.push_back( thePropertyValue ); + } + } +} + +} // namespace + +int NeonSession::GET( ne_session * sess, + const char * uri, + ne_block_reader reader, + bool getheaders, + void * userdata ) +{ + //struct get_context ctx; + ne_request * req = ne_request_create( sess, "GET", uri ); + int ret; + + ne_decompress * dc + = ne_decompress_reader( req, ne_accept_2xx, reader, userdata ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( getheaders ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + char buffer[8192]; + + SAL_INFO( "ucb.ucp.webdav", "GET - received header: " << name << ": " << value ); + ne_snprintf(buffer, sizeof buffer, "%s: %s", name, value); + runResponseHeaderHandler(userdata, buffer); + } + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + if ( dc != nullptr ) + ne_decompress_destroy(dc); + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::GET0( ne_session * sess, + const char * uri, + bool getheaders, + void * userdata ) +{ + //struct get_context ctx; + ne_request * req = ne_request_create( sess, "GET", uri ); + int ret; + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( getheaders ) + { + void *cursor = nullptr; + const char *name, *value; + while ( ( cursor = ne_response_header_iterate( + req, cursor, &name, &value ) ) != nullptr ) + { + char buffer[8192]; + + SAL_INFO( "ucb.ucp.webdav", "GET - received header: " << name << ": " << value ); + ne_snprintf(buffer, sizeof buffer, "%s: %s", name, value); + runResponseHeaderHandler(userdata, buffer); + } + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::PUT( ne_session * sess, + const char * uri, + const char * buffer, + size_t size) +{ + ne_request * req = ne_request_create( sess, "PUT", uri ); + int ret; + + // tdf#99246 + // extract the path of uri + // ne_lock_using_resource below compares path, ignores all the rest. + // in case of Web proxy active, this function uri parameter is instead absolute + ne_uri aUri; + ne_uri_parse( uri, &aUri ); + ne_lock_using_resource( req, aUri.path, 0 ); + ne_lock_using_parent( req, uri ); + + ne_set_request_body_buffer( req, buffer, size ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + return ret; +} + +int NeonSession::POST( ne_session * sess, + const char * uri, + const char * buffer, + ne_block_reader reader, + void * userdata, + const OUString & rContentType, + const OUString & rReferer ) +{ + ne_request * req = ne_request_create( sess, "POST", uri ); + //struct get_context ctx; + int ret; + + RequestDataMap * pData = nullptr; + + if ( !rContentType.isEmpty() || !rReferer.isEmpty() ) + { + // Remember contenttype and referer. Data will be added to HTTP request + // header in 'PreSendRequest' callback. + pData = static_cast< RequestDataMap* >( m_pRequestData ); + (*pData)[ req ] = RequestData( rContentType, rReferer ); + } + + //ctx.total = -1; + //ctx.fd = fd; + //ctx.error = 0; + //ctx.session = sess; + + ///* Read the value of the Content-Length header into ctx.total */ + //ne_add_response_header_handler( req, "Content-Length", + // ne_handle_numeric_header, &ctx.total ); + + ne_add_response_body_reader( req, ne_accept_2xx, reader, userdata ); + + ne_set_request_body_buffer( req, buffer, strlen( buffer ) ); + + { + osl::Guard< osl::Mutex > theGlobalGuard(getGlobalNeonMutex()); + ret = ne_request_dispatch( req ); + } + + //if ( ctx.error ) + // ret = NE_ERROR; + //else + if ( ret == NE_OK && ne_get_status( req )->klass != 2 ) + ret = NE_ERROR; + + ne_request_destroy( req ); + + if ( pData ) + { + // Remove request data from session's list. + RequestDataMap::iterator it = pData->find( req ); + if ( it != pData->end() ) + pData->erase( it ); + } + + return ret; +} + +bool +NeonSession::getDataFromInputStream( + const uno::Reference< io::XInputStream > & xStream, + uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ) +{ + if ( xStream.is() ) + { + uno::Reference< io::XSeekable > xSeekable( xStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + try + { + sal_Int32 nSize + = sal::static_int_cast<sal_Int32>(xSeekable->getLength()); + sal_Int32 nRead + = xStream->readBytes( rData, nSize ); + + if ( nRead == nSize ) + { + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nSize + 1 ); + rData[ nSize ] = sal_Int8( 0 ); + } + return true; + } + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // getLength, readBytes + } + } + else + { + try + { + uno::Sequence< sal_Int8 > aBuffer; + sal_Int32 nPos = 0; + + sal_Int32 nRead = xStream->readSomeBytes( aBuffer, 65536 ); + while ( nRead > 0 ) + { + if ( rData.getLength() < ( nPos + nRead ) ) + rData.realloc( nPos + nRead ); + + aBuffer.realloc( nRead ); + memcpy( static_cast<void*>( rData.getArray() + nPos ), + static_cast<const void*>(aBuffer.getConstArray()), + nRead ); + nPos += nRead; + + aBuffer.realloc( 0 ); + nRead = xStream->readSomeBytes( aBuffer, 65536 ); + } + + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nPos + 1 ); + rData[ nPos ] = sal_Int8( 0 ); + } + return true; + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // readBytes + } + } + } + return false; +} + +bool +NeonSession::isDomainMatch( const OUString& certHostName ) +{ + OUString hostName = getHostName(); + + if (hostName.equalsIgnoreAsciiCase( certHostName ) ) + return true; + + if ( certHostName.startsWith( "*" ) && + hostName.getLength() >= certHostName.getLength() ) + { + OUString cmpStr = certHostName.copy( 1 ); + + if ( hostName.matchIgnoreAsciiCase( + cmpStr, hostName.getLength() - cmpStr.getLength() ) ) + return true; + } + return false; +} + +OUString NeonSession::makeAbsoluteURL( OUString const & rURL ) const +{ + try + { + // Is URL relative or already absolute? + if ( !rURL.isEmpty() && rURL[ 0 ] != '/' ) + { + // absolute. + return rURL; + } + else + { + ne_uri aUri = {}; + + ne_fill_server_uri( m_pHttpSession, &aUri ); + aUri.path + = ne_strdup( OUStringToOString( + rURL, RTL_TEXTENCODING_UTF8 ).getStr() ); + NeonUri aNeonUri( &aUri ); + ne_uri_free( &aUri ); + return aNeonUri.GetURI(); + } + } + catch ( DAVException const & ) + { + } + // error. + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonSession.hxx b/ucb/source/ucp/webdav-neon/NeonSession.hxx new file mode 100644 index 000000000..51b477373 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonSession.hxx @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX + +#include <config_lgpl.h> +#include <vector> +#include <osl/mutex.hxx> +#include "DAVSession.hxx" +#include "NeonTypes.hxx" +#include "NeonLockStore.hxx" +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +namespace ucbhelper { class ProxyDecider; } + +namespace webdav_ucp +{ + +// A DAVSession implementation using the neon/expat library +class NeonSession : public DAVSession +{ +private: + osl::Mutex m_aMutex; + OUString m_aScheme; + OUString m_aHostName; + OUString m_aProxyName; + sal_Int32 m_nPort; + sal_Int32 m_nProxyPort; + css::uno::Sequence< css::beans::NamedValue > m_aFlags; + HttpSession * m_pHttpSession; + bool m_bNeedNewSession = false; // Something happened that could invalidate m_pHttpSession + void * m_pRequestData; + const ucbhelper::InternetProxyDecider & m_rProxyDecider; + + // @@@ This should really be per-request data. But Neon currently + // (0.23.5) has no interface for passing per-request user data. + // Theoretically, a NeonSession instance could handle multiple requests + // at a time --currently it doesn't. Thus this is not an issue at the + // moment. + DAVRequestEnvironment m_aEnv; + + static bool m_bGlobalsInited; + static NeonLockStore m_aNeonLockStore; + +protected: + virtual ~NeonSession() override; + +public: + /// @throws std::exception + NeonSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const css::uno::Sequence< css::beans::NamedValue >& rFlags, + const ucbhelper::InternetProxyDecider & rProxyDecider ); + + // DAVSession methods + virtual bool CanUse( const OUString & inPath, + const css::uno::Sequence< css::beans::NamedValue >& rFlags ) override; + + virtual bool UsesProxy() override; + + const DAVRequestEnvironment & getRequestEnvironment() const + { return m_aEnv; } + + virtual void + OPTIONS( const OUString & inPath, + DAVOptions& rOptions, // contains the name+values + const DAVRequestEnvironment & rEnv ) override; + + // allprop & named + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) override; + + // propnames + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo >& ioResInfo, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET0( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + css::uno::Reference< css::io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) override; + + virtual void + MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) override; + + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + // set new lock. + virtual void LOCK( const OUString & inURL, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) override; + + virtual void UNLOCK( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) override; + + // helpers + virtual void abort() override; + + const OUString & getHostName() const { return m_aHostName; } + + ::uno::Reference< ::uno::XComponentContext > const & getComponentContext() const + { return m_xFactory->getComponentContext(); } + + const void * getRequestData() const { return m_pRequestData; } + + bool isDomainMatch( const OUString& certHostName ); + + int CertificationNotify(const ne_ssl_certificate *cert); + + int NeonAuth(const char* inAuthProtocol, const char* inRealm, + int attempt, char* inoutUserName, char * inoutPassWord); + + void PreSendRequest(ne_request* req, ne_buffer* headers); + +private: + friend class NeonLockStore; + + /// @throws css::uno::RuntimeException + void Init(); + + /// @throws css::uno::RuntimeException + void Init( const DAVRequestEnvironment & rEnv ); + + // ret: true => retry request. + /// @throws std::exception + void HandleError( int nError, + const OUString & inPath, + const DAVRequestEnvironment & rEnv ); + + ucbhelper::InternetProxyServer getProxySettings() const; + + bool removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ); + + // refresh lock, called by NeonLockStore::refreshLocks + bool LOCK( NeonLock * pLock, + sal_Int32 & rlastChanceToSendRefreshRequest ); + + // unlock, called by NeonLockStore::~NeonLockStore + bool UNLOCK( NeonLock * pLock ); + + // low level GET implementation, used by public GET implementations + static int GET( ne_session * sess, + const char * uri, + ne_block_reader reader, + bool getheaders, + void * userdata ); + + // low level GET implementation, used by public GET implementations + // used as a HEAD substitute when head is not available + static int GET0( ne_session * sess, + const char * uri, + bool getheaders, + void * userdata ); + + // Buffer-based PUT implementation. Neon only has file descriptor- + // based API. + static int PUT( ne_session * sess, + const char * uri, + const char * buffer, + size_t size ); + + // Buffer-based POST implementation. Neon only has file descriptor- + // based API. + int POST( ne_session * sess, + const char * uri, + const char * buffer, + ne_block_reader reader, + void * userdata, + const OUString & rContentType, + const OUString & rReferer ); + + // Helper: XInputStream -> Sequence< sal_Int8 > + static bool getDataFromInputStream( + const css::uno::Reference< css::io::XInputStream > & xStream, + css::uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ); + + OUString makeAbsoluteURL( OUString const & rURL ) const; +}; + +osl::Mutex& getGlobalNeonMutex(); + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonTypes.hxx b/ucb/source/ucp/webdav-neon/NeonTypes.hxx new file mode 100644 index 000000000..3e2a7c575 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonTypes.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX + +#include <config_lgpl.h> +#include <ne_session.h> +#include <ne_utils.h> +#include <ne_basic.h> +#include <ne_props.h> + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif +#include <ne_locks.h> +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +typedef ne_session HttpSession; +typedef ne_status HttpStatus; + +typedef ne_propname NeonPropName; +typedef ne_prop_result_set NeonPropFindResultSet; + +typedef struct ne_lock NeonLock; + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonUri.cxx b/ucb/source/ucp/webdav-neon/NeonUri.cxx new file mode 100644 index 000000000..c1c263421 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonUri.cxx @@ -0,0 +1,291 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/config.h> + +#include <rtl/uri.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <ne_alloc.h> +#include "NeonUri.hxx" +#include "DAVException.hxx" + +#include "../inc/urihelper.hxx" + +using namespace webdav_ucp; + +// FIXME: not sure whether initializing a ne_uri statically is supposed to work +// the string fields of ne_uri are char*, not const char* + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wwrite-strings" +#endif + +namespace { + +const ne_uri g_sUriDefaultsHTTP = { const_cast<char *>("http"), + nullptr, + nullptr, + DEFAULT_HTTP_PORT, + nullptr, + nullptr, + nullptr }; +const ne_uri g_sUriDefaultsHTTPS = { const_cast<char *>("https"), + nullptr, + nullptr, + DEFAULT_HTTPS_PORT, + nullptr, + nullptr, + nullptr }; +const ne_uri g_sUriDefaultsFTP = { const_cast<char *>("ftp"), + nullptr, + nullptr, + DEFAULT_FTP_PORT, + nullptr, + nullptr, + nullptr }; +} // namespace + +NeonUri::NeonUri( const ne_uri * inUri ) +{ + if ( inUri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + char * uri = ne_uri_unparse( inUri ); + + if ( uri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + init( OString( uri ), inUri ); + ne_free( uri ); + + calculateURI(); +} + +NeonUri::NeonUri( const OUString & inUri ) +{ + if ( inUri.isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + // #i77023# + OUString aEscapedUri( ucb_impl::urihelper::encodeURI( inUri ) ); + + OString theInputUri( + aEscapedUri.getStr(), aEscapedUri.getLength(), RTL_TEXTENCODING_UTF8 ); + + ne_uri theUri; + if ( ne_uri_parse( theInputUri.getStr(), &theUri ) != 0 ) + { + ne_uri_free( &theUri ); + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + init( theInputUri, &theUri ); + ne_uri_free( &theUri ); + + calculateURI(); +} + +void NeonUri::init( const OString & rUri, const ne_uri * pUri ) +{ + // Complete URI. + const ne_uri * pUriDefs + = rUri.matchIgnoreAsciiCase( "ftp:" ) ? + &g_sUriDefaultsFTP : + rUri.matchIgnoreAsciiCase( "https:" ) ? + &g_sUriDefaultsHTTPS : + &g_sUriDefaultsHTTP; + + mScheme = OStringToOUString( + pUri->scheme ? pUri->scheme : pUriDefs->scheme, + RTL_TEXTENCODING_UTF8 ); + mUserInfo = OStringToOUString( + pUri->userinfo ? pUri->userinfo : pUriDefs->userinfo, + RTL_TEXTENCODING_UTF8 ); + mHostName = OStringToOUString( + pUri->host ? pUri->host : pUriDefs->host, + RTL_TEXTENCODING_UTF8 ); + mPort = pUri->port > 0 ? pUri->port : pUriDefs->port; + mPath = OStringToOUString( + pUri->path ? pUri->path : pUriDefs->path, + RTL_TEXTENCODING_UTF8 ); + + if ( pUri->query ) + { + mPath += "?" + OStringToOUString( pUri->query, RTL_TEXTENCODING_UTF8 ); + } + + if ( pUri->fragment ) + { + mPath += "#" + OStringToOUString( pUri->fragment, RTL_TEXTENCODING_UTF8 ); + } +} + +void NeonUri::calculateURI () +{ + OUStringBuffer aBuf( 256 ); + aBuf.append( mScheme ); + aBuf.append( "://" ); + if ( !mUserInfo.isEmpty() ) + { + //TODO! differentiate between empty and missing userinfo + aBuf.append( mUserInfo ); + aBuf.append( "@" ); + } + // Is host a numeric IPv6 address? + if ( ( mHostName.indexOf( ':' ) != -1 ) && + ( mHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( mHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( mHostName ); + } + + // append port, but only, if not default port. + bool bAppendPort = true; + switch ( mPort ) + { + case DEFAULT_HTTP_PORT: + bAppendPort = mScheme != "http"; + break; + + case DEFAULT_HTTPS_PORT: + bAppendPort = mScheme != "https"; + break; + + case DEFAULT_FTP_PORT: + bAppendPort = mScheme != "ftp"; + break; + } + if ( bAppendPort ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( mPort ) ); + } + aBuf.append( mPath ); + + mURI = aBuf.makeStringAndClear(); +} + +OUString NeonUri::GetPathBaseName () const +{ + sal_Int32 nPos = mPath.lastIndexOf ('/'); + sal_Int32 nTrail = 0; + if (nPos == mPath.getLength () - 1) + { + // Trailing slash found. Skip. + nTrail = 1; + nPos = mPath.lastIndexOf ('/', nPos); + } + if (nPos != -1) + { + OUString aTemp( + mPath.copy (nPos + 1, mPath.getLength () - nPos - 1 - nTrail) ); + + // query, fragment present? + nPos = aTemp.indexOf( '?' ); + if ( nPos == -1 ) + nPos = aTemp.indexOf( '#' ); + + if ( nPos != -1 ) + aTemp = aTemp.copy( 0, nPos ); + + return aTemp; + } + else + return "/"; +} + +bool NeonUri::operator== ( const NeonUri & rOther ) const +{ + return ( mURI == rOther.mURI ); +} + +OUString NeonUri::GetPathBaseNameUnescaped () const +{ + return unescape( GetPathBaseName() ); +} + +void NeonUri::AppendPath (const OUString& rPath) +{ + if (mPath.lastIndexOf ('/') != mPath.getLength () - 1) + mPath += "/"; + + mPath += rPath; + calculateURI (); +}; + +// static +OUString NeonUri::escapeSegment( const OUString& segment ) +{ + return rtl::Uri::encode( segment, + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString NeonUri::unescape( const OUString& segment ) +{ + return rtl::Uri::decode( segment, + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString NeonUri::makeConnectionEndPointString( + const OUString & rHostName, int nPort ) +{ + OUStringBuffer aBuf; + + // Is host a numeric IPv6 address? + if ( ( rHostName.indexOf( ':' ) != -1 ) && + ( rHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( rHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( rHostName ); + } + + if ( ( nPort != DEFAULT_HTTP_PORT ) && ( nPort != DEFAULT_HTTPS_PORT ) ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( nPort ) ); + } + return aBuf.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/NeonUri.hxx b/ucb/source/ucp/webdav-neon/NeonUri.hxx new file mode 100644 index 000000000..fae505e50 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/NeonUri.hxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX + +#include <config_lgpl.h> +#include <ne_uri.h> +#include <rtl/ustring.hxx> +#include "DAVException.hxx" + +namespace webdav_ucp +{ + +#define DEFAULT_HTTP_PORT 80 +#define DEFAULT_HTTPS_PORT 443 +#define DEFAULT_FTP_PORT 21 + +// A URI implementation for use with the neon/expat library +class NeonUri +{ + private: + OUString mURI; + OUString mScheme; + OUString mUserInfo; + OUString mHostName; + sal_Int32 mPort; + OUString mPath; + + void init( const OString & rUri, const ne_uri * pUri ); + void calculateURI (); + + public: + /// @throws DAVException + explicit NeonUri( const OUString & inUri ); + /// @throws DAVException + explicit NeonUri( const ne_uri * inUri ); + + bool operator== ( const NeonUri & rOther ) const; + bool operator!= ( const NeonUri & rOther ) const + { return !operator==( rOther ); } + + const OUString & GetURI() const + { return mURI; }; + const OUString & GetScheme() const + { return mScheme; }; + const OUString & GetUserInfo() const + { return mUserInfo; }; + const OUString & GetHost() const + { return mHostName; }; + sal_Int32 GetPort() const + { return mPort; }; + const OUString & GetPath() const + { return mPath; }; + + OUString GetPathBaseName() const; + + OUString GetPathBaseNameUnescaped() const; + + void SetScheme (const OUString& scheme) + { mScheme = scheme; calculateURI (); }; + + void AppendPath (const OUString& rPath); + + static OUString escapeSegment( const OUString& segment ); + static OUString unescape( const OUString& string ); + + // "host:port", omit ":port" for port 80 and 443 + static OUString makeConnectionEndPointString( + const OUString & rHostName, + int nPort ); +}; + +} // namespace webdav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_NEONURI_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/PropertyMap.hxx b/ucb/source/ucp/webdav-neon/PropertyMap.hxx new file mode 100644 index 000000000..159651085 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/PropertyMap.hxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPERTYMAP_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPERTYMAP_HXX + +#include <config_lgpl.h> +#include <com/sun/star/beans/Property.hpp> +#include <unordered_set> + +namespace webdav_ucp { + + +struct equalPropertyName +{ + bool operator()( const css::beans::Property & p1, + const css::beans::Property & p2 ) const + { + return p1.Name == p2.Name; + } +}; + +struct hashPropertyName +{ + size_t operator()( const css::beans::Property & p ) const + { + return p.Name.hashCode(); + } +}; + +typedef std::unordered_set +< + css::beans::Property, + hashPropertyName, + equalPropertyName +> +PropertyMap; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/PropfindCache.cxx b/ucb/source/ucp/webdav-neon/PropfindCache.cxx new file mode 100644 index 000000000..a54003661 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/PropfindCache.cxx @@ -0,0 +1,89 @@ +/* -*- 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/. + */ + +#include <osl/time.h> +#include "PropfindCache.hxx" + +namespace webdav_ucp +{ + + // PropertyNames implementation + + PropertyNames::PropertyNames() : + m_nStaleTime( 0 ), + m_sURL(), + m_aPropertiesNames() + { + } + + PropertyNames::PropertyNames( const OUString& rURL ) : + m_nStaleTime( 0 ), + m_sURL( rURL ), + m_aPropertiesNames() + { + } + + //PropertyNamesCache implementation + + PropertyNamesCache::PropertyNamesCache() + { + } + + PropertyNamesCache::~PropertyNamesCache() + { + } + + bool PropertyNamesCache::getCachedPropertyNames( const OUString& rURL, PropertyNames& rCacheElement ) + { + // search the URL in the static map + osl::MutexGuard aGuard( m_aMutex ); + PropNameCache::const_iterator it = m_aTheCache.find( rURL ); + if ( it == m_aTheCache.end() ) + return false; + else + { + // check if the element is stale, before restoring + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( (*it).second.getStaleTime() < t1.Seconds ) + { + // if stale, remove from cache, do not restore + m_aTheCache.erase( it ); + return false; + // return false instead + } + rCacheElement = (*it).second; + return true; + } + } + + void PropertyNamesCache::removeCachedPropertyNames( const OUString& rURL ) + { + osl::MutexGuard aGuard( m_aMutex ); + PropNameCache::const_iterator it = m_aTheCache.find( rURL ); + if ( it != m_aTheCache.end() ) + { + m_aTheCache.erase( it ); + } + } + + void PropertyNamesCache::addCachePropertyNames( PropertyNames& rCacheElement ) + { + osl::MutexGuard aGuard( m_aMutex ); + OUString aURL( rCacheElement.getURL() ); + TimeValue t1; + osl_getSystemTime( &t1 ); + rCacheElement.setStaleTime( t1.Seconds + 10 ); + + m_aTheCache[ aURL ] = rCacheElement; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-neon/PropfindCache.hxx b/ucb/source/ucp/webdav-neon/PropfindCache.hxx new file mode 100644 index 000000000..bcbdff93e --- /dev/null +++ b/ucb/source/ucp/webdav-neon/PropfindCache.hxx @@ -0,0 +1,80 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_PROPFINDCACHE_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <osl/mutex.hxx> +#include <map> +#include <vector> + +#include "DAVResource.hxx" + +namespace webdav_ucp +{ + // A property names cache mechanism, URL driven. + // It is used to cache the property names received + // from the WebDAV server, to minimize the need of + // net transactions (e.g. PROPFIND). + // The cache lifetime should be short + // just to remove the annoying slowness when + // typing text or moving cursor around when the + // net link is slow. + + // Define the properties cache element + class PropertyNames final + { + /// target time when this element becomes stale + sal_uInt32 m_nStaleTime; + OUString m_sURL; + // the property name list received from WebDAV server + std::vector< DAVResourceInfo > m_aPropertiesNames; + + public: + PropertyNames(); + explicit PropertyNames( const OUString& rURL ); + + sal_uInt32 getStaleTime() const { return m_nStaleTime; }; + void setStaleTime( const sal_uInt32 nStaleTime ) { m_nStaleTime = nStaleTime; }; + + OUString& getURL() { return m_sURL; }; + + const std::vector< DAVResourceInfo >& getPropertiesNames() const { return m_aPropertiesNames; }; + void setPropertiesNames( const std::vector< DAVResourceInfo >& aPropertiesNames ) { m_aPropertiesNames = aPropertiesNames; }; + }; + + // Define the PropertyNames cache + // TODO: the OUString key element in std::map needs to be changed with a URI representation + // with a specific compare (std::less) implementation, this last one implementing + // as suggested in <https://tools.ietf.org/html/rfc3986#section-6>. + // To find by URI and not by string equality. + typedef std::map< OUString, PropertyNames, + std::less< OUString > >PropNameCache; + + class PropertyNamesCache final + { + PropNameCache m_aTheCache; + osl::Mutex m_aMutex; + + public: + PropertyNamesCache(); + ~PropertyNamesCache(); + + bool getCachedPropertyNames( const OUString& URL, PropertyNames& rCacheElement ); + void removeCachedPropertyNames( const OUString& URL ); + void addCachePropertyNames( PropertyNames& rCacheElement ); + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx new file mode 100644 index 000000000..6a06f0dcb --- /dev/null +++ b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.cxx @@ -0,0 +1,509 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <config_lgpl.h> +#include <string.h> +#include <ne_xml.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include "UCBDeadPropertyValue.hxx" +#include <memory> + +using namespace webdav_ucp; +using namespace com::sun::star; + +namespace { + +struct UCBDeadPropertyValueParseContext +{ + std::unique_ptr<OUString> pType; + std::unique_ptr<OUString> pValue; + + UCBDeadPropertyValueParseContext() {} +}; + +} + +static const char aTypeString[] = "string"; +static const char aTypeLong[] = "long"; +static const char aTypeShort[] = "short"; +static const char aTypeBoolean[] = "boolean"; +static const char aTypeChar[] = "char"; +static const char aTypeByte[] = "byte"; +static const char aTypeHyper[] = "hyper"; +static const char aTypeFloat[] = "float"; +static const char aTypeDouble[] = "double"; + +static const char aXMLPre[] = "<ucbprop><type>"; +static const char aXMLMid[] = "</type><value>"; +static const char aXMLEnd[] = "</value></ucbprop>"; + + +#define STATE_TOP (1) + +#define STATE_UCBPROP (STATE_TOP) +#define STATE_TYPE (STATE_TOP + 1) +#define STATE_VALUE (STATE_TOP + 2) + + +extern "C" { + +static int UCBDeadPropertyValue_startelement_callback( + void *, + int parent, + const char * /*nspace*/, + const char *name, + const char ** ) +{ + if ( name != nullptr ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "ucbprop" ) == 0 ) + return STATE_UCBPROP; + break; + + case STATE_UCBPROP: + if ( strcmp( name, "type" ) == 0 ) + return STATE_TYPE; + else if ( strcmp( name, "value" ) == 0 ) + return STATE_VALUE; + break; + } + } + return NE_XML_DECLINE; +} + + +static int UCBDeadPropertyValue_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + assert( !pCtx->pType && + "UCBDeadPropertyValue_endelement_callback - " + "Type already set!" ); + pCtx->pType.reset( new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ) ); + break; + + case STATE_VALUE: + assert( !pCtx->pValue && + "UCBDeadPropertyValue_endelement_callback - " + "Value already set!" ); + pCtx->pValue.reset( new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ) ); + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +static int UCBDeadPropertyValue_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + if ( !pCtx->pType ) + return 1; // abort + break; + + case STATE_VALUE: + if ( !pCtx->pValue ) + return 1; // abort + break; + + case STATE_UCBPROP: + if ( !pCtx->pType || ! pCtx->pValue ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + +} + +static OUString encodeValue( const OUString & rValue ) +{ + // Note: I do not use the usual & + < + > encoding, because + // I want to prevent any XML parser from trying to 'understand' + // the value. This caused problems: + + // Example: + // - Unencoded property value: x<z + // PROPPATCH: + // - Encoded property value: x<z + // - UCBDeadPropertyValue::toXML result: + // <ucbprop><type>string</type><value>x<z</value></ucbprop> + // PROPFIND: + // - parser replaces < by > ==> error (not well formed) + + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nCount = rValue.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const sal_Unicode c = pValue[ n ]; + + if ( '%' == c ) + aResult.append( "%per;" ); + else if ( '<' == c ) + aResult.append( "%lt;" ); + else if ( '>' == c ) + aResult.append( "%gt;" ); + else + aResult.append( c ); + } + return aResult.makeStringAndClear(); +} + + +static OUString decodeValue( const OUString & rValue ) +{ + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nPos = 0; + sal_Int32 nEnd = rValue.getLength(); + + while ( nPos < nEnd ) + { + sal_Unicode c = pValue[ nPos ]; + + if ( '%' == c ) + { + nPos++; + + if ( nPos == nEnd ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + c = pValue[ nPos ]; + + if ( 'p' == c ) + { + // %per; + + if ( nPos > nEnd - 4 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 'e' == pValue[ nPos + 1 ] ) + && + ( 'r' == pValue[ nPos + 2 ] ) + && + ( ';' == pValue[ nPos + 3 ] ) ) + { + aResult.append( '%' ); + nPos += 3; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else if ( 'l' == c ) + { + // %lt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '<' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else if ( 'g' == c ) + { + // %gt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '>' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", "decodeValue() - syntax error!" ); + return OUString(); + } + } + else + aResult.append( c ); + + nPos++; + } + + return aResult.makeStringAndClear(); +} + + +// static +bool UCBDeadPropertyValue::supportsType( const uno::Type & rType ) +{ + return rType == cppu::UnoType<OUString>::get() + || rType == cppu::UnoType<sal_Int32>::get() + || rType == cppu::UnoType<sal_Int16>::get() + || rType == cppu::UnoType<bool>::get() + || rType == cppu::UnoType<cppu::UnoCharType>::get() + || rType == cppu::UnoType<sal_Int8>::get() + || rType == cppu::UnoType<sal_Int64>::get() + || rType == cppu::UnoType<float>::get() + || rType == cppu::UnoType<double>::get(); +} + + +// static +bool UCBDeadPropertyValue::createFromXML( const OString & rInData, + uno::Any & rOutData ) +{ + bool success = false; + + ne_xml_parser * parser = ne_xml_create(); + if ( parser ) + { + UCBDeadPropertyValueParseContext aCtx; + ne_xml_push_handler( parser, + UCBDeadPropertyValue_startelement_callback, + UCBDeadPropertyValue_chardata_callback, + UCBDeadPropertyValue_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, rInData.getStr(), rInData.getLength() ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( success ) + { + if ( aCtx.pType && aCtx.pValue ) + { + // Decode aCtx.pValue! It may contain XML reserved chars. + OUString aStringValue = decodeValue( *aCtx.pValue ); + if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeString ) ) + { + rOutData <<= aStringValue; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeLong ) ) + { + rOutData <<= aStringValue.toInt32(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeShort ) ) + { + rOutData <<= sal_Int16( aStringValue.toInt32() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeBoolean ) ) + { + if ( aStringValue.equalsIgnoreAsciiCase("true") ) + rOutData <<= true; + else + rOutData <<= false; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeChar ) ) + { + rOutData <<= aStringValue.toChar(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeByte ) ) + { + rOutData <<= sal_Int8( aStringValue.toChar() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeHyper ) ) + { + rOutData <<= aStringValue.toInt64(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeFloat ) ) + { + rOutData <<= aStringValue.toFloat(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeDouble ) ) + { + rOutData <<= aStringValue.toDouble(); + } + else + { + SAL_WARN( "ucb.ucp.webdav", "createFromXML() - " + "Unsupported property type!" ); + success = false; + } + } + else + success = false; + } + } + + return success; +} + + +// static +bool UCBDeadPropertyValue::toXML( const uno::Any & rInData, + OUString & rOutData ) +{ + // <ucbprop><type>the_type</type><value>the_value</value></ucbprop> + + // Check property type. Extract type and value as string. + + const uno::Type& rType = rInData.getValueType(); + OUString aStringValue; + OUString aStringType; + + if ( rType == cppu::UnoType<OUString>::get() ) + { + // string + rInData >>= aStringValue; + aStringType = aTypeString; + } + else if ( rType == cppu::UnoType<sal_Int32>::get() ) + { + // long + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeLong; + } + else if ( rType == cppu::UnoType<sal_Int16>::get() ) + { + // short + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeShort; + } + else if ( rType == cppu::UnoType<bool>::get() ) + { + // boolean + bool bValue = false; + rInData >>= bValue; + aStringValue = OUString::boolean( bValue ); + aStringType = aTypeBoolean; + } + else if ( rType == cppu::UnoType<cppu::UnoCharType>::get() ) + { + // char + sal_Unicode cValue = 0; + rInData >>= cValue; + aStringValue = OUString( cValue ); + aStringType = aTypeChar; + } + else if ( rType == cppu::UnoType<sal_Int8>::get() ) + { + // byte + sal_Int8 nValue = 0; + rInData >>= nValue; + aStringValue = OUString( sal_Unicode( nValue ) ); + aStringType = aTypeByte; + } + else if ( rType == cppu::UnoType<sal_Int64>::get() ) + { + // hyper + sal_Int64 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeHyper; + } + else if ( rType == cppu::UnoType<float>::get() ) + { + // float + float nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeFloat; + } + else if ( rType == cppu::UnoType<double>::get() ) + { + // double + double nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeDouble; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "toXML() - unsupported property type!" ); + return false; + } + + // Encode value! It must not contain XML reserved chars! + aStringValue = encodeValue( aStringValue ); + + rOutData = aXMLPre; + rOutData += aStringType; + rOutData += aXMLMid; + rOutData += aStringValue; + rOutData += aXMLEnd; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx new file mode 100644 index 000000000..b93c142d5 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/UCBDeadPropertyValue.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace webdav_ucp +{ + +class UCBDeadPropertyValue +{ +public: + static bool supportsType( const css::uno::Type & rType ); + + static bool createFromXML( const OString & rInData, + css::uno::Any & rOutData ); + static bool toXML( const css::uno::Any & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_UCBDEADPROPERTYVALUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/ucpdav1.component b/ucb/source/ucp/webdav-neon/ucpdav1.component new file mode 100644 index 000000000..5bebe2535 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/ucpdav1.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!--********************************************************************** +* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* Copyright 2000, 2010 Oracle and/or its affiliates. +* +* OpenOffice.org - a multi-platform office productivity suite +* +* This file is part of OpenOffice.org. +* +* OpenOffice.org is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* only, as published by the Free Software Foundation. +* +* OpenOffice.org is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License version 3 for more details +* (a copy is included in the LICENSE file that accompanied this code). +* +* You should have received a copy of the GNU Lesser General Public License +* version 3 along with OpenOffice.org. If not, see +* <http://www.openoffice.org/license.html> +* for a copy of the LGPLv3 License. +* +**********************************************************************--> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + prefix="ucpdav1" xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.WebDAVContentProvider"> + <service name="com.sun.star.ucb.WebDAVContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav-neon/webdavcontent.cxx b/ucb/source/ucp/webdav-neon/webdavcontent.cxx new file mode 100644 index 000000000..931195e35 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontent.cxx @@ -0,0 +1,4217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <memory> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <officecfg/Inet.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleinteractionrequest.hxx> +#include <ucbhelper/cancelcommandexecution.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/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 <ucbhelper/macros.hxx> + +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "webdavresultset.hxx" +#include "ContentProperties.hxx" +#include "NeonUri.hxx" +#include "UCBDeadPropertyValue.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + +namespace +{ + // implement a GET to substitute HEAD, when HEAD not available + 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( OUString( "Range" ), // see <https://tools.ietf.org/html/rfc7233#section-3.1> + OUString( "bytes=0-0" ) ); + + 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 + // <https://tools.ietf.org/html/rfc7233#section-2> + // needs some explanation for this + // probably some changes? + // 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() ) ); + + NeonUri 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() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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 "com.sun.star.comp.ucb.WebDAVContent"; +} + + +// virtual +uno::Sequence< OUString > SAL_CALL Content::getSupportedServiceNames() +{ + return { WEBDAV_CONTENT_SERVICE_NAME }; +} + + +// 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.Name << ", 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.hasElements() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + transfer( transferArgs, Environment ); + } + else if ( aCommand.Name == "post" ) + { + + // post + + + ucb::PostCommandArgument2 aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aArg ); + } + else if ( aCommand.Name == "addProperty" ) + { + ucb::PropertyCommandArgument aPropArg; + if ( !( aCommand.Argument >>= aPropArg )) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch ( const beans::IllegalTypeException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + catch ( const lang::IllegalArgumentException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else if ( aCommand.Name == "removeProperty" ) + { + OUString sPropName; + if ( !( aCommand.Argument >>= sPropName ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch( const beans::NotRemoveableException &e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + aCommand.Name, + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ucb::PropertyCommandArgument& aCmdArg, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ +// if ( m_bTransient ) +// @@@ ??? + + if ( aCmdArg.Property.Name.isEmpty() ) + throw lang::IllegalArgumentException( + "\"addProperty\" with empty Property.Name", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + + // Check property type. + if ( !UCBDeadPropertyValue::supportsType( aCmdArg.Property.Type ) ) + { + throw beans::IllegalTypeException( + "\"addProperty\" unsupported Property.Type", + static_cast< cppu::OWeakObject * >( this ) ); + } + + if ( aCmdArg.DefaultValue.hasValue() + && aCmdArg.DefaultValue.getValueType() != aCmdArg.Property.Type ) + { + throw beans::IllegalTypeException( + "\"addProperty\" DefaultValue does not match Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + } + + + // 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( + aCmdArg.Property.Name, aSpecialName ); + + // Note: This requires network access! + if ( getPropertySetInfo( xEnv, false /* don't cache data */ ) + ->hasPropertyByName( + bIsSpecial ? aSpecialName : aCmdArg.Property.Name ) ) + { + // Property does already exist. + throw beans::PropertyExistException(); + } + + + // Add a new dynamic property. + + + ProppatchValue aValue( + PROPSET, aCmdArg.Property.Name, aCmdArg.DefaultValue ); + + 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( + static_cast< cppu::OWeakObject * >( this ), + bIsSpecial ? aSpecialName : aCmdArg.Property.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 : aCmdArg.Property.Name, + aCmdArg.Property.Attributes, aCmdArg.DefaultValue ); + } + else + { + if ( shouldAccessNetworkAfterException( e ) ) + { + try + { + ResourceType eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw lang::IllegalArgumentException(); + + case FTP: + case NON_DAV: + // Store property locally. + ContentImplHelper::addProperty( + bIsSpecial ? aSpecialName : aCmdArg.Property.Name, + aCmdArg.Property.Attributes, aCmdArg.DefaultValue ); + 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 ) +{ + + // 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( + static_cast< cppu::OWeakObject * >( this ), + 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 + { + ResourceType eType = getResourceType( xEnv ); + switch ( eType ) + { + case UNKNOWN: + case DAV: + throw beans::UnknownPropertyException(Name); + + case FTP: + 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( + "Title", aProp ); + + uno::Sequence< beans::Property > aDocProps( 1 ); + aDocProps.getArray()[ 0 ] = aProp; + aSeq.getArray()[ 0 ].Properties = 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 ] = aProp; + aSeq.getArray()[ 1 ].Properties = 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.isEmpty() ) + 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(); + + assert( !aURL.isEmpty() && "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 ::webdav_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 ); + + if ( rProperties.hasElements() ) + { + uno::Reference< beans::XPropertySet > xAdditionalPropSet; + bool bTriedToGetAdditionalPropSet = false; + + for ( const beans::Property& rProp : rProperties ) + { + // 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 ) + { + 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.get() ); +} + +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, "405 Not Implemented", 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< ucb::XContentIdentifier > xIdentifier; + rtl::Reference< ::ucbhelper::ContentProviderImplHelper > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aUnescapedTitle = NeonUri::unescape( m_aEscapedTitle ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + 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)); + } + } + + if ( !m_bTransient && !bHasAll ) + { + + // Obtain values from server... + + + // First, identify whether resource is DAV or not + bool bNetworkAccessAllowed = true; + 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; + + uno::Sequence< beans::Property > aProperties( + rProperties.getLength() ); + + if ( !m_aFailedPropNames.empty() ) + { + sal_Int32 nProps = rProperties.getLength(); + std::copy(rProperties.begin(), rProperties.end(), aProperties.begin()); + + aProperties.realloc( nProps ); + } + else + { + aProperties = rProperties; + } + + if ( aProperties.hasElements() ) + 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 + for ( const auto& rProp : resources[0].properties ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if( rProp.Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" << aPropValue ); + else if( rProp.Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( rProp.Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getPropertyValues) - returned property: " << rProp.Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav"," scope: " + << ( aSupportedLocks[ n ].Scope == css::ucb::LockScope_SHARED ? "shared" : "exclusive" ) + << ", type: " + << ( aSupportedLocks[ n ].Type != css::ucb::LockType_WRITE ? "" : "write" ) ); + } + } + } + } +#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 ) ) + && !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. + NeonUri 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. + + if ( eType == DAV ) + { + if (!xProps) + xProps.reset(new ContentProperties(aUnescapedTitle)); + else + xProps->addProperty("Title", uno::makeAny(aUnescapedTitle), true); + } + else + { + if (!xProps) + xProps.reset( new ContentProperties( aUnescapedTitle, false ) ); + else + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + + xProps->addProperty( + "IsFolder", + uno::makeAny( false ), + true ); + xProps->addProperty( + "IsDocument", + uno::makeAny( true ), + 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 ( const auto& rProp : aMissingProps ) + { + // For the time being only a couple of properties need to be added + if ( rProp == "DateModified" || rProp == "DateCreated" ) + { + util::DateTime aDate; + xProps->addProperty( + rProp, + uno::makeAny( aDate ), + true ); + } + // If WebDAV didn't return the resource type, assume default + // This happens e.g. for lists exported by SharePoint + else if ( rProp == "IsFolder" ) + { + xProps->addProperty( + rProp, + uno::makeAny( false ), + true ); + } + else if ( rProp == "IsDocument" ) + { + xProps->addProperty( + rProp, + uno::makeAny( true ), + true ); + } + } + } + + for ( const auto& rProperty : rProperties ) + { + const OUString rName = rProperty.Name; + if ( rName == "BaseURI" ) + { + // Add BaseURI property, if requested. + xProps->addProperty( + "BaseURI", + uno::makeAny( getBaseURI( xResAccess ) ), + true ); + } + else if ( rName == "CreatableContentsInfo" ) + { + // Add CreatableContentsInfo property, if requested. + bool bFolder = false; + xProps->getValue( + "IsFolder" ) + >>= bFolder; + xProps->addProperty( + "CreatableContentsInfo", + uno::makeAny( bFolder + ? queryCreatableContentsInfo() + : uno::Sequence< ucb::ContentInfo >() ), + true ); + } + } + + uno::Reference< sdbc::XRow > xResultRow + = getPropertyValues( m_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 = NeonUri::escapeSegment( 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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + aEvent.Further = false; + // aEvent.PropertyName = + aEvent.PropertyHandle = -1; + // aEvent.OldValue = + // aEvent.NewValue = + + std::vector< ProppatchValue > aProppatchValues; + + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + + // Mandatory props. + + + if ( rName == "ContentType" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "Title" ) + { + OUString aNewValue; + if ( rValue.Value >>= aNewValue ) + { + // No empty titles! + if ( !aNewValue.isEmpty() ) + { + try + { + NeonUri 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 & ) + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Invalid content identifier!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + + // Optional props. + + + OUString aSpecialName; + bool bIsSpecial = DAVProperties::isUCBSpecialProperty( + rName, aSpecialName ); + + if ( !xInfo.is() ) + xInfo = getPropertySetInfo( xEnv, + false /* don't cache data */ ); + + if ( !xInfo->hasPropertyByName( + bIsSpecial ? aSpecialName : rName ) ) + { + // Check, whether property exists. Skip otherwise. + // PROPPATCH::set would add the property automatically, which + // is not allowed for "setPropertyValues" command! + aRet[ n ] <<= beans::UnknownPropertyException( + "Property is unknown!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( rName == "Size" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateCreated" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateModified" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "MediaType" ) + { + // Read-only property! + // (but could be writable, if 'getcontenttype' would be) + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + if ( rName == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + if ( getResourceType( xEnv, xResAccess ) == DAV ) + { + // Property value will be set on server. + ProppatchValue aValue( PROPSET, rName, rValue.Value ); + aProppatchValues.push_back( aValue ); + } + else + { + // Property value will be stored in local property store. + if ( !bTriedToGetAdditionalPropSet && + !xAdditionalPropSet.is() ) + { + xAdditionalPropSet + = getAdditionalPropertySet( false ); + bTriedToGetAdditionalPropSet = true; + } + + if ( xAdditionalPropSet.is() ) + { + try + { + uno::Any aOldValue + = xAdditionalPropSet->getPropertyValue( rName ); + if ( aOldValue != rValue.Value ) + { + xAdditionalPropSet->setPropertyValue( + rName, rValue.Value ); + + aEvent.PropertyName = rName; + aEvent.OldValue = aOldValue; + aEvent.NewValue = rValue.Value; + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + } + catch ( beans::UnknownPropertyException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + } + } // for + + if ( !bTransient && !aProppatchValues.empty() ) + { + try + { + // clean cached value of PROPFIND property names + // PROPPATCH can change them + removeCachedPropertyNames( xResAccess->getURL() ); + // Set property values at server. + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + xResAccess->PROPPATCH( aProppatchValues, xEnv ); + + for ( const auto& rProppatchValue : aProppatchValues ) + { + aEvent.PropertyName = rProppatchValue.name; + aEvent.OldValue = uno::Any(); // @@@ too expensive to obtain! + aEvent.NewValue = rProppatchValue.value; + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + } + catch ( DAVException const & e ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::setPropertyValues - PROPPATCH failed!" ); + cancelCommandExecution( e, xEnv ); + // unreachable + } + } + + if ( bExchange ) + { + // Assemble new content identifier... + + OUString aNewURL = getParentURL(); + if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) ) + aNewURL += "/"; + + aNewURL += NeonUri::escapeSegment( aNewTitle ); + + uno::Reference< ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + + NeonUri sourceURI( xIdentifier->getContentIdentifier() ); + NeonUri targetURI( xNewId->getContentIdentifier() ); + + try + { + targetURI.SetScheme( sourceURI.GetScheme() ); + + // clean cached value of PROPFIND property names + removeCachedPropertyNames( sourceURI.GetURI() ); + removeCachedPropertyNames( targetURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + xResAccess->MOVE( + sourceURI.GetPath(), targetURI.GetURI(), false, xEnv ); + + // @@@ Should check for resources that could not be moved + // (due to source access or target overwrite) and send + // this information through the interaction handler. + + // @@@ Existing content should be checked to see if it needs + // to be deleted at the source + + // @@@ Existing content should be checked to see if it has + // been overwritten at the target + + if ( exchangeIdentity( xNewId ) ) + { + xResAccess->setURL( aNewURL ); + +// DAV resources store all additional props on server! +// // Adapt Additional Core Properties. +// renameAdditionalPropertySet( xOldId->getContentIdentifier(), +// xNewId->getContentIdentifier(), +// sal_True ); + } + else + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + catch ( DAVException const & e ) + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] = MapDAVException( e, true ); + } + } + + if ( !aNewTitle.isEmpty() ) + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aEvent.PropertyName = "Title"; + aEvent.OldValue <<= aOldTitle; + aEvent.NewValue <<= aNewTitle; + + m_aEscapedTitle = NeonUri::escapeSegment( aNewTitle ); + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + + if ( nChanged > 0 ) + { + aChanges.realloc( nChanged ); + notifyPropertiesChange( aChanges ); + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + + return aRet; +} + + +uno::Any Content::open( + const ucb::OpenCommandArgument3 & rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + uno::Any aRet; + + bool bOpenFolder = ( ( rArg.Mode == ucb::OpenMode::ALL ) || + ( rArg.Mode == ucb::OpenMode::FOLDERS ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENTS ) ); + if ( bOpenFolder ) + { + if ( isFolder( xEnv ) ) + { + // Open collection. + + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet( m_xContext, this, rArg, xEnv ); + aRet <<= xSet; + } + else + { + // Error: Not a folder! + + OUStringBuffer aMsg; + if ( getResourceType( xEnv ) == FTP ) + { + aMsg.append( "FTP over HTTP proxy: resource cannot " + "be opened as folder! Wrong Open Mode!" ); + } + else + { + aMsg.append( "Non-folder resource cannot be " + "opened as folder! Wrong Open Mode!" ); + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + lang::IllegalArgumentException( + aMsg.makeStringAndClear(), + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + } + + if ( rArg.Sink.is() ) + { + // Open document. + + if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) ) + { + // Currently(?) unsupported. + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + sal_Int16( rArg.Mode ) ) ), + xEnv ); + // Unreachable + } + + uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY ); + if ( xOut.is() ) + { + // PUSH: write data + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::MutexGuard aGuard( m_aMutex ); + + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + xResAccess->setFlags( rArg.OpeningFlags ); + DAVResource aResource; + std::vector< OUString > aHeaders; + + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->GET( xOut, aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if (!m_xCachedProps) + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( aResource ) ) ); + else + m_xCachedProps->addProperties( ContentProperties( aResource ) ); + + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + uno::Reference< io::XActiveDataSink > xDataSink( rArg.Sink, uno::UNO_QUERY ); + if ( xDataSink.is() ) + { + // PULL: wait for client read + OUString aTargetURL = m_xIdentifier->getContentIdentifier(); + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + xResAccess->setFlags( rArg.OpeningFlags ); + + // fill inputstream sync; return if all data present + DAVResource aResource; + std::vector< OUString > aHeaders; + + aTargetURL = xResAccess->getURL(); + removeCachedPropertyNames( aTargetURL ); + // check if the resource was present on the server + // first update it, if necessary + // if the open is called directly, without the default open sequence, + // e.g. the one used when opening a file looking for properties + // first this call will have no effect, since OPTIONS would have already been called + // as a consequence of getPropertyValues() + DAVOptions aDAVOptions; + getResourceOptions( xEnv, aDAVOptions, xResAccess ); + + 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() ); + } + uno::Reference< io::XInputStream > xIn + = xResAccess->GET( aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if (!m_xCachedProps) + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( aResource ) ) ); + else + m_xCachedProps->addProperties( + aResource.properties ); + + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + + xDataSink->setInputStream( xIn ); + } + catch ( DAVException const & e ) + { + //TODO cache the http error if not yet cached + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + // Note: aOpenCommand.Sink may contain an XStream + // implementation. Support for this type of + // sink is optional... + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArg.Sink ) ), + xEnv ); + // Unreachable + } + } + } + + return aRet; +} + +void Content::post( + const ucb::PostCommandArgument2 & rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + uno::Reference< io::XActiveDataSink > xSink( rArg.Sink, uno::UNO_QUERY ); + if ( xSink.is() ) + { + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + removeCachedPropertyNames( xResAccess->getURL() ); + uno::Reference< io::XInputStream > xResult + = xResAccess->POST( rArg.MediaType, + rArg.Referer, + rArg.Source, + xEnv ); + + { + osl::MutexGuard aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + + xSink->setInputStream( xResult ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv, true ); + // Unreachable + } + } + else + { + uno::Reference< io::XOutputStream > xResult( rArg.Sink, uno::UNO_QUERY ); + if ( xResult.is() ) + { + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->POST( rArg.MediaType, + rArg.Referer, + rArg.Source, + xResult, + xEnv ); + + { + osl::MutexGuard aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv, true ); + // Unreachable + } + } + else + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArg.Sink ) ), + xEnv ); + // Unreachable + } + } +} + + +void Content::queryChildren( ContentRefList& rChildren ) +{ + // Obtain a list with a snapshot of all currently instantiated contents + // from provider and extract the contents which are direct children + // of this content. + + ::ucbhelper::ContentRefList aAllContents; + m_xProvider->queryExistingContents( aAllContents ); + + OUString aURL = m_xIdentifier->getContentIdentifier(); + sal_Int32 nURLPos = aURL.lastIndexOf( '/' ); + + if ( nURLPos != ( aURL.getLength() - 1 ) ) + { + // No trailing slash found. Append. + aURL += "/"; + } + + sal_Int32 nLen = aURL.getLength(); + + for ( const auto& rChild : aAllContents ) + { + ::ucbhelper::ContentImplHelperRef xChild = rChild; + OUString aChildURL + = xChild->getIdentifier()->getContentIdentifier(); + + // Is aURL a prefix of aChildURL? + if ( ( aChildURL.getLength() > nLen ) && + ( aChildURL.startsWith( aURL ) ) ) + { + sal_Int32 nPos = aChildURL.indexOf( '/', nLen ); + + if ( ( nPos == -1 ) || + ( nPos == ( aChildURL.getLength() - 1 ) ) ) + { + // No further slashes / only a final slash. It's a child! + rChildren.emplace_back( + static_cast< ::webdav_ucp::Content * >( + xChild.get() ) ); + } + } + } +} + + +void Content::insert( + const uno::Reference< io::XInputStream > & xInputStream, + bool bReplaceExisting, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + bool bTransient, bCollection; + OUString aEscapedTitle; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + bTransient = m_bTransient; + bCollection = m_bCollection; + aEscapedTitle = m_aEscapedTitle; + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + // Check, if all required properties are present. + + if ( aEscapedTitle.isEmpty() ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::insert - Title missing!" ); + + uno::Sequence<OUString> aProps { "Title" }; + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::MissingPropertiesException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + aProps ) ), + Environment ); + // Unreachable + } + + if ( !bReplaceExisting ) + { + /* [RFC 2616] - HTTP + + The PUT method requests that the enclosed entity be stored under the + supplied Request-URI. If the Request-URI refers to an already + existing resource, the enclosed entity SHOULD be considered as a + modified version of the one residing on the origin server. + */ + + /* [RFC 2518] - WebDAV + + MKCOL creates a new collection resource at the location specified by + the Request-URI. If the resource identified by the Request-URI is + non-null then the MKCOL MUST fail. + */ + + // ==> Complain on PUT, continue on MKCOL. + if ( !bTransient || !bCollection ) + { + ucb::UnsupportedNameClashException aEx( + "Unable to write without overwrite!", + static_cast< cppu::OWeakObject * >( this ), + ucb::NameClash::ERROR ); + + uno::Reference< task::XInteractionHandler > xIH; + + if ( Environment.is() ) + xIH = Environment->getInteractionHandler(); + + if ( !xIH.is() ) + { + // No IH; throw. + throw aEx; + } + + uno::Any aExAsAny( uno::makeAny( aEx ) ); + + rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest + = new ucbhelper::SimpleInteractionRequest( + aExAsAny, + ContinuationFlags::Approve | ContinuationFlags::Disapprove ); + xIH->handle( xRequest.get() ); + + const ContinuationFlags nResp = xRequest->getResponse(); + + switch ( nResp ) + { + case ContinuationFlags::NONE: + // Not handled; throw. + throw aEx; +// break; + + case ContinuationFlags::Approve: + // Continue -> Overwrite. + bReplaceExisting = true; + break; + + case ContinuationFlags::Disapprove: + // Abort. + throw ucb::CommandFailedException( + OUString(), + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + + default: + SAL_WARN( "ucb.ucp.webdav", "Content::insert - " + "Unknown interaction selection!" ); + throw ucb::CommandFailedException( + "Unknown interaction selection!", + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + } + + } + } + + if ( bTransient ) + { + // Assemble new content identifier... + OUString aURL = getParentURL(); + if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += aEscapedTitle; + + try + { + xResAccess->setURL( aURL ); + + if ( bCollection ) + { + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->MKCOL( Environment ); + } + else + { + // remove options from cache, PUT may change it + // it will be refreshed when needed + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->PUT( xInputStream, Environment ); + // clean cached value of PROPFIND properties names + } + // no error , set the resourcetype to unknown type + // the resource may have transitioned from NOT FOUND or UNKNOWN to something else + // depending on the server behaviour + // this will force a recheck of the resource type + m_eResourceType = UNKNOWN; + m_eResourceTypeForLocks = UNKNOWN; + } + catch ( DAVException const & except ) + { + if ( bCollection ) + { + if ( except.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // [RFC 2518] - WebDAV + // 405 (Method Not Allowed) - MKCOL can only be + // executed on a deleted/non-existent resource. + + if ( bReplaceExisting ) + { + // Destroy old resource. + try + { + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->DESTROY( Environment ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + + // Insert (recursion!). + insert( xInputStream, bReplaceExisting, Environment ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + + // Success! + return; + } + else + { + OUString aTitle; + try + { + NeonUri aURI( aURL ); + aTitle = aURI.GetPathBaseNameUnescaped(); + } + catch ( DAVException const & ) + { + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aTitle ) ), + Environment ); + // Unreachable + } + } + } + + cancelCommandExecution( except, Environment, true ); + // Unreachable + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xIdentifier = new ::ucbhelper::ContentIdentifier( aURL ); + } + + inserted(); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_bTransient = false; + } + } + else + { + if ( !xInputStream.is() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + // save the URL since it may change due to redirection + OUString aTargetUrl = xResAccess->getURL(); + try + { + removeCachedPropertyNames( xResAccess->getURL() ); + // remove options from cache, PUT may change it + // it will be refreshed when needed + aStaticDAVOptionsCache.removeDAVOptions( aTargetUrl ); + xResAccess->PUT( xInputStream, Environment ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } +} + + +void Content::transfer( + const ucb::TransferInfo & rArgs, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + uno::Reference< ucb::XContentIdentifier > xIdentifier; + uno::Reference< ucb::XContentProvider > xProvider; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + NeonUri sourceURI( rArgs.SourceURL ); + NeonUri targetURI( xIdentifier->getContentIdentifier() ); + + OUString aTargetURI; + try + { + aTargetURI = targetURI.GetPathBaseNameUnescaped(); + + // Check source's and target's URL scheme + + OUString aScheme = sourceURI.GetScheme().toAsciiLowerCase(); + if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if ( aScheme == DAV_URL_SCHEME ) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == DAVS_URL_SCHEME ) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if ( aScheme == WEBDAV_URL_SCHEME ) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == WEBDAVS_URL_SCHEME ) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else + { + if ( aScheme != HTTP_URL_SCHEME && aScheme != HTTPS_URL_SCHEME ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + } + + aScheme = targetURI.GetScheme().toAsciiLowerCase(); + if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME) + targetURI.SetScheme( HTTP_URL_SCHEME ); + else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME) + targetURI.SetScheme( HTTPS_URL_SCHEME ); + else if ( aScheme == DAV_URL_SCHEME ) + targetURI.SetScheme( HTTP_URL_SCHEME ); + else if ( aScheme == DAVS_URL_SCHEME ) + targetURI.SetScheme( HTTPS_URL_SCHEME ); + else if ( aScheme == WEBDAV_URL_SCHEME ) + targetURI.SetScheme( HTTP_URL_SCHEME ); + else if ( aScheme == WEBDAVS_URL_SCHEME ) + targetURI.SetScheme( HTTPS_URL_SCHEME ); + + // @@@ This implementation of 'transfer' only works + // if the source and target are located at same host. + // (Neon does not support cross-server copy/move) + + // Check for same host + + if ( !sourceURI.GetHost().isEmpty() && + ( sourceURI.GetHost() != targetURI.GetHost() ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::InteractiveBadTransferURLException( + "Different hosts!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + OUString aTitle = rArgs.NewTitle; + + if ( aTitle.isEmpty() ) + aTitle = sourceURI.GetPathBaseNameUnescaped(); + + if ( aTitle == "/" ) + { + // kso: ??? + aTitle.clear(); + } + + targetURI.AppendPath( aTitle ); + + OUString aTargetURL = xIdentifier->getContentIdentifier(); + if ( ( aTargetURL.lastIndexOf( '/' ) + 1 ) + != aTargetURL.getLength() ) + aTargetURL += "/"; + + aTargetURL += aTitle; + + uno::Reference< ucb::XContentIdentifier > xTargetId + = new ::ucbhelper::ContentIdentifier( aTargetURL ); + + DAVResourceAccess aSourceAccess( m_xContext, + xResAccess->getSessionFactory(), + sourceURI.GetURI() ); + + if ( rArgs.MoveData ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( rArgs.SourceURL ); + + // Note: The static cast is okay here, because its sure that + // xProvider is always the WebDAVContentProvider. + rtl::Reference< Content > xSource + = static_cast< Content * >( + xProvider->queryContent( xId ).get() ); + + // [RFC 2518] - WebDAV + // If a resource exists at the destination and the Overwrite + // header is "T" then prior to performing the move the server + // MUST perform a DELETE with "Depth: infinity" on the + // destination resource. If the Overwrite header is set to + // "F" then the operation will fail. + + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + aSourceAccess.MOVE( sourceURI.GetPath(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + + if ( xSource.is() ) + { + // Propagate destruction to listeners. + xSource->destroy( true ); + } + +// DAV resources store all additional props on server! +// // Rename own and all children's Additional Core Properties. +// renameAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// sal_True ); + } + else + { + // [RFC 2518] - WebDAV + // If a resource exists at the destination and the Overwrite + // header is "T" then prior to performing the copy the server + // MUST perform a DELETE with "Depth: infinity" on the + // destination resource. If the Overwrite header is set to + // "F" then the operation will fail. + + aStaticDAVOptionsCache.removeDAVOptions( sourceURI.GetURI() ); + aStaticDAVOptionsCache.removeDAVOptions( targetURI.GetURI() ); + aSourceAccess.COPY( sourceURI.GetPath(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + +// DAV resources store all additional props on server! +// // Copy own and all children's Additional Core Properties. +// copyAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// sal_True ); + } + + // Note: The static cast is okay here, because its sure that + // xProvider is always the WebDAVContentProvider. + rtl::Reference< Content > xTarget + = static_cast< Content * >( + xProvider->queryContent( xTargetId ).get() ); + + // Announce transferred content in its new folder. + xTarget->inserted(); + } + catch ( ucb::IllegalIdentifierException const & ) + { + // queryContent + } + catch ( DAVException const & e ) + { + // [RFC 2518] - WebDAV + // 412 (Precondition Failed) - The server was unable to maintain + // the liveness of the properties listed in the propertybehavior + // XML element or the Overwrite header is "F" and the state of + // the destination resource is non-null. + + if ( e.getStatus() == SC_PRECONDITION_FAILED ) + { + switch ( rArgs.NameClash ) + { + case ucb::NameClash::ERROR: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aTargetURI ) ), + Environment ); + [[fallthrough]]; // Unreachable + } + + case ucb::NameClash::OVERWRITE: + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::RENAME: + case ucb::NameClash::ASK: + default: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArgs.NameClash ) ), + Environment ); + // Unreachable + } + } + } + + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } +} + + +void Content::destroy( bool bDeletePhysical ) +{ + // @@@ take care about bDeletePhysical -> trashcan support + uno::Reference< ucb::XContent > xThis = this; + + deleted(); + + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // Process instantiated children... + + ::webdav_ucp::Content::ContentRefList aChildren; + queryChildren( aChildren ); + + for ( auto& rChild : aChildren ) + { + rChild->destroy( bDeletePhysical ); + } +} + +// returns the resource type, to be checked for locks +Content::ResourceType Content::resourceTypeForLocks( + const uno::Reference< ucb::XCommandEnvironment >& Environment, + const std::unique_ptr< DAVResourceAccess > & rResAccess) +{ + ResourceType eResourceTypeForLocks = UNKNOWN; + { + osl::MutexGuard g(m_aMutex); + //check if cache contains what we need, usually the first PROPFIND on the URI has supported lock + if (m_xCachedProps) + { + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK ) + >>= aSupportedLocks ) //get the cached value for supportedlock + { + for ( const auto& rSupportedLock : std::as_const(aSupportedLocks) ) + { + if ( rSupportedLock.Scope + == ucb::LockScope_EXCLUSIVE && + rSupportedLock.Type + == ucb::LockType_WRITE ) + eResourceTypeForLocks = DAV; + } + } + } + } + + const OUString & rURL = m_xIdentifier->getContentIdentifier(); + + if ( eResourceTypeForLocks == UNKNOWN ) + { + // resource type for lock/unlock operations still unknown, need to ask the server + + const OUString aScheme( + rURL.copy( 0, rURL.indexOf( ':' ) ).toAsciiLowerCase() ); + + if ( aScheme == FTP_URL_SCHEME ) + { + eResourceTypeForLocks = FTP; + } + else + { + DAVOptions aDAVOptions; + getResourceOptions( Environment, aDAVOptions, rResAccess ); + if( aDAVOptions.isClass1() || + aDAVOptions.isClass2() || + aDAVOptions.isClass3() ) + { + // this is at least a DAV, lock to be confirmed + // class 2 is needed for full lock support + // see + // <https://tools.ietf.org/html/rfc4918#section-18.2> + eResourceTypeForLocks = DAV_NOLOCK; + if( aDAVOptions.isClass2() ) + { + // ok, possible lock, check for it + try + { + // we need only DAV:supportedlock + std::vector< DAVResource > resources; + std::vector< OUString > aPropNames; + uno::Sequence< beans::Property > aProperties( 1 ); + aProperties[ 0 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames ); + rResAccess->PROPFIND( DAVZERO, aPropNames, resources, Environment ); + + bool wasSupportedlockFound = false; + + // only one resource should be returned + if ( resources.size() == 1 ) + { + // we may have received a bunch of other properties + // (some servers seems to do so) + // but we need only supported lock for this check + // all returned properties are in + // resources.properties[n].Name/.Value + + for ( const auto& rProp : resources[0].properties ) + { + if ( rProp.Name == DAVProperties::SUPPORTEDLOCK ) + { + wasSupportedlockFound = true; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( rProp.Value >>= aSupportedLocks ) + { + bool isSupported = std::any_of(aSupportedLocks.begin(), aSupportedLocks.end(), + [](const ucb::LockEntry& rLock) { + // TODO: if the lock type is changed from 'exclusive write' to 'shared write' + // e.g. to implement 'Calc shared file feature', the ucb::LockScope_EXCLUSIVE + // value should be checked as well, adaptation the code may be needed + return rLock.Scope == ucb::LockScope_EXCLUSIVE + && rLock.Type == ucb::LockType_WRITE; + }); + if (isSupported) + { + // requested locking mode is supported + eResourceTypeForLocks = DAV; + SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV lock/unlock supported"); + } + break; + } + } + } + } + else + { + // PROPFIND failed; check if HEAD contains Content-Disposition: attachment (RFC1806, HTTP/1.1 19.5.1), + // which supposedly means no lock for the resource (happens e.g. with SharePoint exported lists) + OUString sContentDisposition; + // First, check cached properties + if (m_xCachedProps) + { + if ((m_xCachedProps->getValue("Content-Disposition") >>= sContentDisposition) + && sContentDisposition.startsWithIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + } + // If no data in cache, try HEAD request + if (sContentDisposition.isEmpty() && !m_bDidGetOrHead) try + { + DAVResource resource; + GetPropsUsingHeadRequest(resource, rResAccess, {"Content-Disposition"}, Environment); + m_bDidGetOrHead = true; + for (const auto& it : resource.properties) + { + if (it.Name.equalsIgnoreAsciiCase("Content-Disposition")) + { + if ((it.Value >>= sContentDisposition) && sContentDisposition.equalsIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + break; + } + } + } + catch (...){} + } + // check if this is still only a DAV_NOLOCK + // a fallback for resources that do not have DAVProperties::SUPPORTEDLOCK property + // we check for the returned OPTION if LOCK is allowed on the resource + if ( !wasSupportedlockFound && eResourceTypeForLocks == DAV_NOLOCK ) + { + SAL_INFO( "ucb.ucp.webdav", "This WebDAV server has no supportedlock property, check for allowed LOCK method in OPTIONS" ); + // ATTENTION: if the lock type is changed from 'exclusive write' to 'shared write' + // e.g. to implement 'Calc shared file feature' on WebDAV directly, and we arrive to this fallback + // and the LOCK is allowed, we should assume that only exclusive write lock is available + // this is just a reminder... + if ( aDAVOptions.isLockAllowed() ) + eResourceTypeForLocks = DAV; + } + } + catch ( DAVException const & e ) + { + rResAccess->resetUri(); + //grab the error code + switch( e.getStatus() ) + { + case SC_NOT_FOUND: + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <" + << m_xIdentifier->getContentIdentifier() << "> was not found. "); + eResourceTypeForLocks = NOT_FOUND; + break; + // some servers returns SC_FORBIDDEN, instead + // the meaning of SC_FORBIDDEN is, according to <http://tools.ietf.org/html/rfc7231#section-6.5.3>: + // The 403 (Forbidden) status code indicates that the server understood + // the request but refuses to authorize it + case SC_FORBIDDEN: + // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are + // part of base http 1.1 RFCs + case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2> + case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5> + // they all mean the resource is NON_DAV + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException (SC_FORBIDDEN, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + eResourceTypeForLocks = NON_DAV; + break; + default: + //fallthrough + SAL_WARN( "ucb.ucp.webdav", "resourceTypeForLocks() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + eResourceTypeForLocks = UNKNOWN; + } + } + } + } + else + eResourceTypeForLocks = NON_DAV; + + } + } + osl::MutexGuard g(m_aMutex); + if (m_eResourceTypeForLocks == UNKNOWN) + { + m_eResourceTypeForLocks = eResourceTypeForLocks; + } + else + { + SAL_WARN_IF( + eResourceTypeForLocks != m_eResourceTypeForLocks, "ucb.ucp.webdav", + "different resource types for <" << rURL << ">: " + << +eResourceTypeForLocks << " vs. " << +m_eResourceTypeForLocks); + } + SAL_INFO( "ucb.ucp.webdav", "resourceTypeForLocks() - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, m_eResourceTypeForLocks: " << m_eResourceTypeForLocks ); + return m_eResourceTypeForLocks; +} + +Content::ResourceType Content::resourceTypeForLocks( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + Content::ResourceType ret = resourceTypeForLocks( Environment, xResAccess ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + return ret; +} + +void Content::lock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ +// prepare aURL to be used in exception, see below + OUString aURL; + if ( m_bTransient ) + { + aURL = getParentURL(); + if ( aURL.lastIndexOf('/') != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += m_aEscapedTitle; + } + else + { + aURL = m_xIdentifier->getContentIdentifier(); + } + + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + uno::Any aOwnerAny; + aOwnerAny + <<= OUString("LibreOffice - http://www.libreoffice.org/"); + + ucb::Lock aLock( + ucb::LockScope_EXCLUSIVE, + ucb::LockType_WRITE, + ucb::LockDepth_ZERO, + aOwnerAny, + 180, // lock timeout in secs + //-1, // infinite lock + uno::Sequence< OUString >() ); + + // OPTIONS may change as a consequence of the lock operation + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->LOCK( aLock, Environment ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + } + catch ( DAVException const & e ) + { + // check if the exception thrown is 'already locked' + // this exception is mapped directly to the ucb correct one, without + // going into the cancelCommandExecution() user interaction + // this exception should be managed by the issuer of 'lock' command + switch( e.getError() ) + { + case DAVException::DAV_LOCKED: + { + SAL_WARN( "ucb.ucp.webdav", "lock() resource already locked - URL: <" + << m_xIdentifier->getContentIdentifier() << ">"); + throw + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); + } + break; + case DAVException::DAV_HTTP_AUTH: + { + SAL_WARN( "ucb.ucp.webdav", "lock() DAVException Authentication error - URL: <" + << m_xIdentifier->getContentIdentifier() << ">" ); + // DAVException::DAV_HTTP_AUTH exception can mean: + // - interaction handler for credential management not present (happens, depending + // on the LO framework processing) + // - the remote site is a WebDAV with special configuration: read/only for read operations + // and read/write for write operations, the user is not allowed to lock/write and + // she cancelled the credentials request. + // this is not actually an error, but the exception is sent directly from here, avoiding the automatic + // management that takes part in cancelCommandExecution() below + // Unfortunately there is no InteractiveNetwork*Exception available to signal this + // since it mostly happens on read/only part of webdav, this appears to be the most correct exception available + throw + ucb::InteractiveNetworkWriteException( + "Authentication error while trying to lock! Write only WebDAV perhaps?", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + } + break; + case DAVException::DAV_HTTP_ERROR: + //grab the error code + switch( e.getStatus() ) + { + // The 'case SC_NOT_FOUND' just below tries to solve a problem in eXo Platform + // WebDAV connector which apparently fail on resource first creation + // rfc4918 section-7.3 (see link below) + case SC_NOT_FOUND: // <http://tools.ietf.org/html/rfc7231#section-6.5.4> + // The 'case SC_PRECONDITION_FAILED' just below tries to solve a problem + // in SharePoint when locking the resource on first creation fails due to this: + // <https://msdn.microsoft.com/en-us/library/jj575265%28v=office.12%29.aspx#id15> + // (retrieved on 2015-08-14) + case SC_PRECONDITION_FAILED: // <http://tools.ietf.org/html/rfc7232#section-4.2> + // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are + // part of base http 1.1 RFCs + case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2> + case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5> + SAL_WARN( "ucb.ucp.webdav", "lock() DAVException (SC_NOT_FOUND, SC_PRECONDITION_FAILED, SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + // act as nothing happened + // that's because when a resource is first created + // the lock is sent before the put, so the resource + // is actually created by LOCK, locking it before + // the first PUT, but if LOCK is not supported + // (simple web or DAV with lock disabled) we end with one of these http + // errors. + // These same errors may be reported when the LOCK on an unmapped + // (i.e. non existent) resource is not implemented. + // Detailed specification in: + // <http://tools.ietf.org/html/rfc4918#section-7.3> + return; + break; + default: + //fallthrough + ; + } + break; + case DAVException::DAV_LOCKED_SELF: + // we already hold the lock and it is in our internal lockstore + // just return as if the lock was successful + return; + break; + default: + //fallthrough + ; + } + + SAL_WARN( "ucb.ucp.webdav","lock() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + cancelCommandExecution( e, Environment ); + // Unreachable + } +} + + +void Content::unlock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + + // check if the target URL is a Class1 DAV + DAVOptions aDAVOptions; + getResourceOptions( Environment, aDAVOptions, xResAccess ); + + // at least class one is needed + if( aDAVOptions.isClass1() ) + { + // remove options from cache, unlock may change it + // it will be refreshed when needed + aStaticDAVOptionsCache.removeDAVOptions( xResAccess->getURL() ); + // clean cached value of PROPFIND properties names + removeCachedPropertyNames( xResAccess->getURL() ); + xResAccess->UNLOCK( Environment ); + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + } + catch ( DAVException const & e ) + { + switch( e.getError() ) + { + case DAVException::DAV_NOT_LOCKED: + SAL_WARN( "ucb.ucp.webdav", "unlock() DAVException::DAV_NOT_LOCKED - URL: <" + << m_xIdentifier->getContentIdentifier() << ">"); + // means that we don't own any lock on this resource + // intercepted here to remove a confusing indication to the user + // unfortunately this happens in some WebDAV server configuration + // acting as WebDAV and having lock/unlock enabled only + // for authorized user. + return; + break; + case DAVException::DAV_HTTP_ERROR: + //grab the error code + switch( e.getStatus() ) + { + // Errors SC_NOT_IMPLEMENTED and SC_METHOD_NOT_ALLOWED are + // part of base http 1.1 RFCs + case SC_NOT_IMPLEMENTED: // <http://tools.ietf.org/html/rfc7231#section-6.6.2> + case SC_METHOD_NOT_ALLOWED: // <http://tools.ietf.org/html/rfc7231#section-6.5.5> + SAL_WARN( "ucb.ucp.webdav", "unlock() DAVException (SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED) - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + return; + break; + default: + //fallthrough + ; + } + break; + default: + //fallthrough + ; + } + SAL_WARN( "ucb.ucp.webdav","unlock() DAVException - URL: <" + << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + cancelCommandExecution( e, Environment ); + // Unreachable + } +} + + +bool Content::exchangeIdentity( + const uno::Reference< ucb::XContentIdentifier >& xNewId ) +{ + if ( !xNewId.is() ) + return false; + + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + uno::Reference< ucb::XContent > xThis = this; + + // Already persistent? + if ( m_bTransient ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::exchangeIdentity - Not persistent!" ); + return false; + } + + // Exchange own identitity. + + // Fail, if a content with given id already exists. +// if ( !hasData( xNewId ) ) + { + OUString aOldURL = m_xIdentifier->getContentIdentifier(); + + aGuard.clear(); + if ( exchange( xNewId ) ) + { + // Process instantiated children... + + ContentRefList aChildren; + queryChildren( aChildren ); + + for ( const auto& rChild : aChildren ) + { + ContentRef xChild = rChild; + + // Create new content identifier for the child... + uno::Reference< ucb::XContentIdentifier > + xOldChildId = xChild->getIdentifier(); + OUString aOldChildURL + = xOldChildId->getContentIdentifier(); + OUString aNewChildURL + = aOldChildURL.replaceAt( + 0, + aOldURL.getLength(), + xNewId->getContentIdentifier() ); + uno::Reference< ucb::XContentIdentifier > xNewChildId + = new ::ucbhelper::ContentIdentifier( aNewChildURL ); + + if ( !xChild->exchangeIdentity( xNewChildId ) ) + return false; + } + return true; + } + } + + SAL_WARN( "ucb.ucp.webdav", "Content::exchangeIdentity - " + "Panic! Cannot exchange identity!" ); + return false; +} + + +bool Content::isFolder( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + { + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_bTransient ) + return m_bCollection; + } + + uno::Sequence< beans::Property > aProperties( 1 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 0 ].Handle = -1; + uno::Reference< sdbc::XRow > xRow( getPropertyValues( aProperties, xEnv ) ); + if ( xRow.is() ) + { + try + { + return xRow->getBoolean( 1 ); + } + catch ( sdbc::SQLException const & ) + { + } + } + + return false; +} + + +uno::Any Content::MapDAVException( const DAVException & e, bool bWrite ) +{ + // Map DAVException... + uno::Any aException; + + OUString aURL; + if ( m_bTransient ) + { + aURL = getParentURL(); + if ( aURL.lastIndexOf('/') != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += m_aEscapedTitle; + } + else + { + aURL = m_xIdentifier->getContentIdentifier(); + } + + switch ( e.getStatus() ) + { + case SC_NOT_FOUND: + { + uno::Sequence< uno::Any > aArgs( 1 ); + aArgs[ 0 ] <<= beans::PropertyValue( + "Uri", -1, + uno::makeAny(aURL), + beans::PropertyState_DIRECT_VALUE); + + aException <<= + ucb::InteractiveAugmentedIOException( + "Not found!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + ucb::IOErrorCode_NOT_EXISTING, + aArgs ); + return aException; + } + default: + break; + } + + switch ( e.getError() ) + { + case DAVException::DAV_HTTP_ERROR: + { + if ( bWrite ) + aException <<= + ucb::InteractiveNetworkWriteException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + else + aException <<= + ucb::InteractiveNetworkReadException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + } + + case DAVException::DAV_HTTP_LOOKUP: + aException <<= + ucb::InteractiveNetworkResolveNameException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_AUTH: +// break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_AUTHPROXY: +// break; + + case DAVException::DAV_HTTP_TIMEOUT: + case DAVException::DAV_HTTP_CONNECT: + aException <<= + ucb::InteractiveNetworkConnectException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_REDIRECT: +// break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_SESSION_CREATE: +// break; + + case DAVException::DAV_INVALID_ARG: + aException <<= + lang::IllegalArgumentException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + -1 ); + break; + + case DAVException::DAV_LOCKED: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); // not SelfOwned + break; + + case DAVException::DAV_LOCKED_SELF: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked (self!)", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + true ); // SelfOwned + break; + + case DAVException::DAV_NOT_LOCKED: + aException <<= + ucb::InteractiveLockingNotLockedException( + "Not locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + case DAVException::DAV_LOCK_EXPIRED: + aException <<= + ucb::InteractiveLockingLockExpiredException( + "Lock expired!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + default: + aException <<= + ucb::InteractiveNetworkGeneralException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR ); + break; + } + + return aException; +} + + +// static +bool Content::shouldAccessNetworkAfterException( const DAVException & e ) +{ + return !(( e.getStatus() == SC_NOT_FOUND ) || + ( e.getStatus() == SC_GONE ) || + ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) || + ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) || + ( e.getError() == DAVException::DAV_HTTP_AUTH ) || + ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY )); +} + + +void Content::cancelCommandExecution( + const DAVException & e, + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + bool bWrite /* = sal_False */ ) +{ + ucbhelper::cancelCommandExecution( MapDAVException( e, bWrite ), xEnv ); + // Unreachable +} + + +OUString +Content::getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // First, try to obtain value of response header "Content-Location". + if (m_xCachedProps) + { + OUString aLocation; + m_xCachedProps->getValue( "Content-Location" ) >>= aLocation; + if ( !aLocation.isEmpty() ) + { + try + { + // Do not use m_xIdentifier->getContentIdentifier() because it + // for example does not reflect redirects applied to requests + // done using the original URI but m_xResAccess' URI does. + return rtl::Uri::convertRelToAbs( rResAccess->getURL(), + aLocation ); + } + catch ( rtl::MalformedUriException const & ) + { + } + } + } + + return rResAccess->getURL(); +} + +// resource type is the type of the WebDAV resource +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed) +{ + { + osl::MutexGuard g(m_aMutex); + if (m_eResourceType != UNKNOWN) { + return m_eResourceType; + } + } + + ResourceType eResourceType = UNKNOWN; + DAVOptions aDAVOptions; + + const OUString & rURL = rResAccess->getURL(); + const OUString aScheme( + rURL.copy( 0, rURL.indexOf( ':' ) ).toAsciiLowerCase() ); + + if ( aScheme == FTP_URL_SCHEME ) + { + eResourceType = FTP; + } + else + { + getResourceOptions( xEnv, aDAVOptions, rResAccess, networkAccessAllowed ); + + // at least class one is needed + if( aDAVOptions.isClass1() ) + { + try + { + // Try to fetch some frequently used property value, e.g. those + // used when loading documents... along with identifying whether + // this is a DAV resource. + std::vector< DAVResource > resources; + std::vector< OUString > aPropNames; + uno::Sequence< beans::Property > aProperties( 5 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 1 ].Name = "IsDocument"; + aProperties[ 2 ].Name = "IsReadOnly"; + aProperties[ 3 ].Name = "MediaType"; + aProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( aProperties, aPropNames ); + + rResAccess->PROPFIND( DAVZERO, aPropNames, resources, xEnv ); + + if ( resources.size() == 1 ) + { +#if defined SAL_LOG_INFO + {//debug + // print received resources + for ( const auto& rProp : resources[0].properties ) + { + OUString aPropValue; + bool bValue; + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if(rProp.Value >>= aPropValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" << aPropValue ); + else if( rProp.Value >>= bValue ) + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" << + ( bValue ? "true" : "false" ) ); + else if( rProp.Value >>= aSupportedLocks ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND (getResourceType) - ret'd prop: " << rProp.Name << ":" ); + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + SAL_INFO( "ucb.ucp.webdav","PROPFIND (getResourceType) - supportedlock[" << n <<"]: scope: " + << ( aSupportedLocks[ n ].Scope == css::ucb::LockScope_SHARED ? "shared" : "exclusive" ) + << ", type: " + << ( aSupportedLocks[ n ].Type != css::ucb::LockType_WRITE ? "" : "write" ) ); + } + } + } + } +#endif + osl::MutexGuard g(m_aMutex); + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( resources[ 0 ] ) ) ); + m_xCachedProps->containsAllNames( + aProperties, m_aFailedPropNames ); + } + eResourceType = DAV; + } + catch ( DAVException const & e ) + { + rResAccess->resetUri(); + + SAL_WARN( "ucb.ucp.webdav", "Content::getResourceType returned errors, DAV ExceptionCode: " << e.getError() << ", HTTP error: " << e.getStatus() ); + + if ( e.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // Status SC_METHOD_NOT_ALLOWED is a safe indicator that the + // resource is NON_DAV + eResourceType = NON_DAV; + } + else if (networkAccessAllowed != nullptr) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + if ( e.getStatus() == SC_NOT_FOUND ) + { + // arrives here if OPTIONS is still cached for a resource previously available + // operate on the OPTIONS cache: + // if OPTIONS was not found, do nothing + // else OPTIONS returned on a resource not existent (example a server that allows lock on null resource) set + // not found and adjust lifetime accordingly + DAVOptions aDAVOptionsInner; + if( aStaticDAVOptionsCache.getDAVOptions( rURL, aDAVOptionsInner ) ) + { + // TODO? get redirected url + aDAVOptionsInner.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptionsInner.setHttpResponseStatusText( e.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsInner, + m_nOptsCacheLifeNotFound ); + } + } + // if the two net events below happen, something + // is going on to the connection so break the command flow + if ( ( e.getError() == DAVException::DAV_HTTP_TIMEOUT ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) ) + { + cancelCommandExecution( e, xEnv ); + // unreachable + } + } + } + else + { + rResAccess->resetUri(); + + // first check if the cached error can be mapped to DAVException::DAV_HTTP_TIMEOUT or mapped to DAVException::DAV_HTTP_CONNECT + if ( aDAVOptions.getHttpResponseStatusCode() == USC_CONNECTION_TIMED_OUT ) + { + // behave same as DAVException::DAV_HTTP_TIMEOUT or DAVException::DAV_HTTP_CONNECT was thrown + try + { + // extract host name and connection port + NeonUri theUri( rURL ); + const OUString& aHostName = theUri.GetHost(); + sal_Int32 nPort = theUri.GetPort(); + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + NeonUri::makeConnectionEndPointString( aHostName, + nPort ) ); + } + catch ( DAVException& exp ) + { + cancelCommandExecution( exp, xEnv ); + } + } + + if ( aDAVOptions.getHttpResponseStatusCode() != SC_NOT_FOUND && + aDAVOptions.getHttpResponseStatusCode() != SC_GONE ) // the cached OPTIONS can have SC_GONE + { + eResourceType = NON_DAV; + } + else + { + //resource doesn't exist + if ( networkAccessAllowed != nullptr ) + *networkAccessAllowed = false; } + } + } + + osl::MutexGuard g(m_aMutex); + if (m_eResourceType == UNKNOWN) { + m_eResourceType = eResourceType; + } else { + SAL_WARN_IF( + eResourceType != m_eResourceType, "ucb.ucp.webdav", + "different resource types for <" << rURL << ">: " + << +eResourceType << " vs. " << +m_eResourceType); + } + SAL_INFO( "ucb.ucp.webdav", "m_eResourceType for <"<<rURL<<">: " << m_eResourceType ); + return m_eResourceType; +} + + +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + } + Content::ResourceType const ret = getResourceType( xEnv, xResAccess ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset(new DAVResourceAccess(*xResAccess)); + } + return ret; +} + + +void Content::initOptsCacheLifeTime() +{ + // see description in + // officecfg/registry/schema/org/openoffice/Inet.xcs + // for use of these field values. + sal_uInt32 nAtime; + nAtime = officecfg::Inet::Settings::OptsCacheLifeImplWeb::get( m_xContext ); + m_nOptsCacheLifeImplWeb = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAV::get( m_xContext ); + m_nOptsCacheLifeDAV = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeDAVLocked::get( m_xContext ); + m_nOptsCacheLifeDAVLocked = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 3600 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotImpl::get( m_xContext ); + m_nOptsCacheLifeNotImpl = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 43200 ) ) ); + + nAtime = officecfg::Inet::Settings::OptsCacheLifeNotFound::get( m_xContext ); + m_nOptsCacheLifeNotFound = std::max( sal_uInt32( 0 ), + std::min( nAtime, sal_uInt32( 30 ) ) ); +} + + +void Content::getResourceOptions( + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + DAVOptions& rDAVOptions, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed ) +{ + OUString aRedirURL; + OUString aTargetURL = rResAccess->getURL(); + DAVOptions aDAVOptions; + // first check if in cache, if not, then send method to server + if ( !aStaticDAVOptionsCache.getDAVOptions( aTargetURL, aDAVOptions ) ) + { + try + { + rResAccess->OPTIONS( aDAVOptions, xEnv ); + // IMPORTANT:the correctly implemented server will answer without errors, even if the resource is not present + sal_uInt32 nLifeTime = ( aDAVOptions.isClass1() || + aDAVOptions.isClass2() || + aDAVOptions.isClass3() ) ? + m_nOptsCacheLifeDAV : // a WebDAV site + m_nOptsCacheLifeImplWeb; // a site implementing OPTIONS but + // it's not DAV + // if resource is locked, will use a + // different lifetime + if( aDAVOptions.isLocked() ) + nLifeTime = m_nOptsCacheLifeDAVLocked; + + // check if redirected + aRedirURL = rResAccess->getURL(); + if( aRedirURL == aTargetURL) + { // no redirection + aRedirURL.clear(); + } + // cache this URL's option + aDAVOptions.setURL( aTargetURL ); + aDAVOptions.setRedirectedURL( aRedirURL ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + nLifeTime ); + } + catch ( DAVException const & e ) + { + // first, remove from cache, will be added if needed, depending on the error received + aStaticDAVOptionsCache.removeDAVOptions( aTargetURL ); + rResAccess->resetUri(); + + aDAVOptions.setURL( aTargetURL ); + aDAVOptions.setRedirectedURL( aRedirURL ); + switch( e.getError() ) + { + case DAVException::DAV_HTTP_TIMEOUT: + case DAVException::DAV_HTTP_CONNECT: + { + // something bad happened to the connection + // not same as not found, this instead happens when the server doesn't exist or doesn't answer at all + // probably a new bit stating 'timed out' should be added to opts var? + // in any case abort the command + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_TIMEOUT or DAV_HTTP_CONNECT for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // cache the internal unofficial status code + + aDAVOptions.setHttpResponseStatusCode( USC_CONNECTION_TIMED_OUT ); + // used only internally, so the text doesn't really matter... + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_LOOKUP: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_LOOKUP for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + aDAVOptions.setHttpResponseStatusCode( USC_LOOKUP_FAILED ); + // used only internally, so the text doesn't really matter... + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_AUTH: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTH for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // - the remote site is a WebDAV with special configuration: read/only for read operations + // and read/write for write operations, the user is not allowed to lock/write and + // she cancelled the credentials request. + // this is not actually an error, it means only that for current user this is a standard web, + // though possibly DAV enabled + aDAVOptions.setHttpResponseStatusCode( USC_AUTH_FAILED ); + // used only internally, so the text doesn't really matter... + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_AUTHPROXY: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAVException: DAV_HTTP_AUTHPROXY for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + aDAVOptions.setHttpResponseStatusCode( USC_AUTHPROXY_FAILED ); + // used only internally, so the text doesn't really matter... + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + break; + case DAVException::DAV_HTTP_ERROR: + { + switch( e.getStatus() ) + { + case SC_FORBIDDEN: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_FORBIDDEN for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + case SC_BAD_REQUEST: + case SC_INTERNAL_SERVER_ERROR: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_BAD_REQUEST or SC_INTERNAL_SERVER_ERROR for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + // cache it, so OPTIONS won't be called again, this URL detect some problem while answering the method + aDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptions.setHttpResponseStatusText( e.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotFound ); + } + break; + case SC_NOT_IMPLEMENTED: + case SC_METHOD_NOT_ALLOWED: + { + // OPTIONS method must be implemented in DAV + // resource is NON_DAV, or not advertising it + SAL_WARN( "ucb.ucp.webdav","OPTIONS - SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + case SC_NOT_FOUND: + { + // Apparently on IIS 10.0, if you disabled OPTIONS method, this error is the one reported, + // instead of SC_NOT_IMPLEMENTED or SC_METHOD_NOT_ALLOWED. + // So check if this is an available resource, or a real 'Not Found' event. + sal_uInt32 nLifeTime = m_nOptsCacheLifeNotFound; + if( isResourceAvailable( xEnv, rResAccess, aDAVOptions ) ) + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - Got an SC_NOT_FOUND, but the URL <" << m_xIdentifier->getContentIdentifier() << "> resource exists" ); + nLifeTime = m_nOptsCacheLifeNotImpl; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - SC_NOT_FOUND for URL <" << m_xIdentifier->getContentIdentifier() << ">" ); + if ( networkAccessAllowed != nullptr ) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + } + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + nLifeTime ); + } + break; + default: + { + SAL_WARN( "ucb.ucp.webdav", "OPTIONS - DAV_HTTP_ERROR, for URL <" << m_xIdentifier->getContentIdentifier() << ">, HTTP error: "<< e.getStatus() + << ", '" << e.getData() << "'" ); + aDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + aDAVOptions.setHttpResponseStatusText( e.getData() ); + // cache it, so OPTIONS won't be called again, this URL does not support it + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + } + } + break; + // The 'DAVException::DAV_HTTP_REDIRECT' means we reached the maximum + // number of redirections, consider the resource type as UNKNOWN + // possibly a normal web site, not DAV + case DAVException::DAV_HTTP_REDIRECT: + default: + { + SAL_WARN( "ucb.ucp.webdav","OPTIONS - General DAVException (or max DAV_HTTP_REDIRECT reached) for URL <" << m_xIdentifier->getContentIdentifier() << ">, DAV ExceptionCode: " + << e.getError() << ", HTTP error: "<< e.getStatus() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptions, + m_nOptsCacheLifeNotImpl ); + } + break; + } + } + } + else + { + // check current response status code, perhaps we need to set networkAccessAllowed + sal_uInt16 CachedResponseStatusCode = aDAVOptions.getHttpResponseStatusCode(); + if ( networkAccessAllowed != nullptr && + ( ( CachedResponseStatusCode == SC_NOT_FOUND ) || + ( CachedResponseStatusCode == SC_GONE ) || + ( CachedResponseStatusCode == USC_CONNECTION_TIMED_OUT ) || + ( CachedResponseStatusCode == USC_LOOKUP_FAILED ) || + ( CachedResponseStatusCode == USC_AUTH_FAILED ) || + ( CachedResponseStatusCode == USC_AUTHPROXY_FAILED ) + ) + ) + { + *networkAccessAllowed = false; + } + } + rDAVOptions = aDAVOptions; +} + + +//static +bool Content::isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + DAVOptions& rDAVOptions ) +{ + std::vector< OUString > aHeaderNames; + DAVResource aResource; + + try + { + // To check for the physical URL resource availability, first + // try using a simple HEAD command + // if HEAD is successful, set element found, + rResAccess->HEAD( aHeaderNames, aResource, xEnv ); + rDAVOptions.setHttpResponseStatusCode( 0 ); + rDAVOptions.setHttpResponseStatusText( OUString() ); + return true; + } + catch ( DAVException const & e ) + { + if ( e.getError() == DAVException::DAV_HTTP_ERROR ) + { + if ( e.getStatus() == SC_NOT_IMPLEMENTED || + e.getStatus() == SC_METHOD_NOT_ALLOWED || + e.getStatus() == SC_NOT_FOUND ) + { + SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" ); + // set in cached OPTIONS "HEAD not implemented" + // so it won't be used again on this resource + rDAVOptions.setHeadAllowed( false ); + try + { + // do a GET with a payload of 0, the server does not + // support HEAD (or has HEAD disabled) + DAVRequestHeaders aPartialGet; + aPartialGet.emplace_back( + OUString( "Range" ), + OUString( "bytes=0-0" )); + + rResAccess->GET0( aPartialGet, + aHeaderNames, + aResource, + xEnv ); + return true; + } + catch ( DAVException const & ex ) + { + if ( ex.getError() == DAVException::DAV_HTTP_ERROR ) + { + rDAVOptions.setHttpResponseStatusCode( ex.getStatus() ); + rDAVOptions.setHttpResponseStatusText( ex.getData() ); + } + } + } + else + { + rDAVOptions.setHttpResponseStatusCode( e.getStatus() ); + rDAVOptions.setHttpResponseStatusText( e.getData() ); + } + } + return false; + } + catch ( ... ) + { + } + // set SC_NOT_IMPLEMENTED since at a minimum GET must be implemented in a basic Web server + rDAVOptions.setHttpResponseStatusCode( SC_NOT_IMPLEMENTED ); + rDAVOptions.setHttpResponseStatusText( OUString() ); + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavcontent.hxx b/ucb/source/ucp/webdav-neon/webdavcontent.hxx new file mode 100644 index 000000000..4a976a26b --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontent.hxx @@ -0,0 +1,320 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVCONTENT_HXX + +#include <config_lgpl.h> +#include <memory> +#include <list> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include <ucbhelper/contenthelper.hxx> +#include "DAVResourceAccess.hxx" +#include "PropertyMap.hxx" + +namespace com::sun::star::beans { + struct Property; + struct PropertyValue; +} + +namespace com::sun::star::io { + class XInputStream; +} + +namespace com::sun::star::sdbc { + class XRow; +} + +namespace com::sun::star::ucb { + struct OpenCommandArgument3; + struct PostCommandArgument2; + struct PropertyCommandArgument; + struct TransferInfo; +} + +namespace webdav_ucp +{ + + +// UNO service name for the content. +#define WEBDAV_CONTENT_SERVICE_NAME "com.sun.star.ucb.WebDAVContent" + + +class ContentProvider; +class ContentProperties; +class CachableContentProperties; + +class Content : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator +{ + enum ResourceType + { + UNKNOWN, // the type of the Web resource is unknown + NOT_FOUND, // the Web resource does not exists + FTP, // the Web resource exists but it's ftp + NON_DAV, // the Web resource exists but it's not DAV + DAV, // the type of the Web resource is DAV with lock/unlock available + DAV_NOLOCK // the type of the Web resource is DAV with no lock/unlock available + }; + + std::unique_ptr< DAVResourceAccess > m_xResAccess; + std::unique_ptr< CachableContentProperties > + m_xCachedProps; // locally cached props + OUString m_aEscapedTitle; + // resource type for general DAV methods + ResourceType m_eResourceType; + // resource type for general LOCK method only + ResourceType m_eResourceTypeForLocks; + ContentProvider* m_pProvider; // No need for a ref, base class holds object + bool m_bTransient; + bool m_bCollection; + bool m_bDidGetOrHead; + std::vector< OUString > m_aFailedPropNames; + // Options Cache lifetime + // for web site implementing OPTIONS, but not dav + sal_uInt32 m_nOptsCacheLifeImplWeb; + // for WebDAV site where OPTIONS is mandatory + sal_uInt32 m_nOptsCacheLifeDAV; + // same as above, but when the resource is locked by us + sal_uInt32 m_nOptsCacheLifeDAVLocked; +// For web site not implementing OPTIONS + // during this time we assume the site doesn't turn to WebDAV + // but remains a simple Web + sal_uInt32 m_nOptsCacheLifeNotImpl; + // When resource is not found + // may be the resource is unavailable only briefly? + // so better have this small + sal_uInt32 m_nOptsCacheLifeNotFound; + + void initOptsCacheLifeTime(); + +private: + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual OUString getParentURL() override; + + /// @throws css::uno::Exception + bool isFolder( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Sequence< css::uno::Any > + setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + typedef rtl::Reference< Content > ContentRef; + typedef std::vector< ContentRef > ContentRefList; + void queryChildren( ContentRefList& rChildren); + + bool + exchangeIdentity( const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId ); + + OUString + getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed = nullptr); + + // Command "open" + /// @throws css::uno::Exception + css::uno::Any open( + const css::ucb::OpenCommandArgument3 & rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // Command "post" + /// @throws css::uno::Exception + void post( const css::ucb::PostCommandArgument2 & rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // Command "insert" + /// @throws css::uno::Exception + void insert( const css::uno::Reference< css::io::XInputStream > & xInputStream, + bool bReplaceExisting, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "transfer" + /// @throws css::uno::Exception + void transfer( const css::ucb::TransferInfo & rArgs, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "delete" + /// @throws css::uno::Exception + void destroy( bool bDeletePhysical ); + + // Command "lock" + /// @throws css::uno::Exception + void lock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "unlock" + /// @throws css::uno::Exception + void unlock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + css::uno::Any MapDAVException( const DAVException & e, + bool bWrite ); + /// @throws css::uno::Exception + void cancelCommandExecution( + const DAVException & e, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + bool bWrite = false ); + + static bool shouldAccessNetworkAfterException( const DAVException & e ); + + ResourceType resourceTypeForLocks( + const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment, + const std::unique_ptr< DAVResourceAccess > & rResAccess ); + + ResourceType resourceTypeForLocks( + const css::uno::Reference< css::ucb::XCommandEnvironment >& rEnvironment ); + + /// @throws css::beans::PropertyExistException + /// @throws css::beans::IllegalTypeException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void addProperty( const css::ucb::PropertyCommandArgument &aCmdArg, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + /// @throws css::beans::UnknownPropertyException + /// @throws css::beans::NotRemoveableException + /// @throws css::uno::RuntimeException + void removeProperty( const OUString& Name, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + +public: + /// @throws css::ucb::ContentCreationException + /// @throws css::uno::RuntimeException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory ); + /// @throws css::ucb::ContentCreationException + /// @throws css::uno::RuntimeException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + bool isCollection ); + virtual ~Content() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL + getImplementationName() override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XContent + virtual OUString SAL_CALL + getContentType() override; + + // XCommandProcessor + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + virtual void SAL_CALL + abort( sal_Int32 CommandId ) override; + + // XPropertyContainer + virtual void SAL_CALL + addProperty( const OUString& Name, + sal_Int16 Attributes, + const css::uno::Any& DefaultValue ) override; + + virtual void SAL_CALL + removeProperty( const OUString& Name ) override; + + + // Additional interfaces + + + // XContentCreator + virtual css::uno::Sequence< css::ucb::ContentInfo > SAL_CALL + queryCreatableContentsInfo() override; + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createNewContent( const css::ucb::ContentInfo& Info ) override; + + + // Non-interface methods. + + + DAVResourceAccess & getResourceAccess() { return *m_xResAccess; } + + // Called from resultset data supplier. + static css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::beans::Property >& rProperties, + const ContentProperties& rData, + const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider, + const OUString& rContentId ); + + // Use OPTIONS method to retrieve the type of the Web resource + /// @throws css::uno::Exception + void getResourceOptions( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + DAVOptions& rDAVOptions, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed = nullptr); + + static bool isResourceAvailable( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + DAVOptions& rDAVOptions ); + + static void removeCachedPropertyNames( const OUString & rURL ); + +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx b/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx new file mode 100644 index 000000000..d8ff4ff6f --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavcontentcaps.cxx @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <memory> +#include <set> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ucb/CommandInfo.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.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/uno/Sequence.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/ucb/Link.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/LockEntry.hpp> +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" +#include "PropfindCache.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + + +// ContentProvider implementation. + + +void ContentProvider::getProperty( + const OUString & rPropName, beans::Property & rProp ) +{ + if ( !m_pProps ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_pProps ) + { + m_pProps.reset( new PropertyMap ); + + + // Fill map of known properties... + + + // Mandatory UCB properties. + m_pProps->insert( + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + // Optional UCB properties. + + m_pProps->insert( + beans::Property( + "DateCreated", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "DateModified", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "MediaType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Size", + -1, + cppu::UnoType<sal_Int64>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "BaseURI", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType< + uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + // Standard DAV properties. + + m_pProps->insert( + beans::Property( + DAVProperties::CREATIONDATE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::DISPLAYNAME, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLANGUAGE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLENGTH, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTTYPE , + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETETAG, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETLASTMODIFIED, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::LOCKDISCOVERY, + -1, + cppu::UnoType< + uno::Sequence< ucb::Lock >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::RESOURCETYPE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::SOURCE, + -1, + cppu::UnoType<uno::Sequence< ucb::Link >>::get(), + beans::PropertyAttribute::BOUND ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::SUPPORTEDLOCK, + -1, + cppu::UnoType<uno::Sequence< ucb::LockEntry >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::EXECUTABLE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + } + } + + + // Lookup property. + + + beans::Property aProp; + aProp.Name = rPropName; + const PropertyMap::const_iterator it = m_pProps->find( aProp ); + if ( it != m_pProps->end() ) + { + rProp = *it; + } + else + { + // All unknown props are treated as: + rProp = beans::Property( + rPropName, + - 1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ); + } +} + + +static PropertyNamesCache aStaticPropertyNamesCache; + +// static +void Content::removeCachedPropertyNames( const OUString & rURL ) +{ + aStaticPropertyNamesCache.removeCachedPropertyNames( rURL ); +} + +// Content implementation. + + +// virtual +uno::Sequence< beans::Property > Content::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + bool bTransient; + std::unique_ptr< DAVResourceAccess > xResAccess; + std::unique_ptr< ContentProperties > xCachedProps; + rtl::Reference< ContentProvider > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + bTransient = m_bTransient; + xResAccess.reset(new DAVResourceAccess(*m_xResAccess)); + if (m_xCachedProps) + xCachedProps.reset(new ContentProperties(*m_xCachedProps)); + xProvider.set( m_pProvider ); + } + + std::set< OUString > aPropSet; + + // No server access for just created (not yet committed) objects. + // Only a minimal set of properties supported at this stage. + if ( !bTransient ) + { + // Obtain all properties supported for this resource from server. + DAVOptions aDAVOptions; + getResourceOptions( xEnv, aDAVOptions, xResAccess ); + // only Class 1 is needed for PROPFIND + if ( aDAVOptions.isClass1() ) + { + try + { + std::vector< DAVResourceInfo > props; + OUString aTheURL( xResAccess->getURL() ); + PropertyNames aPropsNames( aTheURL ); + + if( !aStaticPropertyNamesCache.getCachedPropertyNames( aTheURL, aPropsNames ) ) + { + + xResAccess->PROPFIND( DAVZERO, props, xEnv ); + aPropsNames.setPropertiesNames( props ); + + aStaticPropertyNamesCache.addCachePropertyNames( aPropsNames ); + } + else + { + props = aPropsNames.getPropertiesNames(); + } + + // Note: vector should contain exactly one resource info, because + // we used a depth of DAVZERO for PROPFIND. + if (props.size() == 1) + { + aPropSet.insert( (*props.begin()).properties.begin(), + (*props.begin()).properties.end() ); + } + } + catch ( DAVException const & ) + { + } + } + } + + // Add DAV properties, map DAV properties to UCB properties. + bool bHasCreationDate = false; // creationdate <-> DateCreated + bool bHasGetLastModified = false; // getlastmodified <-> DateModified + bool bHasGetContentType = false; // getcontenttype <-> MediaType + bool bHasGetContentLength = false; // getcontentlength <-> Size + + bool bHasContentType = false; + bool bHasIsDocument = false; + bool bHasIsFolder = false; + bool bHasTitle = false; + bool bHasBaseURI = false; + bool bHasDateCreated = false; + bool bHasDateModified = false; + bool bHasMediaType = false; + bool bHasSize = false; + bool bHasCreatableInfos = false; + + { + for ( const auto& rProp : aPropSet ) + { + if ( !bHasCreationDate && + ( rProp == DAVProperties::CREATIONDATE ) ) + { + bHasCreationDate = true; + } + else if ( !bHasGetLastModified && + ( rProp == DAVProperties::GETLASTMODIFIED ) ) + { + bHasGetLastModified = true; + } + else if ( !bHasGetContentType && + ( rProp == DAVProperties::GETCONTENTTYPE ) ) + { + bHasGetContentType = true; + } + else if ( !bHasGetContentLength && + ( rProp == DAVProperties::GETCONTENTLENGTH ) ) + { + bHasGetContentLength = true; + } + else if ( !bHasContentType && rProp == "ContentType" ) + { + bHasContentType = true; + } + else if ( !bHasIsDocument && rProp == "IsDocument" ) + { + bHasIsDocument = true; + } + else if ( !bHasIsFolder && rProp == "IsFolder" ) + { + bHasIsFolder = true; + } + else if ( !bHasTitle && rProp == "Title" ) + { + bHasTitle = true; + } + else if ( !bHasBaseURI && rProp == "BaseURI" ) + { + bHasBaseURI = true; + } + else if ( !bHasDateCreated && rProp == "DateCreated" ) + { + bHasDateCreated = true; + } + else if ( !bHasDateModified && rProp == "DateModified" ) + { + bHasDateModified = true; + } + else if ( !bHasMediaType && rProp == "MediaType" ) + { + bHasMediaType = true; + } + else if ( !bHasSize && rProp == "Size" ) + { + bHasSize = true; + } + else if ( !bHasCreatableInfos && rProp == "CreatableContentsInfo" ) + { + bHasCreatableInfos = true; + } + } + } + + // Add mandatory properties. + if ( !bHasContentType ) + aPropSet.insert( + OUString( "ContentType" ) ); + + if ( !bHasIsDocument ) + aPropSet.insert( + OUString( "IsDocument" ) ); + + if ( !bHasIsFolder ) + aPropSet.insert( + OUString( "IsFolder" ) ); + + if ( !bHasTitle ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "Title" ) ); + } + + // Add optional properties. + + if ( !bHasBaseURI ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "BaseURI" ) ); + } + + if ( !bHasDateCreated && bHasCreationDate ) + aPropSet.insert( + OUString( "DateCreated" ) ); + + if ( !bHasDateModified && bHasGetLastModified ) + aPropSet.insert( + OUString( "DateModified" ) ); + + if ( !bHasMediaType && bHasGetContentType ) + aPropSet.insert( + OUString( "MediaType" ) ); + + if ( !bHasSize && bHasGetContentLength ) + aPropSet.insert( + OUString( "Size" ) ); + + if ( !bHasCreatableInfos ) + aPropSet.insert( + OUString( + "CreatableContentsInfo" ) ); + + // Add cached properties, if present and still missing. + if (xCachedProps) + { + const std::unique_ptr< PropertyValueMap > & xProps + = xCachedProps->getProperties(); + + for ( const auto& rEntry : *xProps ) + aPropSet.insert( rEntry.first ); + } + + // std::set -> uno::Sequence + sal_Int32 nCount = aPropSet.size(); + uno::Sequence< beans::Property > aProperties( nCount ); + + sal_Int32 n = 0; + beans::Property aProp; + + for ( const auto& rProp : aPropSet ) + { + xProvider->getProperty( rProp, aProp ); + aProperties[ n++ ] = aProp; + } + + return aProperties; +} + + +// virtual +uno::Sequence< ucb::CommandInfo > Content::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< ucb::CommandInfo > aCmdInfo( 10 ); + + + // Mandatory commands + + + aCmdInfo[ 0 ] = + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 1 ] = + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 2 ] = + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get() ); + aCmdInfo[ 3 ] = + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get() ); + + + // Optional standard commands + + + aCmdInfo[ 4 ] = + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() ); + aCmdInfo[ 5 ] = + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() ); + aCmdInfo[ 6 ] = + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() ); + + + // New commands + + + aCmdInfo[ 7 ] = + ucb::CommandInfo( + "post", + -1, + cppu::UnoType<ucb::PostCommandArgument2>::get() ); + aCmdInfo[ 8 ] = + ucb::CommandInfo( + "addProperty", + -1, + cppu::UnoType<ucb::PropertyCommandArgument>::get() ); + aCmdInfo[ 9 ] = + ucb::CommandInfo( + "removeProperty", + -1, + cppu::UnoType<OUString>::get() ); + + bool bFolder = false; + + try + { + bFolder = isFolder( xEnv ); + } + catch ( uno::Exception const & ) + { + return aCmdInfo; + } + + ResourceType eType = resourceTypeForLocks( xEnv ); + bool bSupportsLocking = ( eType == NOT_FOUND || eType == DAV ); + + sal_Int32 nPos = aCmdInfo.getLength(); + sal_Int32 nMoreCmds = ( bFolder ? 2 : 0 ) + ( bSupportsLocking ? 2 : 0 ); + if ( nMoreCmds ) + aCmdInfo.realloc( nPos + nMoreCmds ); + else + return aCmdInfo; + + if ( bFolder ) + { + + // Optional standard commands + + + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() ); + nPos++; + } + else + { + // no document-only commands at the moment. + } + + if ( bSupportsLocking ) + { + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "lock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "unlock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + } + return aCmdInfo; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavdatasupplier.cxx b/ucb/source/ucp/webdav-neon/webdavdatasupplier.cxx new file mode 100644 index 000000000..7675f6c5d --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavdatasupplier.cxx @@ -0,0 +1,487 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + *************************************************************************/ + +#include <sal/log.hxx> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include <memory> +#include <vector> +#include "webdavdatasupplier.hxx" +#include "webdavcontent.hxx" +#include "ContentProperties.hxx" +#include "NeonUri.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + +namespace webdav_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aId; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + std::shared_ptr<ContentProperties> const pData; + + explicit ResultListEntry(std::shared_ptr<ContentProperties> const& pEntry) + : pData(pEntry) + {} +}; + +} + +// ResultList. + + +typedef std::vector<std::unique_ptr<ResultListEntry>> ResultList; + + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + ResultList m_Results; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) + : m_xContent( rContent ), m_xContext( rxContext ), m_nOpenMode( nOpenMode ), + m_bCountFinal( false ), m_bThrowException( false ) {} +}; + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) +: m_pImpl( new DataSupplier_Impl( rxContext, rContent, nOpenMode ) ) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{ +} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + OUString aId = m_pImpl->m_Results[ nIndex ]->aId; + if ( !aId.isEmpty() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId = m_pImpl->m_xContent->getResourceAccess().getURL(); + + const ContentProperties& props(*(m_pImpl->m_Results[ nIndex ]->pData)); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += props.getEscapedTitle(); + + if ( props.isTrailingSlash() ) + aId += "/"; + + m_pImpl->m_Results[ nIndex ]->aId = aId; + return aId; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_Results[ nIndex ]->xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( !aId.isEmpty() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->m_Results[ nIndex ]->xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + + +// virtual +uno::Reference< ucb::XContent > +DataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->m_Results[ nIndex ]->xContent; + if ( xContent.is() ) + { + // Already cached. + return xContent; + } + } + + uno::Reference< ucb::XContentIdentifier > xId + = queryContentIdentifier( nIndex ); + if ( xId.is() ) + { + try + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_Results[ nIndex ]->xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException& ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + + +// virtual +bool DataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + // Result already present. + return true; + } + + // Obtain values... + if ( getData() ) + { + if (nIndex < m_pImpl->m_Results.size()) + { + // Result already present. + return true; + } + } + + return false; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + // Obtain values... + getData(); + + return m_pImpl->m_Results.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_pImpl->m_Results.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + { + uno::Reference< sdbc::XRow > xRow = m_pImpl->m_Results[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow + = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + *(m_pImpl->m_Results[ nIndex ]->pData), + rtl::Reference< ::ucbhelper::ContentProviderImplHelper >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_Results[ nIndex ]->xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if (nIndex < m_pImpl->m_Results.size()) + m_pImpl->m_Results[ nIndex ]->xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + + +bool DataSupplier::getData() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( !m_pImpl->m_bCountFinal ) + { + std::vector< OUString > propertyNames; + ContentProperties::UCBNamesToDAVNames( + getResultSet()->getProperties(), propertyNames ); + + // Append "resourcetype", if not already present. It's value is + // needed to get a valid ContentProperties::pIsFolder value, which + // is needed for OpenMode handling. + + bool isNoResourceType = std::none_of(propertyNames.begin(), propertyNames.end(), + [](const OUString& rName) { return rName == DAVProperties::RESOURCETYPE; }); + + if ( isNoResourceType ) + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + + std::vector< DAVResource > resources; + try + { + // propfind depth 1, get property values for parent AND for each + // child + m_pImpl->m_xContent->getResourceAccess() + .PROPFIND( DAVONE, + propertyNames, + resources, + getResultSet()->getEnvironment() ); +#if defined SAL_LOG_INFO + { + //print the resource for every URI returned + for ( const auto& rResource : resources ) + { + NeonUri aCurrURI( rResource.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + aCurrPath = NeonUri::unescape( aCurrPath ); + SAL_INFO( "ucb.ucp.webdav", "getData() - resource URL: <" << rResource.uri << ">, unescaped to: <" << aCurrPath << "> )" ); + for ( const auto& rProp : rResource.properties ) + { + SAL_INFO( "ucb.ucp.webdav", "PROPFIND - property name: " << rProp.Name ); + } + } + } +#endif + } + catch ( DAVException & ) + { + SAL_WARN( "ucb.ucp.webdav", "Running PROPFIND: DAVException" ); + m_pImpl->m_bThrowException = true; + } + + if ( !m_pImpl->m_bThrowException ) + { + try + { + NeonUri aURI( + m_pImpl->m_xContent->getResourceAccess().getURL() ); + OUString aPath = aURI.GetPath(); + + if ( aPath.endsWith("/") ) + aPath = aPath.copy( 0, aPath.getLength() - 1 ); + + aPath = NeonUri::unescape( aPath ); + bool bFoundParent = false; + + for (DAVResource & rRes : resources) + { + // Filter parent, which is contained somewhere(!) in + // the vector. + if ( !bFoundParent ) + { + try + { + NeonUri aCurrURI( rRes.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + if ( aCurrPath.endsWith("/") ) + aCurrPath + = aCurrPath.copy( + 0, + aCurrPath.getLength() - 1 ); + + aCurrPath = NeonUri::unescape( aCurrPath ); + if ( aPath == aCurrPath ) + { + bFoundParent = true; + continue; + } + } + catch ( DAVException const & ) + { + // do nothing, ignore error. continue. + } + } + + std::shared_ptr<ContentProperties> const + pContentProperties = std::make_shared<ContentProperties>(rRes); + + // Check resource against open mode. + switch ( m_pImpl->m_nOpenMode ) + { + case ucb::OpenMode::FOLDERS: + { + bool bFolder = false; + + const uno::Any & rValue + = pContentProperties->getValue( + "IsFolder" ); + rValue >>= bFolder; + + if ( !bFolder ) + continue; + + break; + } + + case ucb::OpenMode::DOCUMENTS: + { + bool bDocument = false; + + const uno::Any & rValue + = pContentProperties->getValue( + "IsDocument" ); + rValue >>= bDocument; + + if ( !bDocument ) + continue; + + break; + } + + case ucb::OpenMode::ALL: + default: + break; + } + + m_pImpl->m_Results.push_back( + std::make_unique<ResultListEntry>(pContentProperties)); + } + } + catch ( DAVException const & ) + { + } + } + + m_pImpl->m_bCountFinal = true; + + // Callback possible, because listeners may be informed! + aGuard.clear(); + getResultSet()->rowCountFinal(); + } + return !m_pImpl->m_bThrowException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx b/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx new file mode 100644 index 000000000..bd5b73340 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavdatasupplier.hxx @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVDATASUPPLIER_HXX + +#include <config_lgpl.h> +#include <memory> +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +namespace webdav_ucp { + +struct DataSupplier_Impl; +class Content; + +class DataSupplier : public ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +private: + bool getData(); + +public: + DataSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode); + + virtual ~DataSupplier() override; + + virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > + queryContentIdentifier( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContent > + queryContent( sal_uInt32 nIndex ) override; + + virtual bool getResult( sal_uInt32 nIndex ) override; + + virtual sal_uInt32 totalCount() override; + virtual sal_uInt32 currentCount() override; + virtual bool isCountFinal() override; + + virtual css::uno::Reference< css::sdbc::XRow > + queryPropertyValues( sal_uInt32 nIndex ) override; + virtual void releasePropertyValues( sal_uInt32 nIndex ) override; + + virtual void close() override; + + virtual void validate() override; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavprovider.cxx b/ucb/source/ucp/webdav-neon/webdavprovider.cxx new file mode 100644 index 000000000..2b0bd5bfe --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavprovider.cxx @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <sal/config.h> + +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <comphelper/processfactory.hxx> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/getcomponentcontext.hxx> +#include <ucbhelper/macros.hxx> +#include <cppuhelper/queryinterface.hxx> +#include "webdavprovider.hxx" +#include "webdavcontent.hxx" + +#include <osl/mutex.hxx> +#include <tools/urlobj.hxx> + +using namespace com::sun::star; +using namespace webdav_ucp; + + +// ContentProvider Implementation. + + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rxContext ) +: ::ucbhelper::ContentProviderImplHelper( rxContext ), + m_xDAVSessionFactory( new DAVSessionFactory ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{ +} + + +// XInterface methods. + +void SAL_CALL ContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL ContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider* >(this), + static_cast< lang::XServiceInfo* >(this), + static_cast< ucb::XContentProvider* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_3( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider ); + + +// XServiceInfo methods. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.WebDAVContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = new ContentProvider( ucbhelper::getComponentContext(rSMgr) ); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// XContentProvider methods. + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +ContentProvider::queryContent( + const uno::Reference< + ucb::XContentIdentifier >& Identifier ) +{ + // Check URL scheme... + INetURLObject aURL( Identifier->getContentIdentifier() ); + + if ( aURL.isSchemeEqualTo( INetProtocol::NotValid ) ) + throw ucb::IllegalIdentifierException(); + + if ( !aURL.isAnyKnownWebDAVScheme() ) + throw ucb::IllegalIdentifierException(); + + uno::Reference< ucb::XContentIdentifier > xCanonicId; + + if (aURL.isSchemeEqualTo( INetProtocol::VndSunStarWebdav ) || + aURL.isSchemeEqualTo(DAV_URL_SCHEME) || + aURL.isSchemeEqualTo( WEBDAV_URL_SCHEME ) ) + { + aURL.changeScheme( INetProtocol::Http ); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else if ( aURL.isSchemeEqualTo( VNDSUNSTARWEBDAVS_URL_SCHEME ) || + aURL.isSchemeEqualTo( DAVS_URL_SCHEME ) || + aURL.isSchemeEqualTo( WEBDAVS_URL_SCHEME )) + { + aURL.changeScheme( INetProtocol::Https ); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else + { + xCanonicId = Identifier; + } + + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference< ucb::XContent > xContent + = queryExistingContent( xCanonicId ).get(); + if ( xContent.is() ) + return xContent; + + // Create a new content. + + try + { + xContent = new ::webdav_ucp::Content( + m_xContext, this, xCanonicId, m_xDAVSessionFactory ); + registerNewContent( xContent ); + } + catch ( ucb::ContentCreationException const & ) + { + throw ucb::IllegalIdentifierException(); + } + + if ( !xContent->getIdentifier().is() ) + throw ucb::IllegalIdentifierException(); + + return xContent; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavprovider.hxx b/ucb/source/ucp/webdav-neon/webdavprovider.hxx new file mode 100644 index 000000000..80556ab23 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavprovider.hxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVPROVIDER_HXX + +#include <memory> +#include <config_lgpl.h> +#include <rtl/ref.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "DAVSessionFactory.hxx" +#include <ucbhelper/providerhelper.hxx> +#include "PropertyMap.hxx" + +namespace webdav_ucp { + + +// UNO service name for the provider. This name will be used by the UCB to +// create instances of the provider. +#define WEBDAV_CONTENT_PROVIDER_SERVICE_NAME "com.sun.star.ucb.WebDAVContentProvider" + +// URL scheme. This is the scheme the provider will be able to create +// contents for. The UCB will select the provider ( i.e. in order to create +// contents ) according to this scheme. +#define VNDSUNSTARWEBDAV_URL_SCHEME "vnd.sun.star.webdav" +#define VNDSUNSTARWEBDAVS_URL_SCHEME u"vnd.sun.star.webdavs" +#define HTTP_URL_SCHEME "http" +#define HTTPS_URL_SCHEME "https" +#define DAV_URL_SCHEME u"dav" +#define DAVS_URL_SCHEME u"davs" +#define WEBDAV_URL_SCHEME u"webdav" +#define WEBDAVS_URL_SCHEME u"webdavs" + +#define FTP_URL_SCHEME "ftp" + +#define HTTP_CONTENT_TYPE "application/" HTTP_URL_SCHEME "-content" + +#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE +#define WEBDAV_COLLECTION_TYPE "application/" VNDSUNSTARWEBDAV_URL_SCHEME "-collection" + + +class ContentProvider : public ::ucbhelper::ContentProviderImplHelper +{ + rtl::Reference< DAVSessionFactory > m_xDAVSessionFactory; + std::unique_ptr<PropertyMap> m_pProps; + +public: + explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~ContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + + // Non-interface methods. + + void getProperty( const OUString & rPropName, + css::beans::Property & rProp ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavresultset.cxx b/ucb/source/ucp/webdav-neon/webdavresultset.cxx new file mode 100644 index 000000000..be4fadc9b --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavresultset.cxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + + +/************************************************************************** + TODO + ************************************************************************** + + - This implementation is not a dynamic result set!!! It only implements + the necessary interfaces, but never recognizes/notifies changes!!! + + *************************************************************************/ +#include "webdavresultset.hxx" + +using namespace com::sun::star; +using namespace webdav_ucp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const ucb::OpenCommandArgument2& rCommand, + const uno::Reference< ucb::XCommandEnvironment >& rxEnv ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ), + m_xEnv( rxEnv ) +{ +} + + +// Non-interface methods. + + +void DynamicResultSet::initStatic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); +} + + +void DynamicResultSet::initDynamic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); + m_xResultSet2 = m_xResultSet1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavresultset.hxx b/ucb/source/ucp/webdav-neon/webdavresultset.hxx new file mode 100644 index 000000000..470b16f25 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavresultset.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_NEON_WEBDAVRESULTSET_HXX + +#include <config_lgpl.h> +#include <rtl/ref.hxx> +#include <ucbhelper/resultsethelper.hxx> +#include "webdavcontent.hxx" +#include "webdavdatasupplier.hxx" + +namespace webdav_ucp { + +class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper +{ + rtl::Reference< Content > m_xContent; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + +private: + virtual void initStatic() override; + virtual void initDynamic() override; + +public: + DynamicResultSet( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav-neon/webdavservices.cxx b/ucb/source/ucp/webdav-neon/webdavservices.cxx new file mode 100644 index 000000000..b29955535 --- /dev/null +++ b/ucb/source/ucp/webdav-neon/webdavservices.cxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "webdavprovider.hxx" + +using namespace com::sun::star; + + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpdav1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( + pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // WebDAV Content Provider. + + + if ( ::webdav_ucp::ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ::webdav_ucp::ContentProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/AprEnv.cxx b/ucb/source/ucp/webdav/AprEnv.cxx new file mode 100644 index 000000000..a1e61e5f9 --- /dev/null +++ b/ucb/source/ucp/webdav/AprEnv.cxx @@ -0,0 +1,64 @@ +/* -*- 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 "AprEnv.hxx" + +namespace apr_environment +{ + +AprEnv::AprEnv() + : mpAprPool( nullptr ) +{ + apr_initialize(); + + apr_pool_create(&mpAprPool, nullptr); + + mpSerfLockStore = new http_dav_ucp::SerfLockStore(); +} + +AprEnv::~AprEnv() +{ + delete mpSerfLockStore; + + apr_pool_destroy(mpAprPool); + + apr_terminate(); +} + +/* static */ +AprEnv* AprEnv::getAprEnv() +{ + static AprEnv rAprEnv; + + return &rAprEnv; +} + +apr_pool_t* AprEnv::getAprPool() +{ + return mpAprPool; +} + +http_dav_ucp::SerfLockStore* AprEnv::getSerfLockStore() +{ + return mpSerfLockStore; +} + +} // namespace apr_environment + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/AprEnv.hxx b/ucb/source/ucp/webdav/AprEnv.hxx new file mode 100644 index 000000000..4590c64aa --- /dev/null +++ b/ucb/source/ucp/webdav/AprEnv.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX + +#include <apr_pools.h> +#include "SerfLockStore.hxx" + +namespace apr_environment +{ + +// singleton class providing environment for APR libraries +class AprEnv +{ + public: + ~AprEnv(); + + static AprEnv* getAprEnv(); + + apr_pool_t* getAprPool(); + + http_dav_ucp::SerfLockStore* getSerfLockStore(); + + private: + apr_pool_t* mpAprPool; + // SerfLockStore is a static object and has to be destroyed + // before AprEnv, so store it here. + http_dav_ucp::SerfLockStore* mpSerfLockStore; + + AprEnv(); + + AprEnv(const AprEnv&) = delete; + AprEnv& operator=(const AprEnv&) = delete; +}; + +} // namespace apr_environment + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_APRENV_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/ContentProperties.cxx b/ucb/source/ucp/webdav/ContentProperties.cxx new file mode 100644 index 000000000..85406e680 --- /dev/null +++ b/ucb/source/ucp/webdav/ContentProperties.cxx @@ -0,0 +1,593 @@ +/* -*- 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 <com/sun/star/util/DateTime.hpp> +#include "SerfUri.hxx" +#include "DAVResource.hxx" +#include "DAVProperties.hxx" +#include "DateTimeHelper.hxx" +#include "webdavprovider.hxx" +#include "ContentProperties.hxx" + +#include <sal/log.hxx> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +/* +============================================================================= + + Property Mapping + +============================================================================= +HTTP (entity header) WebDAV (property) UCB (property) +============================================================================= + +Allow +Content-Encoding +Content-Language getcontentlanguage +Content-Length getcontentlength Size +Content-Location +Content-MD5 +Content-Range +Content-Type getcontenttype MediaType +Expires +Last-Modified getlastmodified DateModified + creationdate DateCreated + resourcetype IsFolder,IsDocument,ContentType + displayname +ETag (actually getetag +a response header ) + lockdiscovery + supportedlock + source + Title (always taken from URI) + +============================================================================= + +Important: HTTP headers will not be mapped to DAV properties; only to UCB + properties. (Content-Length,Content-Type,Last-Modified) +*/ + + +// ContentProperties Implementation. + + +// static member! +uno::Any ContentProperties::m_aEmptyAny; + +ContentProperties::ContentProperties( const DAVResource& rResource ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + SAL_WARN_IF( !rResource.uri.getLength(), "ucb.ucp.webdav", + "ContentProperties ctor - Empty resource URI!" ); + + // Title + try + { + SerfUri aURI( rResource.uri ); + m_aEscapedTitle = aURI.GetPathBaseName(); + + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( aURI.GetPathBaseNameUnescaped() ), true ); + } + catch ( DAVException const & ) + { + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( + uno::makeAny( + OUString( "*** unknown ***" ) ), + true ); + } + + for ( const auto& rProp : rResource.properties ) + { + addProperty( rProp ); + } + + if ( rResource.uri.endsWith("/") ) + m_bTrailingSlash = true; +} + + +ContentProperties::ContentProperties( + const OUString & rTitle, bool bFolder ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::makeAny( rTitle ), true ); + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); +} + + +ContentProperties::ContentProperties( const OUString & rTitle ) +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ + (*m_xProps)[ OUString( "Title" ) ] + = PropertyValue( uno::makeAny( rTitle ), true ); +} + + +ContentProperties::ContentProperties() +: m_xProps( new PropertyValueMap ), + m_bTrailingSlash( false ) +{ +} + + +ContentProperties::ContentProperties( const ContentProperties & rOther ) +: m_aEscapedTitle( rOther.m_aEscapedTitle ), + m_xProps( rOther.m_xProps.get() + ? new PropertyValueMap( *rOther.m_xProps ) + : new PropertyValueMap ), + m_bTrailingSlash( rOther.m_bTrailingSlash ) +{ +} + + +bool ContentProperties::contains( const OUString & rName ) const +{ + if ( get( rName ) ) + return true; + else + return false; +} + + +const uno::Any & ContentProperties::getValue( + const OUString & rName ) const +{ + const PropertyValue * pProp = get( rName ); + if ( pProp ) + return pProp->value(); + else + return m_aEmptyAny; +} + + +const PropertyValue * ContentProperties::get( + const OUString & rName ) const +{ + PropertyValueMap::const_iterator it = m_xProps->find( rName ); + const PropertyValueMap::const_iterator end = m_xProps->end(); + + if ( it == end ) + { + it = std::find_if(m_xProps->cbegin(), end, + [&rName](const PropertyValueMap::value_type& rEntry) { + return rEntry.first.equalsIgnoreAsciiCase( rName ); + }); + if ( it != end ) + return &(*it).second; + + return nullptr; + } + else + return &(*it).second; +} + + +// static +void ContentProperties::UCBNamesToDAVNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames, + bool bIncludeUnmatched /* = true */ ) +{ + + // Assemble list of DAV properties to obtain from server. + // Append DAV properties needed to obtain requested UCB props. + + + // DAV UCB + // creationdate <- DateCreated + // getlastmodified <- DateModified + // getcontenttype <- MediaType + // getcontentlength <- Size + // resourcetype <- IsFolder, IsDocument, ContentType + // (taken from URI) <- Title + + bool bCreationDate = false; + bool bLastModified = false; + bool bContentType = false; + bool bContentLength = false; + bool bResourceType = false; + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "Title" ) + { + // Title is always obtained from resource's URI. + continue; + } + else if ( rProp.Name == "DateCreated" || + ( rProp.Name == DAVProperties::CREATIONDATE ) ) + { + if ( !bCreationDate ) + { + propertyNames.push_back( DAVProperties::CREATIONDATE ); + bCreationDate = true; + } + } + else if ( rProp.Name == "DateModified" || + ( rProp.Name == DAVProperties::GETLASTMODIFIED ) ) + { + if ( !bLastModified ) + { + propertyNames.push_back( + DAVProperties::GETLASTMODIFIED ); + bLastModified = true; + } + } + else if ( rProp.Name == "MediaType" || + ( rProp.Name == DAVProperties::GETCONTENTTYPE ) ) + { + if ( !bContentType ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTTYPE ); + bContentType = true; + } + } + else if ( rProp.Name == "Size" || + ( rProp.Name == DAVProperties::GETCONTENTLENGTH ) ) + { + if ( !bContentLength ) + { + propertyNames.push_back( + DAVProperties::GETCONTENTLENGTH ); + bContentLength = true; + } + } + else if ( rProp.Name == "ContentType" || + rProp.Name == "IsDocument" || + rProp.Name == "IsFolder" || + ( rProp.Name == DAVProperties::RESOURCETYPE ) ) + { + if ( !bResourceType ) + { + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + bResourceType = true; + } + } + else + { + if ( bIncludeUnmatched ) + propertyNames.push_back( rProp.Name ); + } + } +} + + +// static +void ContentProperties::UCBNamesToHTTPNames( + const uno::Sequence< beans::Property > & rProps, + std::vector< OUString > & propertyNames, + bool bIncludeUnmatched /* = true */ ) +{ + + // Assemble list of HTTP header names to obtain from server. + // Append HTTP headers needed to obtain requested UCB props. + + + // HTTP UCB + // Last-Modified <- DateModified + // Content-Type <- MediaType + // Content-Length <- Size + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const beans::Property & rProp = rProps[ n ]; + + if ( rProp.Name == "DateModified" ) + { + propertyNames.push_back( OUString( "Last-Modified" ) ); + } + else if ( rProp.Name == "MediaType" ) + { + propertyNames.push_back( OUString( "Content-Type" ) ); + } + else if ( rProp.Name == "Size" ) + { + propertyNames.push_back( OUString( "Content-Length" ) ); + } + else + { + if ( bIncludeUnmatched ) + propertyNames.push_back( rProp.Name ); + } + } +} + + +bool ContentProperties::containsAllNames( + const uno::Sequence< beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const +{ + rNamesNotContained.clear(); + + sal_Int32 nCount = rProps.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString & rName = rProps[ n ].Name; + if ( !contains( rName ) ) + { + // Not found. + rNamesNotContained.push_back( rName ); + } + } + + return ( rNamesNotContained.size() == 0 ); +} + + +void ContentProperties::addProperties( + const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ) +{ + for ( const OUString & rName : rProps ) + { + if ( !contains( rName ) ) // ignore duplicates + { + const PropertyValue * pProp = rContentProps.get( rName ); + if ( pProp ) + { + // Add it. + addProperty( rName, pProp->value(), pProp->isCaseSensitive() ); + } + else + { + addProperty( rName, uno::Any(), false ); + } + } + } +} + + +void ContentProperties::addProperties( const ContentProperties & rProps ) +{ + for ( const auto& rProp : *rProps.m_xProps ) + { + addProperty( + rProp.first, rProp.second.value(), rProp.second.isCaseSensitive() ); + } +} + + +void ContentProperties::addProperties( + const std::vector< DAVPropertyValue > & rProps ) +{ + for ( const auto& rProp : rProps ) + { + addProperty( rProp ); + } +} + + +void ContentProperties::addProperty( const DAVPropertyValue & rProp ) +{ + addProperty( rProp.Name, rProp.Value, rProp.IsCaseSensitive ); +} + + +void ContentProperties::addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ) +{ + if ( rName == DAVProperties::CREATIONDATE ) + { + // Map DAV:creationdate to UCP:DateCreated + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateCreated" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::DISPLAYNAME ) ) + // { + // } + // else if ( rName.equals( DAVProperties::GETCONTENTLANGUAGE ) ) + // { + // } + else if ( rName == DAVProperties::GETCONTENTLENGTH ) + { + // Map DAV:getcontentlength to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName == "Content-Length" ) + { + // Do NOT map Content-Length entity header to DAV:getcontentlength! + // Only DAV resources have this property. + + // Map Content-Length entity header to UCP:Size + OUString aValue; + rValue >>= aValue; + + (*m_xProps)[ OUString( "Size" ) ] + = PropertyValue( uno::makeAny( aValue.toInt64() ), true ); + } + else if ( rName == DAVProperties::GETCONTENTTYPE ) + { + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + else if ( rName == "Content-Type" ) + { + // Do NOT map Content-Type entity header to DAV:getcontenttype! + // Only DAV resources have this property. + + // Map DAV:getcontenttype to UCP:MediaType (1:1) + (*m_xProps)[ OUString( "MediaType" ) ] + = PropertyValue( rValue, true ); + } + // else if ( rName.equals( DAVProperties::GETETAG ) ) + // { + // } + else if ( rName == DAVProperties::GETLASTMODIFIED ) + { + // Map the DAV:getlastmodified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + else if ( rName == "Last-Modified" ) + { + // Do not map Last-Modified entity header to DAV:getlastmodified! + // Only DAV resources have this property. + + // Map the Last-Modified entity header to UCP:DateModified + OUString aValue; + rValue >>= aValue; + util::DateTime aDate; + DateTimeHelper::convert( aValue, aDate ); + + (*m_xProps)[ OUString( "DateModified" ) ] + = PropertyValue( uno::makeAny( aDate ), true ); + } + // else if ( rName.equals( DAVProperties::LOCKDISCOVERY ) ) + // { + // } + else if ( rName == DAVProperties::RESOURCETYPE ) + { + OUString aValue; + rValue >>= aValue; + + // Map DAV:resourcetype to UCP:IsFolder, UCP:IsDocument, UCP:ContentType + bool bFolder = + aValue.equalsIgnoreAsciiCase( "collection" ); + + (*m_xProps)[ OUString( "IsFolder" ) ] + = PropertyValue( uno::makeAny( bFolder ), true ); + (*m_xProps)[ OUString( "IsDocument" ) ] + = PropertyValue( uno::makeAny( bool( !bFolder ) ), true ); + (*m_xProps)[ OUString( "ContentType" ) ] + = PropertyValue( uno::makeAny( bFolder + ? OUString( WEBDAV_COLLECTION_TYPE ) + : OUString( WEBDAV_CONTENT_TYPE ) ), true ); + } + // else if ( rName.equals( DAVProperties::SUPPORTEDLOCK ) ) + // { + // } + + // Save property. + (*m_xProps)[ rName ] = PropertyValue( rValue, bIsCaseSensitive ); +} + + +// CachableContentProperties Implementation. + + +namespace +{ + bool isCachable( OUString const & rName, + bool isCaseSensitive ) + { + const OUString aNonCachableProps [] = + { + DAVProperties::LOCKDISCOVERY, + + DAVProperties::GETETAG, + OUString( "ETag" ), + + OUString( "DateModified" ), + OUString( "Last-Modified" ), + DAVProperties::GETLASTMODIFIED, + + OUString( "Size" ), + OUString( "Content-Length" ), + DAVProperties::GETCONTENTLENGTH, + + OUString( "Date" ) + }; + + for ( sal_uInt32 n = 0; + n < ( sizeof( aNonCachableProps ) + / sizeof( aNonCachableProps[ 0 ] ) ); + ++n ) + { + if ( isCaseSensitive ) + { + if ( rName.equals( aNonCachableProps[ n ] ) ) + return false; + } + else + if ( rName.equalsIgnoreAsciiCase( aNonCachableProps[ n ] ) ) + return false; + } + return true; + } + +} // namespace + + +CachableContentProperties::CachableContentProperties( + const ContentProperties & rProps ) +{ + addProperties( rProps ); +} + + +void CachableContentProperties::addProperties( + const ContentProperties & rProps ) +{ + const std::unique_ptr< PropertyValueMap > & props = rProps.getProperties(); + + for ( const auto& rProp : *props ) + { + if ( isCachable( rProp.first, rProp.second.isCaseSensitive() ) ) + m_aProps.addProperty( rProp.first, + rProp.second.value(), + rProp.second.isCaseSensitive() ); + } +} + + +void CachableContentProperties::addProperties( + const std::vector< DAVPropertyValue > & rProps ) +{ + for ( const auto& rProp : rProps ) + { + if ( isCachable( rProp.Name, rProp.IsCaseSensitive ) ) + m_aProps.addProperty( rProp ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/ContentProperties.hxx b/ucb/source/ucp/webdav/ContentProperties.hxx new file mode 100644 index 000000000..a48383f8e --- /dev/null +++ b/ucb/source/ucp/webdav/ContentProperties.hxx @@ -0,0 +1,184 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX + +#include <memory> +#include <unordered_map> +#include <vector> +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "DAVResource.hxx" + +namespace com::sun::star::beans { + struct Property; +} + +namespace http_dav_ucp +{ + +struct DAVResource; + +// PropertyValueMap. +class PropertyValue +{ +private: + css::uno::Any m_aValue; + bool m_bIsCaseSensitive; + +public: + PropertyValue() + : m_bIsCaseSensitive( true ) {} + + explicit PropertyValue( const css::uno::Any & rValue, + bool bIsCaseSensitive ) + : m_aValue( rValue), + m_bIsCaseSensitive( bIsCaseSensitive ) {} + + bool isCaseSensitive() const { return m_bIsCaseSensitive; } + const css::uno::Any & value() const { return m_aValue; } + +}; + +typedef std::unordered_map< OUString, PropertyValue > PropertyValueMap; + +class ContentProperties +{ +public: + ContentProperties(); + + explicit ContentProperties( const DAVResource& rResource ); + + // Mini props for transient contents. + ContentProperties( const OUString & rTitle, bool bFolder ); + + // Micro props for non-existing contents. + explicit ContentProperties( const OUString & rTitle ); + + ContentProperties( const ContentProperties & rOther ); + + bool contains( const OUString & rName ) const; + + const css::uno::Any& getValue( const OUString & rName ) const; + + // Maps the UCB property names contained in rProps with their DAV property + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector unless bIncludeUnmatched is set to false. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::PROPFIND. The result from PROPFIND + // (vector< DAVResource >) can be used to create a ContentProperties + // instance which can map DAV properties back to UCB properties. + static void UCBNamesToDAVNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources, + bool bIncludeUnmatched = true ); + + // Maps the UCB property names contained in rProps with their HTTP header + // counterparts, if possible. All unmappable properties will be included + // unchanged in resulting vector unless bIncludeUnmatched is set to false. + // The vector filled by this method can directly be handed over to + // DAVResourceAccess::HEAD. The result from HEAD (vector< DAVResource >) + // can be used to create a ContentProperties instance which can map header + // names back to UCB properties. + static void UCBNamesToHTTPNames( const css::uno::Sequence< css::beans::Property > & rProps, + std::vector< OUString > & resources, + bool bIncludeUnmatched = true ); + + // return true, if all properties contained in rProps are contained in + // this ContentProperties instance. Otherwise, false will be returned. + // rNamesNotContained contain the missing names. + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const; + + // adds all properties described by rProps that are actually contained in + // rContentProps to this instance. In case of duplicates the value + // already contained in this will left unchanged. + void addProperties( const std::vector< OUString > & rProps, + const ContentProperties & rContentProps ); + + // overwrites probably existing entries. + void addProperties( const ContentProperties & rProps ); + + // overwrites probably existing entries. + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + // overwrites probably existing entry. + void addProperty( const OUString & rName, + const css::uno::Any & rValue, + bool bIsCaseSensitive ); + + // overwrites probably existing entry. + void addProperty( const DAVPropertyValue & rProp ); + + bool isTrailingSlash() const { return m_bTrailingSlash; } + + const OUString & getEscapedTitle() const { return m_aEscapedTitle; } + + // Not good to expose implementation details, but this is actually an + // internal class. + const std::unique_ptr< PropertyValueMap > & getProperties() const + { return m_xProps; } + +private: + OUString m_aEscapedTitle; + std::unique_ptr< PropertyValueMap > m_xProps; + bool m_bTrailingSlash; + + static css::uno::Any m_aEmptyAny; + + ContentProperties & operator=( const ContentProperties & ); // n.i. + + const PropertyValue * get( const OUString & rName ) const; +}; + +class CachableContentProperties +{ +private: + ContentProperties m_aProps; + + CachableContentProperties & operator=( const CachableContentProperties & ); // n.i. + CachableContentProperties( const CachableContentProperties & ); // n.i. + +public: + explicit CachableContentProperties( const ContentProperties & rProps ); + + void addProperties( const ContentProperties & rProps ); + + void addProperties( const std::vector< DAVPropertyValue > & rProps ); + + bool containsAllNames( + const css::uno::Sequence< css::beans::Property >& rProps, + std::vector< OUString > & rNamesNotContained ) const + { return m_aProps.containsAllNames( rProps, rNamesNotContained ); } + + const css::uno::Any & + getValue( const OUString & rName ) const + { return m_aProps.getValue( rName ); } + + operator const ContentProperties & () const { return m_aProps; } +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_CONTENTPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVAuthListener.hxx b/ucb/source/ucp/webdav/DAVAuthListener.hxx new file mode 100644 index 000000000..95f61c0a0 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVAuthListener.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX + +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + +class DAVAuthListener : public salhelper::SimpleReferenceObject +{ + public: + virtual int authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials, + bool bUsePreviousCredentials = true ) = 0; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx b/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx new file mode 100644 index 000000000..fc3f9a845 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVAuthListenerImpl.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENERIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVAUTHLISTENERIMPL_HXX + +#include "DAVAuthListener.hxx" +#include <com/sun/star/ucb/XCommandEnvironment.hpp> + + +namespace http_dav_ucp +{ + + + + + class DAVAuthListener_Impl : public DAVAuthListener + { + public: + + DAVAuthListener_Impl( + const css::uno::Reference<css::ucb::XCommandEnvironment>& xEnv, + const OUString & inURL ) + : m_xEnv( xEnv ), m_aURL( inURL ) + { + } + + virtual int authenticate( const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials, + bool bUsePreviousCredentials = true ) override; + private: + + const css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + const OUString m_aURL; + + OUString m_aPrevPassword; + OUString m_aPrevUsername; + }; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVException.hxx b/ucb/source/ucp/webdav/DAVException.hxx new file mode 100644 index 000000000..3b21067e5 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVException.hxx @@ -0,0 +1,168 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX + +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + + +// HTTP/WebDAV status codes + + +const sal_uInt16 SC_NONE = 0; + +// 1xx (Informational - no errors) +const sal_uInt16 SC_CONTINUE = 100; +const sal_uInt16 SC_SWITCHING_PROTOCOLS = 101; +// DAV extensions +const sal_uInt16 SC_PROCESSING = 102; + +//2xx (Successful - no errors) +const sal_uInt16 SC_OK = 200; +const sal_uInt16 SC_CREATED = 201; +const sal_uInt16 SC_ACCEPTED = 202; +const sal_uInt16 SC_NON_AUTHORITATIVE_INFORMATION = 203; +const sal_uInt16 SC_NO_CONTENT = 204; +const sal_uInt16 SC_RESET_CONTENT = 205; +const sal_uInt16 SC_PARTIAL_CONTENT = 206; +// DAV extensions +const sal_uInt16 SC_MULTISTATUS = 207; + +//3xx (Redirection) +const sal_uInt16 SC_MULTIPLE_CHOICES = 300; +const sal_uInt16 SC_MOVED_PERMANENTLY = 301; +const sal_uInt16 SC_MOVED_TEMPORARILY = 302; +const sal_uInt16 SC_SEE_OTHER = 303; +const sal_uInt16 SC_NOT_MODIFIED = 304; +const sal_uInt16 SC_USE_PROXY = 305; +const sal_uInt16 SC_TEMPORARY_REDIRECT = 307; + +//4xx (Client error) +const sal_uInt16 SC_BAD_REQUEST = 400; +const sal_uInt16 SC_UNAUTHORIZED = 401; +const sal_uInt16 SC_PAYMENT_REQUIRED = 402; +const sal_uInt16 SC_FORBIDDEN = 403; +const sal_uInt16 SC_NOT_FOUND = 404; +const sal_uInt16 SC_METHOD_NOT_ALLOWED = 405; +const sal_uInt16 SC_NOT_ACCEPTABLE = 406; +const sal_uInt16 SC_PROXY_AUTHENTICATION_REQUIRED = 407; +const sal_uInt16 SC_REQUEST_TIMEOUT = 408; +const sal_uInt16 SC_CONFLICT = 409; +const sal_uInt16 SC_GONE = 410; +const sal_uInt16 SC_LENGTH_REQUIRED = 411; +const sal_uInt16 SC_PRECONDITION_FAILED = 412; +const sal_uInt16 SC_REQUEST_ENTITY_TOO_LARGE = 413; +const sal_uInt16 SC_REQUEST_URI_TOO_LONG = 414; +const sal_uInt16 SC_UNSUPPORTED_MEDIA_TYPE = 415; +const sal_uInt16 SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; +const sal_uInt16 SC_EXPECTATION_FAILED = 417; +// DAV extensions +const sal_uInt16 SC_UNPROCESSABLE_ENTITY = 422; +const sal_uInt16 SC_LOCKED = 423; +const sal_uInt16 SC_FAILED_DEPENDENCY = 424; + +//5xx (Server error) +const sal_uInt16 SC_INTERNAL_SERVER_ERROR = 500; +const sal_uInt16 SC_NOT_IMPLEMENTED = 501; +const sal_uInt16 SC_BAD_GATEWAY = 502; +const sal_uInt16 SC_SERVICE_UNAVAILABLE = 503; +const sal_uInt16 SC_GATEWAY_TIMEOUT = 504; +const sal_uInt16 SC_HTTP_VERSION_NOT_SUPPORTED = 505; +// DAV extensions +const sal_uInt16 SC_INSUFFICIENT_STORAGE = 507; + + +class DAVException : public std::exception +{ + public: + enum ExceptionCode { + DAV_HTTP_ERROR = 0, // Generic error, + // mData = server error message, + // mStatusCode = HTTP status code + DAV_HTTP_LOOKUP, // Name lookup failed, + // mData = server[:port] + DAV_HTTP_NOAUTH, // No User authentication data provided - e.g., user aborts corresponding dialog + // mData = server[:port] + DAV_HTTP_AUTH, // User authentication failed on server, + // mData = server[:port] + DAV_HTTP_AUTHPROXY, // User authentication failed on proxy, + // mData = proxy server[:port] + DAV_HTTP_CONNECT, // Could not connect to server, + // mData = server[:port] + DAV_HTTP_TIMEOUT, // Connection timed out + // mData = server[:port] + DAV_HTTP_FAILED, // The precondition failed + // mData = server[:port] + DAV_HTTP_RETRY, // Retry request + // mData = server[:port] + DAV_HTTP_REDIRECT, // Request was redirected, + // mData = new URL + DAV_SESSION_CREATE, // session creation error, + // mData = server[:port] + DAV_INVALID_ARG, // invalid argument + + DAV_LOCK_EXPIRED, // DAV lock expired + + DAV_NOT_LOCKED, // not locked + + DAV_LOCKED_SELF, // locked by this OOo session + + DAV_LOCKED // locked by third party + }; + + private: + ExceptionCode mExceptionCode; + OUString mData; + sal_uInt16 mStatusCode; + + public: + explicit DAVException( ExceptionCode inExceptionCode ) + : mExceptionCode( inExceptionCode ) + , mData() + , mStatusCode( SC_NONE ) + {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData ) + : mExceptionCode( inExceptionCode ) + , mData( rData ) + , mStatusCode( SC_NONE ) + {}; + DAVException( ExceptionCode inExceptionCode, + const OUString & rData, + sal_uInt16 nStatusCode ) + : mExceptionCode( inExceptionCode ) + , mData( rData ) + , mStatusCode( nStatusCode ) + {}; + + const ExceptionCode & getError() const { return mExceptionCode; } + const OUString & getData() const { return mData; } + sal_uInt16 getStatus() const { return mStatusCode; } +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVEXCEPTION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVProperties.cxx b/ucb/source/ucp/webdav/DAVProperties.cxx new file mode 100644 index 000000000..a08a8488d --- /dev/null +++ b/ucb/source/ucp/webdav/DAVProperties.cxx @@ -0,0 +1,222 @@ +/* -*- 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 <string.h> +#include "DAVProperties.hxx" +#include <rtl/ustrbuf.hxx> + +using namespace http_dav_ucp; + +const OUString DAVProperties::CREATIONDATE = + OUString( "DAV:creationdate" ); +const OUString DAVProperties::DISPLAYNAME = + OUString( "DAV:displayname" ); +const OUString DAVProperties::GETCONTENTLANGUAGE = + OUString( "DAV:getcontentlanguage" ); +const OUString DAVProperties::GETCONTENTLENGTH = + OUString( "DAV:getcontentlength" ); +const OUString DAVProperties::GETCONTENTTYPE = + OUString( "DAV:getcontenttype" ); +const OUString DAVProperties::GETETAG = + OUString( "DAV:getetag" ); +const OUString DAVProperties::GETLASTMODIFIED = + OUString( "DAV:getlastmodified" ); +const OUString DAVProperties::LOCKDISCOVERY = + OUString( "DAV:lockdiscovery" ); +const OUString DAVProperties::RESOURCETYPE = + OUString( "DAV:resourcetype" ); +const OUString DAVProperties::SUPPORTEDLOCK = + OUString( "DAV:supportedlock" ); + +const OUString DAVProperties::EXECUTABLE = + OUString( "http://apache.org/dav/props/executable" ); + + +// static +void DAVProperties::createSerfPropName( const OUString & rFullName, + SerfPropName & rName ) +{ + if ( rFullName.startsWith( "DAV:" ) ) + { + rName.nspace = "DAV:"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( RTL_CONSTASCII_LENGTH( "DAV:" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + rName.nspace = "http://apache.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://apache.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( + rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ), + RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if ( rFullName.startsWith( "<prop:" ) ) + { + // Support for 3rd party namespaces/props + + OString aFullName + = OUStringToOString( rFullName, RTL_TEXTENCODING_UTF8 ); + + // Format: <prop:the_propname xmlns:prop="the_namespace"> + + sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" ); + sal_Int32 nLen = aFullName.indexOf( ' ' ) - nStart; + rName.name = strdup( aFullName.copy( nStart, nLen ).getStr() ); + + nStart = aFullName.indexOf( '=', nStart + nLen ) + 2; // after =" + nLen = aFullName.getLength() - RTL_CONSTASCII_LENGTH( "\">" ) - nStart; + rName.nspace = strdup( aFullName.copy( nStart, nLen ).getStr() ); + } + else + { + // Add our namespace to our own properties. + rName.nspace = "http://ucb.openoffice.org/dav/props/"; + rName.name + = strdup( OUStringToOString( rFullName, + RTL_TEXTENCODING_UTF8 ).getStr() ); + } +} + + +// static +void DAVProperties::createUCBPropName( const char * nspace, + const char * name, + OUString & rFullName ) +{ + OUString aNameSpace + = OStringToOUString( nspace, RTL_TEXTENCODING_UTF8 ); + OUString aName + = OStringToOUString( name, RTL_TEXTENCODING_UTF8 ); + + if ( !aNameSpace.getLength() ) + { + // Some servers send XML without proper namespaces. Assume "DAV:" + // in this case, if name is a well-known dav property name. + // Although this is not 100% correct, it solves many problems. + + if ( DAVProperties::RESOURCETYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::SUPPORTEDLOCK.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::LOCKDISCOVERY.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::CREATIONDATE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::DISPLAYNAME.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLANGUAGE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTLENGTH.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETCONTENTTYPE.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETETAG.matchIgnoreAsciiCase( aName, 4 ) || + DAVProperties::GETLASTMODIFIED.matchIgnoreAsciiCase( aName, 4 ) ) + { + aNameSpace = "DAV:"; + } + } + + // Note: Concatenating strings BEFORE comparing against known namespaces + // is important. See RFC 2815 ( 23.4.2 Meaning of Qualified Names ). + rFullName = aNameSpace; + rFullName += aName; + + if ( rFullName.startsWith( "DAV:" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://apache.org/dav/props/" ) ) + { + // Okay, Just concat strings. + } + else if ( rFullName.startsWith( "http://ucb.openoffice.org/dav/props/" ) ) + { + // Remove namespace from our own properties. + rFullName = rFullName.copy( + RTL_CONSTASCII_LENGTH( + "http://ucb.openoffice.org/dav/props/" ) ); + } + else + { + // Create property name that encodes, namespace and name ( XML ). + rFullName = "<prop:"; + rFullName += aName; + rFullName += " xmlns:prop=\""; + rFullName += aNameSpace; + rFullName += "\">"; + } +} + + +// static +bool DAVProperties::isUCBDeadProperty( const SerfPropName & rName ) +{ + return ( rName.nspace && + ( rtl_str_compareIgnoreAsciiCase( + rName.nspace, "http://ucb.openoffice.org/dav/props/" ) + == 0 ) ); +} + +bool DAVProperties::isUCBSpecialProperty(const OUString& rFullName, OUString& rParsedName) +{ + sal_Int32 nLen = rFullName.getLength(); + if ( nLen <= 0 || + !rFullName.startsWith( "<prop:" ) || + !rFullName.endsWith( "\">" ) ) + return false; + + sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" ); + sal_Int32 nEnd = rFullName.indexOf( ' ', nStart ); + if ( nEnd == -1 ) + return false; + + OUString sPropName = rFullName.copy( nStart, nEnd - nStart ); + if ( !sPropName.getLength() ) + return false; + + // TODO skip whitespaces? + if ( !rFullName.match( "xmlns:prop=\"", ++nEnd ) ) + return false; + + nStart = nEnd + RTL_CONSTASCII_LENGTH( "xmlns:prop=\"" ); + nEnd = rFullName.indexOf( '"', nStart ); + if ( nEnd != nLen - RTL_CONSTASCII_LENGTH( "\">" ) ) + return false; + + OUString sNamesp = rFullName.copy( nStart, nEnd - nStart ); + if ( !( nLen = sNamesp.getLength() ) ) + return false; + + OUStringBuffer aBuff( sNamesp ); + if ( sNamesp[nLen - 1] != '/' ) + aBuff.append( '/' ); + aBuff.append( sPropName ); + rParsedName = aBuff.makeStringAndClear(); + + return rParsedName.getLength(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVProperties.hxx b/ucb/source/ucp/webdav/DAVProperties.hxx new file mode 100644 index 000000000..926afd6a7 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVProperties.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX + +#include <rtl/ustring.hxx> + +namespace http_dav_ucp +{ + +typedef struct { const char *nspace, *name; } SerfPropName; + +struct DAVProperties +{ + static const OUString CREATIONDATE; + static const OUString DISPLAYNAME; + static const OUString GETCONTENTLANGUAGE; + static const OUString GETCONTENTLENGTH; + static const OUString GETCONTENTTYPE; + static const OUString GETETAG; + static const OUString GETLASTMODIFIED; + static const OUString LOCKDISCOVERY; + static const OUString RESOURCETYPE; + static const OUString SUPPORTEDLOCK; + static const OUString EXECUTABLE; + + static void createSerfPropName( const OUString & rFullName, + SerfPropName & rName ); + static void createUCBPropName ( const char * nspace, + const char * name, + OUString & rFullName ); + + static bool isUCBDeadProperty( const SerfPropName & rName ); + static bool isUCBSpecialProperty( const OUString & rFullName, + OUString & rParsedName ); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVPROPERTIES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVRequestEnvironment.hxx b/ucb/source/ucp/webdav/DAVRequestEnvironment.hxx new file mode 100644 index 000000000..1b1faff89 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVRequestEnvironment.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX + +#include <vector> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include "DAVAuthListener.hxx" + +namespace http_dav_ucp +{ + typedef std::pair< OUString, OUString > DAVRequestHeader; + typedef std::vector< DAVRequestHeader > DAVRequestHeaders; + +struct DAVRequestEnvironment +{ + OUString m_aRequestURI; + rtl::Reference< DAVAuthListener > m_xAuthListener; + DAVRequestHeaders m_aRequestHeaders; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + +DAVRequestEnvironment( const OUString & rRequestURI, + const rtl::Reference< DAVAuthListener > & xListener, + const DAVRequestHeaders & rRequestHeaders, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv) + : m_aRequestURI( rRequestURI ), + m_xAuthListener( xListener ), + m_aRequestHeaders( rRequestHeaders ), + m_xEnv( xEnv ){} + + DAVRequestEnvironment() {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVREQUESTENVIRONMENT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVResource.hxx b/ucb/source/ucp/webdav/DAVResource.hxx new file mode 100644 index 000000000..aca9f3846 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResource.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX + +#include <vector> + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ + +struct DAVPropertyValue +{ + OUString Name; + css::uno::Any Value; + bool IsCaseSensitive; + + DAVPropertyValue() : IsCaseSensitive( true ) {} +}; + +struct DAVResource +{ + OUString uri; + std::vector< DAVPropertyValue > properties; + + DAVResource() {} + explicit DAVResource( const OUString & inUri ) : uri( inUri ) {} +}; + +struct DAVResourceInfo +{ + OUString uri; + std::vector < OUString > properties; + + explicit DAVResourceInfo( const OUString & inUri ) : uri( inUri ) {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVResourceAccess.cxx b/ucb/source/ucp/webdav/DAVResourceAccess.cxx new file mode 100644 index 000000000..90001a818 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResourceAccess.cxx @@ -0,0 +1,1116 @@ +/* -*- 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 <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/ucb/XWebDAVCommandEnvironment.hpp> + +#include <ucbhelper/simpleauthenticationrequest.hxx> +#include <comphelper/seekableinput.hxx> +#include <sal/log.hxx> + +#include "DAVAuthListenerImpl.hxx" +#include "DAVResourceAccess.hxx" + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/io/IOException.hpp> + +using namespace http_dav_ucp; +using namespace com::sun::star; + + +// DAVAuthListener_Impl Implementation. + + +// virtual +int DAVAuthListener_Impl::authenticate( + const OUString & inRealm, + const OUString & inHostName, + OUString & inoutUserName, + OUString & outPassWord, + bool bCanUseSystemCredentials, + bool bUsePreviousCredentials ) +{ + if ( m_xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = m_xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + // Providing previously retrieved credentials will cause the password + // container to reject these. Thus, the credential input dialog will be shown again. + // #102871# - Supply username and password from previous try. + // Password container service depends on this! + if ( inoutUserName.isEmpty() && bUsePreviousCredentials ) + inoutUserName = m_aPrevUsername; + + if ( outPassWord.isEmpty() && bUsePreviousCredentials ) + outPassWord = m_aPrevPassword; + + rtl::Reference< ucbhelper::SimpleAuthenticationRequest > xRequest + = new ucbhelper::SimpleAuthenticationRequest( + m_aURL, inHostName, inRealm, inoutUserName, + outPassWord, + true /*bAllowPersistentStoring*/, + bCanUseSystemCredentials ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + uno::Reference< task::XInteractionAbort > xAbort( + xSelection.get(), uno::UNO_QUERY ); + if ( !xAbort.is() ) + { + const rtl::Reference< + ucbhelper::InteractionSupplyAuthentication > & xSupp + = xRequest->getAuthenticationSupplier(); + + bool bUseSystemCredentials = false; + + if ( bCanUseSystemCredentials ) + bUseSystemCredentials + = xSupp->getUseSystemCredentials(); + + if ( bUseSystemCredentials ) + { + // This is the (strange) way to tell neon to use + // system credentials. + inoutUserName.clear(); + outPassWord.clear(); + } + else + { + inoutUserName = xSupp->getUserName(); + outPassWord = xSupp->getPassword(); + } + + // #102871# - Remember username and password. + m_aPrevUsername = inoutUserName; + m_aPrevPassword = outPassWord; + + // go on. + return 0; + } + } + } + } + // Abort. + return -1; +} + + +// DAVResourceAccess Implementation. + + +DAVResourceAccess::DAVResourceAccess( + const uno::Reference< uno::XComponentContext > & rContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ) +: m_aURL( rURL ), + m_xSessionFactory( rSessionFactory ), + m_xContext( rContext ) +{ +} + + +DAVResourceAccess::DAVResourceAccess( const DAVResourceAccess & rOther ) +: m_aURL( rOther.m_aURL ), + m_aPath( rOther.m_aPath ), + m_xSession( rOther.m_xSession ), + m_xSessionFactory( rOther.m_xSessionFactory ), + m_xContext( rOther.m_xContext ), + m_aRedirectURIs( rOther.m_aRedirectURIs ) +{ +} + + +DAVResourceAccess & DAVResourceAccess::operator=( + const DAVResourceAccess & rOther ) +{ + m_aURL = rOther.m_aURL; + m_aPath = rOther.m_aPath; + m_xSession = rOther.m_xSession; + m_xSessionFactory = rOther.m_xSessionFactory; + m_xContext = rOther.m_xContext; + m_aRedirectURIs = rOther.m_aRedirectURIs; + + return *this; +} + + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rPropertyNames, + rResources, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPFIND( + const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPFIND, + aHeaders ); + + m_xSession->PROPFIND( getRequestURI(), + nDepth, + rResInfo, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ) ; + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::PROPPATCH( + const std::vector< ProppatchValue >& rValues, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PROPPATCH, + aHeaders ); + + m_xSession->PROPPATCH( getRequestURI(), + rValues, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::HEAD( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_HEAD, + aHeaders ); + + m_xSession->HEAD( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::GET( + DAVRequestHeaders &rRequestHeaders, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + rRequestHeaders ); + + xStream = m_xSession->GET( getRequestURI(), + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + rRequestHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::GET( + uno::Reference< io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, + DAVResource & rResource, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + bool bRetry; + int errorCount = 0; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_GET, + aHeaders ); + + m_xSession->GET( getRequestURI(), + rStream, + rHeaderNames, + rResource, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::abort() +{ + // 17.11.09 (tkr): abort currently disabled caused by issue i106766 + // initialize(); + // m_xSession->abort(); + SAL_INFO("ucb.ucp.webdav", "Not implemented. -> #i106766#" ); +} + + +namespace { + + /// @throws DAVException + void resetInputStream( const uno::Reference< io::XInputStream > & rStream ) + { + try + { + uno::Reference< io::XSeekable > xSeekable( + rStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + xSeekable->seek( 0 ); + return; + } + } + catch ( lang::IllegalArgumentException const & ) + { + } + catch ( io::IOException const & ) + { + } + + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + +} // namespace + + +void DAVResourceAccess::PUT( + const uno::Reference< io::XInputStream > & rStream, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + resetInputStream( xSeekableStream ); + + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_PUT, + aHeaders ); + + m_xSession->PUT( getRequestURI(), + xSeekableStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +uno::Reference< io::XInputStream > DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + uno::Reference< io::XInputStream > xStream; + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + xStream = m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( + xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + return GET( xEnv ); + } + } + } + while ( bRetry ); + + return xStream; +} + + +void DAVResourceAccess::POST( + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & rInputStream, + uno::Reference< io::XOutputStream > & rOutputStream, + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + initialize(); + + // Make stream seekable, if it not. Needed, if request must be retried. + uno::Reference< io::XInputStream > xSeekableStream + = comphelper::OSeekableInputWrapper::CheckSeekableCanWrap( + rInputStream, m_xContext ); + + int errorCount = 0; + bool bRetry = false; + do + { + if ( bRetry ) + { + resetInputStream( xSeekableStream ); + bRetry = false; + } + + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_POST, + aHeaders ); + + m_xSession->POST( getRequestURI(), + rContentType, + rReferer, + xSeekableStream, + rOutputStream, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + + if ( e.getError() == DAVException::DAV_HTTP_REDIRECT ) + { + // #i74980# - Upon POST redirect, do a GET. + GET( rOutputStream, xEnv ); + return; + } + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MKCOL( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MKCOL, + aHeaders ); + + m_xSession->MKCOL( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::COPY( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_COPY, + aHeaders ); + + m_xSession->COPY( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::MOVE( + const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_MOVE, + aHeaders ); + + m_xSession->MOVE( rSourcePath, + rDestinationURI, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ), + bOverwrite ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::DESTROY( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_DELETE, + aHeaders ); + + m_xSession->DESTROY( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +// set new lock. +void DAVResourceAccess::LOCK( + ucb::Lock & inLock, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_LOCK, + aHeaders ); + + m_xSession->LOCK( getRequestURI(), + inLock, + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + +void DAVResourceAccess::UNLOCK( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + initialize(); + + int errorCount = 0; + bool bRetry; + do + { + bRetry = false; + try + { + DAVRequestHeaders aHeaders; + getUserRequestHeaders( xEnv, + getRequestURI(), + ucb::WebDAVHTTPMethod_UNLOCK, + aHeaders ); + + m_xSession->UNLOCK( getRequestURI(), + DAVRequestEnvironment( + getRequestURI(), + new DAVAuthListener_Impl( xEnv, m_aURL ), + aHeaders, xEnv ) ); + } + catch ( DAVException & e ) + { + errorCount++; + bRetry = handleException( e, errorCount ); + if ( !bRetry ) + throw; + } + } + while ( bRetry ); +} + + +void DAVResourceAccess::setURL( const OUString & rNewURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_aURL = rNewURL; + m_aPath.clear(); // Next initialize() will create new session. +} + + +// init dav session and path +void DAVResourceAccess::initialize() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( m_aPath.isEmpty() ) + { + SerfUri aURI( m_aURL ); + OUString aPath( aURI.GetPath() ); + + /* #134089# - Check URI */ + if ( aPath.isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + /* #134089# - Check URI */ + if ( aURI.GetHost().isEmpty() ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + if ( !m_xSession.is() || !m_xSession->CanUse( m_aURL ) ) + { + m_xSession.clear(); + + // create new webdav session + m_xSession + = m_xSessionFactory->createDAVSession( m_aURL, m_xContext ); + + if ( !m_xSession.is() ) + return; + } + + // Own URI is needed for redirect cycle detection. + m_aRedirectURIs.push_back( aURI ); + + // Success. + m_aPath = aPath; + + // Not only the path has to be encoded + m_aURL = aURI.GetURI(); + } +} + + +const OUString & DAVResourceAccess::getRequestURI() const +{ + SAL_WARN_IF( !m_xSession.is(), "ucb.ucp.webdav", + "DAVResourceAccess::getRequestURI - Not initialized!" ); + + // In case a proxy is used we have to use the absolute URI for a request. + if ( m_xSession->UsesProxy() ) + return m_aURL; + + return m_aPath; +} + + +// static +void DAVResourceAccess::getUserRequestHeaders( + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ) +{ + if ( !xEnv.is() ) + return; + + uno::Reference< ucb::XWebDAVCommandEnvironment > xDAVEnv( + xEnv, uno::UNO_QUERY ); + + if ( !xDAVEnv.is() ) + return; + + uno::Sequence< beans::StringPair > aRequestHeaders + = xDAVEnv->getUserRequestHeaders( rURI, eMethod ); + + for ( sal_Int32 n = 0; n < aRequestHeaders.getLength(); ++n ) + { + rRequestHeaders.push_back( + DAVRequestHeader( aRequestHeaders[ n ].First, + aRequestHeaders[ n ].Second ) ); + } +} + + +bool DAVResourceAccess::detectRedirectCycle( + const OUString& rRedirectURL ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + SerfUri aUri( rRedirectURL ); + + return std::any_of(m_aRedirectURIs.begin(), m_aRedirectURIs.end(), + [&aUri](const SerfUri& rUri) { return aUri == rUri; }); +} + + +void DAVResourceAccess::resetUri() +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + if ( ! m_aRedirectURIs.empty() ) + { + std::vector< SerfUri >::const_iterator it = m_aRedirectURIs.begin(); + + SerfUri aUri( *it ); + m_aRedirectURIs.clear(); + setURL ( aUri.GetURI() ); + initialize(); + } +} + + +bool DAVResourceAccess::handleException( DAVException & e, int errorCount ) +{ + switch ( e.getError() ) + { + case DAVException::DAV_HTTP_REDIRECT: + if ( !detectRedirectCycle( e.getData() ) ) + { + // set new URL and path. + setURL( e.getData() ); + initialize(); + return true; + } + return false; + // --> tkr #67048# copy & paste images doesn't display. + // if we have a bad connection try again. Up to three times. + case DAVException::DAV_HTTP_ERROR: + // retry up to three times, if not a client-side error. + if ( ( e.getStatus() < 400 || e.getStatus() >= 500 || + e.getStatus() == 413 ) && + errorCount < 3 ) + { + return true; + } + return false; + // <-- + // --> tkr: if connection has said retry then retry! + case DAVException::DAV_HTTP_RETRY: + return true; + // <-- + default: + return false; // Abort + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVResourceAccess.hxx b/ucb/source/ucp/webdav/DAVResourceAccess.hxx new file mode 100644 index 000000000..37d76d83f --- /dev/null +++ b/ucb/source/ucp/webdav/DAVResourceAccess.hxx @@ -0,0 +1,210 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX + +#include <vector> +#include <rtl/ustring.hxx> +#include <rtl/ref.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/WebDAVHTTPMethod.hpp> +#include "DAVAuthListener.hxx" +#include "DAVException.hxx" +#include "DAVSession.hxx" +#include "DAVResource.hxx" +#include "DAVTypes.hxx" +#include "SerfUri.hxx" + +namespace http_dav_ucp +{ + +class DAVSessionFactory; + +class DAVResourceAccess +{ + osl::Mutex m_aMutex; + OUString m_aURL; + OUString m_aPath; + rtl::Reference< DAVSession > m_xSession; + rtl::Reference< DAVSessionFactory > m_xSessionFactory; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::vector< SerfUri > m_aRedirectURIs; + +public: + DAVResourceAccess() = default; + DAVResourceAccess( const css::uno::Reference< css::uno::XComponentContext > & rContext, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + const OUString & rURL ); + DAVResourceAccess( const DAVResourceAccess & rOther ); + + DAVResourceAccess & operator=( const DAVResourceAccess & rOther ); + + /// @throws DAVException + void setURL( const OUString & rNewURL ); + + void resetUri(); + + const OUString & getURL() const { return m_aURL; } + + rtl::Reference< DAVSessionFactory > getSessionFactory() const + { return m_xSessionFactory; } + + // DAV methods + + + // allprop & named + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + const std::vector< OUString > & rPropertyNames, + std::vector< DAVResource > & rResources, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // propnames + /// @throws DAVException + void + PROPFIND( const Depth nDepth, + std::vector< DAVResourceInfo > & rResInfo, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + PROPPATCH( const std::vector< ProppatchValue > & rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + HEAD( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + GET( DAVRequestHeaders & rRequestHeaders, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + GET( css::uno::Reference< css::io::XOutputStream > & rStream, + const std::vector< OUString > & rHeaderNames, // empty == 'all' + DAVResource & rResource, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + PUT( const css::uno::Reference< css::io::XInputStream > & rStream, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + css::uno::Reference< css::io::XInputStream > + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + POST( const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & rInputStream, + css::uno::Reference< css::io::XOutputStream > & rOutputStream, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws DAVException + void + MKCOL( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + COPY( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + MOVE( const OUString & rSourcePath, + const OUString & rDestinationURI, + bool bOverwrite, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + DESTROY( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // set new lock. + /// @throws DAVException + void + LOCK( css::ucb::Lock & inLock, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + UNLOCK( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + /// @throws DAVException + void + static abort(); + + // helper + static void + getUserRequestHeaders( + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + const OUString & rURI, + css::ucb::WebDAVHTTPMethod eMethod, + DAVRequestHeaders & rRequestHeaders ); + +private: + const OUString & getRequestURI() const; + /// @throws DAVException + bool detectRedirectCycle( const OUString& rRedirectURL ); + /// @throws DAVException + bool handleException( DAVException & e, int errorCount ); + /// @throws DAVException + void initialize(); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVRESOURCEACCESS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVSession.hxx b/ucb/source/ucp/webdav/DAVSession.hxx new file mode 100644 index 000000000..aa47b1504 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSession.hxx @@ -0,0 +1,205 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX + +#include <memory> +#include <rtl/ustring.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include "DAVResource.hxx" +#include "DAVSessionFactory.hxx" +#include "DAVTypes.hxx" +#include "DAVRequestEnvironment.hxx" + +namespace com::sun::star::ucb { + struct Lock; +} + +namespace http_dav_ucp +{ + +class DAVAuthListener; + +class DAVSession +{ +public: + void acquire() + { + osl_atomic_increment( &m_nRefCount ); + } + + void release() + { + if ( osl_atomic_decrement( &m_nRefCount ) == 0 ) + { + m_xFactory->releaseElement( this ); + delete this; + } + } + + virtual bool CanUse( const OUString & inPath ) = 0; + + virtual bool UsesProxy() = 0; + + // DAV methods + + + // NOT USED + /* + virtual void OPTIONS( const OUString & inPath, + DAVCapabilities & outCapabilities, + const DAVRequestEnvironment & rEnv ) + throw( DAVException ) = 0; + */ + + // allprop & named + /// @throws DAVException + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropertyNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) = 0; + + // propnames + /// @throws DAVException + virtual void PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream >& o, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream >& s, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual css::uno::Reference< css::io::XInputStream > + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + css::uno::Reference< css::io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void COPY( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite = false ) = 0; + + /// @throws DAVException + virtual void MOVE( const OUString & inSource, + const OUString & inDestination, + const DAVRequestEnvironment & rEnv, + bool inOverwrite = false ) = 0; + + /// @throws DAVException + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + // set new lock. + /// @throws DAVException + virtual void LOCK( const OUString & inPath, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) = 0; + + // refresh existing lock. + /// @throws DAVException + virtual sal_Int64 LOCK( const OUString & inPath, + sal_Int64 nTimeout, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) = 0; + + /// @throws DAVException + virtual void abort() = 0; + +protected: + rtl::Reference< DAVSessionFactory > m_xFactory; + + explicit DAVSession( rtl::Reference< DAVSessionFactory > const & rFactory ) + : m_xFactory( rFactory ), m_nRefCount( 0 ) {} + + virtual ~DAVSession() {} + +private: + DAVSessionFactory::Map::iterator m_aContainerIt; + oslInterlockedCount m_nRefCount; + + friend class DAVSessionFactory; + friend struct std::default_delete< DAVSession >; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVSessionFactory.cxx b/ucb/source/ucp/webdav/DAVSessionFactory.cxx new file mode 100644 index 000000000..6a0963f91 --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSessionFactory.cxx @@ -0,0 +1,86 @@ +/* -*- 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 "DAVSessionFactory.hxx" +#include "SerfSession.hxx" +#include "SerfUri.hxx" + +using namespace http_dav_ucp; +using namespace com::sun::star; + +DAVSessionFactory::~DAVSessionFactory() +{ +} + +rtl::Reference< DAVSession > DAVSessionFactory::createDAVSession( + const OUString & inUri, + const uno::Reference< uno::XComponentContext > & rxContext ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_xProxyDecider.get() ) + m_xProxyDecider.reset( new ucbhelper::InternetProxyDecider( rxContext ) ); + + Map::iterator aIt = std::find_if(m_aMap.begin(), m_aMap.end(), + [&inUri](const Map::value_type& rEntry) { return rEntry.second->CanUse( inUri ); }); + + if ( aIt == m_aMap.end() ) + { + SerfUri aURI( inUri ); + + std::unique_ptr< DAVSession > xElement( + new SerfSession( this, inUri, *m_xProxyDecider ) ); + + aIt = m_aMap.emplace( inUri, xElement.get() ).first; + aIt->second->m_aContainerIt = aIt; + xElement.release(); + return aIt->second; + } + else if ( osl_atomic_increment( &aIt->second->m_nRefCount ) > 1 ) + { + rtl::Reference< DAVSession > xElement( aIt->second ); + osl_atomic_decrement( &aIt->second->m_nRefCount ); + return xElement; + } + else + { + osl_atomic_decrement( &aIt->second->m_nRefCount ); + aIt->second->m_aContainerIt = m_aMap.end(); + + // If URL scheme is different from http or https we definitely + // have to use a proxy and therefore can optimize the getProxy + // call a little: + SerfUri aURI( inUri ); + + aIt->second = new SerfSession( this, inUri, *m_xProxyDecider ); + aIt->second->m_aContainerIt = aIt; + return aIt->second; + } +} + +void DAVSessionFactory::releaseElement( DAVSession * pElement ) +{ + assert( pElement ); + osl::MutexGuard aGuard( m_aMutex ); + if ( pElement->m_aContainerIt != m_aMap.end() ) + m_aMap.erase( pElement->m_aContainerIt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVSessionFactory.hxx b/ucb/source/ucp/webdav/DAVSessionFactory.hxx new file mode 100644 index 000000000..ec1fcfe3c --- /dev/null +++ b/ucb/source/ucp/webdav/DAVSessionFactory.hxx @@ -0,0 +1,72 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX + +#ifdef min +#undef min // GNU libstdc++ <memory> includes <limit> which defines methods called min... +#endif +#include <map> +#include <memory> +#include <osl/mutex.hxx> +#include <salhelper/simplereferenceobject.hxx> +#include <rtl/ref.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <ucbhelper/proxydecider.hxx> + +using namespace com::sun::star; + +namespace com::sun::star::lang { + class XMultiServiceFactory; +} + +namespace http_dav_ucp +{ + +class DAVSession; + +class DAVSessionFactory : public salhelper::SimpleReferenceObject +{ +public: + virtual ~DAVSessionFactory() override; + + /// @throws DAVException + rtl::Reference< DAVSession > + createDAVSession( const OUString & inUri, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + +private: + typedef std::map< OUString, DAVSession * > Map; + + Map m_aMap; + osl::Mutex m_aMutex; + std::unique_ptr< ucbhelper::InternetProxyDecider > m_xProxyDecider; + + void releaseElement( DAVSession * pElement ); + + friend class DAVSession; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVSESSIONFACTORY_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DAVTypes.hxx b/ucb/source/ucp/webdav/DAVTypes.hxx new file mode 100644 index 000000000..3cce18abb --- /dev/null +++ b/ucb/source/ucp/webdav/DAVTypes.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ +/* RFC 2518 + +15.1 Class 1 + + A class 1 compliant resource MUST meet all "MUST" requirements in all + sections of this document. + + Class 1 compliant resources MUST return, at minimum, the value "1" in + the DAV header on all responses to the OPTIONS method. + +15.2 Class 2 + + A class 2 compliant resource MUST meet all class 1 requirements and + support the LOCK method, the supportedlock property, the + lockdiscovery property, the Time-Out response header and the Lock- + Token request header. A class "2" compliant resource SHOULD also + support the Time-Out request header and the owner XML element. + + Class 2 compliant resources MUST return, at minimum, the values "1" + and "2" in the DAV header on all responses to the OPTIONS method. +*/ + +struct DAVCapabilities +{ + bool class1; + bool class2; + bool executable; // supports "executable" property (introduced by mod_dav) + + DAVCapabilities() : class1( false ), class2( false ), executable( false ) {} +}; + +enum Depth { DAVZERO = 0, DAVONE = 1, DAVINFINITY = -1 }; + +enum ProppatchOperation { PROPSET = 0, PROPREMOVE = 1 }; + +struct ProppatchValue +{ + ProppatchOperation operation; + OUString name; + css::uno::Any value; + + ProppatchValue( const ProppatchOperation o, + const OUString & n, + const css::uno::Any & v ) + : operation( o ), name( n ), value( v ) {} +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_DAVTYPES_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DateTimeHelper.cxx b/ucb/source/ucp/webdav/DateTimeHelper.cxx new file mode 100644 index 000000000..dfa21ed56 --- /dev/null +++ b/ucb/source/ucp/webdav/DateTimeHelper.cxx @@ -0,0 +1,258 @@ +/* -*- 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 <osl/time.h> +#include <com/sun/star/util/DateTime.hpp> +#include "DateTimeHelper.hxx" + +using namespace com::sun::star::util; + +using namespace http_dav_ucp; + +bool DateTimeHelper::ISO8601_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + int year, month, day, hours, minutes, off_hours, off_minutes, fix; + double seconds; + + // 2001-01-01T12:30:00Z + int n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lfZ", + &year, &month, &day, &hours, &minutes, &seconds ); + if ( n == 6 ) + { + fix = 0; + } + else + { + // 2001-01-01T12:30:00+03:30 + n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d", + &year, &month, &day, &hours, &minutes, &seconds, + &off_hours, &off_minutes ); + if ( n == 8 ) + { + fix = - off_hours * 3600 - off_minutes * 60; + } + else + { + // 2001-01-01T12:30:00-03:30 + n = sscanf( aDT.getStr(), "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d", + &year, &month, &day, &hours, &minutes, &seconds, + &off_hours, &off_minutes ); + if ( n == 8 ) + { + fix = off_hours * 3600 + off_minutes * 60; + } + else + { + return false; + } + } + } + + // Convert to local time... + + oslDateTime aDateTime; + aDateTime.NanoSeconds = 0; + aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds); // 0-59 + aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes); // 0-59 + aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours); // 0-23 + aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day); // 1-31 + aDateTime.DayOfWeek = 0; // 0-6, 0 = Sunday + aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month); // 1-12 + aDateTime.Year = sal::static_int_cast< sal_Int16 >(year); + + TimeValue aTimeValue; + if ( osl_getTimeValueFromDateTime( &aDateTime, &aTimeValue ) ) + { + aTimeValue.Seconds += fix; + + if ( osl_getLocalTimeFromSystemTime( &aTimeValue, &aTimeValue ) ) + { + if ( osl_getDateTimeFromTimeValue( &aTimeValue, &aDateTime ) ) + { + dateTime.Year = aDateTime.Year; + dateTime.Month = aDateTime.Month; + dateTime.Day = aDateTime.Day; + dateTime.Hours = aDateTime.Hours; + dateTime.Minutes = aDateTime.Minutes; + dateTime.Seconds = aDateTime.Seconds; + + return true; + } + } + } + + return false; +} + +/* +sal_Int32 DateTimeHelper::convertDayToInt (const OUString& day) +{ + if (day.equalsAscii("Sun")) + return 0; + else if (day.equalsAscii("Mon")) + return 1; + else if (day.equalsAscii("Tue")) + return 2; + else if (day.equalsAscii("Wed")) + return 3; + else if (day.equalsAscii("Thu")) + return 4; + else if (day.equalsAscii("Fri")) + return 5; + else if (day.equalsAscii("Sat")) + return 6; + else + return -1; +} +*/ + +sal_Int32 DateTimeHelper::convertMonthToInt (const OUString& month) +{ + if (month == "Jan") + return 1; + else if (month == "Feb") + return 2; + else if (month == "Mar") + return 3; + else if (month == "Apr") + return 4; + else if (month == "May") + return 5; + else if (month == "Jun") + return 6; + else if (month == "Jul") + return 7; + else if (month == "Aug") + return 8; + else if (month == "Sep") + return 9; + else if (month == "Oct") + return 10; + else if (month == "Nov") + return 11; + else if (month == "Dec") + return 12; + else + return 0; +} + +bool DateTimeHelper::RFC2068_To_DateTime (const OUString& s, + DateTime& dateTime) +{ + int year; + int day; + int hours; + int minutes; + int seconds; + char string_month[3 + 1]; + char string_day[3 + 1]; + + sal_Int32 found = s.indexOf (','); + if (found != -1) + { + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + // RFC 1123 + found = sscanf (aDT.getStr(), "%3s, %2d %3s %4d %2d:%2d:%2d GMT", + string_day, &day, string_month, &year, &hours, &minutes, &seconds); + if (found != 7) + { + // RFC 1036 + found = sscanf (aDT.getStr(), "%3s, %2d-%3s-%2d %2d:%2d:%2d GMT", + string_day, &day, string_month, &year, &hours, &minutes, &seconds); + } + found = (found == 7) ? 1 : 0; + } + else + { + OString aDT (s.getStr(), s.getLength(), RTL_TEXTENCODING_ASCII_US); + + // ANSI C's asctime () format + found = sscanf (aDT.getStr(), "%3s %3s %d %2d:%2d:%2d %4d", + string_day, string_month, + &day, &hours, &minutes, &seconds, &year); + found = (found == 7) ? 1 : 0; + } + + if (found) + { + found = 0; + + int month = DateTimeHelper::convertMonthToInt ( + OUString::createFromAscii (string_month)); + if (month) + { + // Convert to local time... + + oslDateTime aDateTime; + aDateTime.NanoSeconds = 0; + aDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(seconds); + // 0-59 + aDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(minutes); + // 0-59 + aDateTime.Hours = sal::static_int_cast< sal_uInt16 >(hours); + // 0-23 + aDateTime.Day = sal::static_int_cast< sal_uInt16 >(day); + // 1-31 + aDateTime.DayOfWeek = 0; //dayofweek; // 0-6, 0 = Sunday + aDateTime.Month = sal::static_int_cast< sal_uInt16 >(month); + // 1-12 + aDateTime.Year = sal::static_int_cast< sal_Int16 >(year); + + TimeValue aTimeValue; + if ( osl_getTimeValueFromDateTime( &aDateTime, + &aTimeValue ) ) + { + if ( osl_getLocalTimeFromSystemTime( &aTimeValue, + &aTimeValue ) ) + { + if ( osl_getDateTimeFromTimeValue( &aTimeValue, + &aDateTime ) ) + { + dateTime.Year = aDateTime.Year; + dateTime.Month = aDateTime.Month; + dateTime.Day = aDateTime.Day; + dateTime.Hours = aDateTime.Hours; + dateTime.Minutes = aDateTime.Minutes; + dateTime.Seconds = aDateTime.Seconds; + + found = 1; + } + } + } + } + } + + return found; +} + +bool DateTimeHelper::convert (const OUString& s, DateTime& dateTime) +{ + if (ISO8601_To_DateTime (s, dateTime)) + return true; + else if (RFC2068_To_DateTime (s, dateTime)) + return true; + else + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/DateTimeHelper.hxx b/ucb/source/ucp/webdav/DateTimeHelper.hxx new file mode 100644 index 000000000..b63921533 --- /dev/null +++ b/ucb/source/ucp/webdav/DateTimeHelper.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_DATETIMEHELPER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_DATETIMEHELPER_HXX + +#include <sal/types.h> +#include <rtl/ustring.hxx> + +namespace com::sun::star::util { + struct DateTime; +} + +namespace rtl { + class OUString; +} + +namespace http_dav_ucp +{ + +class DateTimeHelper +{ +private: + static sal_Int32 convertMonthToInt (const OUString& ); + + static bool ISO8601_To_DateTime (const OUString&, + css::util::DateTime& ); + + static bool RFC2068_To_DateTime (const OUString&, + css::util::DateTime& ); + +public: + static bool convert (const OUString&, + css::util::DateTime& ); +}; + +} // namespace http_dav_ucp + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/PropertyMap.hxx b/ucb/source/ucp/webdav/PropertyMap.hxx new file mode 100644 index 000000000..ef5693d76 --- /dev/null +++ b/ucb/source/ucp/webdav/PropertyMap.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_PROPERTYMAP_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_PROPERTYMAP_HXX + +#include <com/sun/star/beans/Property.hpp> +#include <unordered_set> + +namespace http_dav_ucp { + +struct equalPropertyName +{ + bool operator()( const css::beans::Property & p1, + const css::beans::Property & p2 ) const + { + return p1.Name == p2.Name; + } +}; + +struct hashPropertyName +{ + size_t operator()( const css::beans::Property & p ) const + { + return p.Name.hashCode(); + } +}; + +typedef std::unordered_set +< + css::beans::Property, + hashPropertyName, + equalPropertyName +> +PropertyMap; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCallbacks.cxx b/ucb/source/ucp/webdav/SerfCallbacks.cxx new file mode 100644 index 000000000..5d1194ea9 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCallbacks.cxx @@ -0,0 +1,111 @@ +/* -*- 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 "SerfCallbacks.hxx" + +#include "SerfSession.hxx" +#include "SerfRequestProcessor.hxx" + +using namespace http_dav_ucp; + +extern "C" apr_status_t Serf_ConnectSetup( apr_socket_t *skt, + serf_bucket_t **read_bkt, + serf_bucket_t **write_bkt, + void *setup_baton, + apr_pool_t *pool ) +{ + SerfSession* pSerfSession = static_cast< SerfSession* >( setup_baton ); + return pSerfSession->setupSerfConnection( skt, + read_bkt, + write_bkt, + pool ); +} + +extern "C" apr_status_t Serf_Credentials( char **username, + char **password, + serf_request_t *request, + void *baton, + int code, + const char *authn_type, + const char *realm, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( baton ); + return pReqProc->provideSerfCredentials( username, + password, + request, + code, + authn_type, + realm, + pool ); +} + +extern "C" apr_status_t Serf_CertificateChainValidation( + void* pSerfSession, + int nFailures, + int /*nErrorCode*/, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + apr_size_t nCertificateChainLength) +{ + return static_cast<SerfSession*>(pSerfSession) + ->verifySerfCertificateChain(nFailures, pCertificateChainBase64Encoded, nCertificateChainLength); +} + +extern "C" apr_status_t Serf_SetupRequest( serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t * pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( setup_baton ); + return pReqProc->setupSerfRequest( request, + req_bkt, + acceptor, + acceptor_baton, + handler, + handler_baton, + pool ); +} + +extern "C" serf_bucket_t* Serf_AcceptResponse( serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( acceptor_baton ); + return pReqProc->acceptSerfResponse( request, + stream, + pool ); +} + +extern "C" apr_status_t Serf_HandleResponse( serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool ) +{ + SerfRequestProcessor* pReqProc = static_cast< SerfRequestProcessor* >( handler_baton ); + return pReqProc->handleSerfResponse( request, + response, + pool ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCallbacks.hxx b/ucb/source/ucp/webdav/SerfCallbacks.hxx new file mode 100644 index 000000000..b82246e68 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCallbacks.hxx @@ -0,0 +1,69 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX + +#include <serf.h> + +extern "C" apr_status_t Serf_ConnectSetup( apr_socket_t *skt, + serf_bucket_t **read_bkt, + serf_bucket_t **write_bkt, + void *setup_baton, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_Credentials( char **username, + char **password, + serf_request_t *request, + void *baton, + int code, + const char *authn_type, + const char *realm, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_CertificateChainValidation( + void* pSerfSession, + int nFailures, + int error_depth, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + apr_size_t nCertificateChainLength); + +extern "C" apr_status_t Serf_SetupRequest( serf_request_t *request, + void *setup_baton, + serf_bucket_t **req_bkt, + serf_response_acceptor_t *acceptor, + void **acceptor_baton, + serf_response_handler_t *handler, + void **handler_baton, + apr_pool_t * pool ); + +extern "C" serf_bucket_t* Serf_AcceptResponse( serf_request_t *request, + serf_bucket_t *stream, + void *acceptor_baton, + apr_pool_t *pool ); + +extern "C" apr_status_t Serf_HandleResponse( serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool ); + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCALLBACKS_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx new file mode 100644 index 000000000..0f9a4ea20 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.cxx @@ -0,0 +1,82 @@ +/* -*- 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 "SerfCopyReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfCopyReqProcImpl::SerfCopyReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ) + : SerfRequestProcessorImpl( inSourcePath, inRequestHeaders ) + , mDestPathStr( inDestinationPath ) + , mbOverwrite( inOverwrite ) +{ +} + +SerfCopyReqProcImpl::~SerfCopyReqProcImpl() +{ +} + +serf_bucket_t * SerfCopyReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "COPY", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // COPY specific header fields + serf_bucket_headers_set( hdrs_bkt, "Destination", mDestPathStr ); + if ( mbOverwrite ) + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "T" ); + } + else + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "F" ); + } + + return req_bkt; +} + +void SerfCopyReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfCopyReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfCopyReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.hxx new file mode 100644 index 000000000..92d12abb6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfCopyReqProcImpl.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfCopyReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfCopyReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ); + + virtual ~SerfCopyReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mDestPathStr; + const bool mbOverwrite; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFCOPYREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx new file mode 100644 index 000000000..141bb0b47 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.cxx @@ -0,0 +1,67 @@ +/* -*- 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 "SerfDeleteReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfDeleteReqProcImpl::SerfDeleteReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) +{ +} + +SerfDeleteReqProcImpl::~SerfDeleteReqProcImpl() +{ +} + +serf_bucket_t * SerfDeleteReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "DELETE", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfDeleteReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfDeleteReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.hxx new file mode 100644 index 000000000..0bb8cd60e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfDeleteReqProcImpl.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfDeleteReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfDeleteReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfDeleteReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFDELETEREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx new file mode 100644 index 000000000..c06b94f16 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfGetReqProcImpl.cxx @@ -0,0 +1,173 @@ +/* -*- 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 "SerfGetReqProcImpl.hxx" + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream( xioInStrm ) + , xOutputStream() + , mpHeaderNames( nullptr ) + , mpResource( nullptr ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream( xioInStrm ) + , xOutputStream() + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream() + , xOutputStream( xioOutStrm ) + , mpHeaderNames( nullptr ) + , mpResource( nullptr ) +{ +} + +SerfGetReqProcImpl::SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , xInputStream() + , xOutputStream( xioOutStrm ) + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfGetReqProcImpl::~SerfGetReqProcImpl() +{ +} + +serf_bucket_t * SerfGetReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "GET", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfGetReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } + else if ( xOutputStream.is() ) + { + const uno::Sequence< sal_Int8 > aDataSeq( reinterpret_cast<const sal_Int8 *>(data), len ); + xOutputStream->writeBytes( aDataSeq ); + } +} + +namespace +{ + apr_status_t Serf_ProcessResponseHeader( void* inUserData, + const char* inHeaderName, + const char* inHeaderValue ) + { + SerfGetReqProcImpl* pReqProcImpl = static_cast< SerfGetReqProcImpl* >( inUserData ); + pReqProcImpl->processSingleResponseHeader( inHeaderName, + inHeaderValue ); + + return APR_SUCCESS; + } +} // end of anonymous namespace + +void SerfGetReqProcImpl::handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) +{ + // read response header, if requested + if ( mpHeaderNames != nullptr && mpResource != nullptr ) + { + serf_bucket_t* SerfHeaderBucket = serf_bucket_response_get_headers( inSerfResponseBucket ); + if ( SerfHeaderBucket != nullptr ) + { + serf_bucket_headers_do( SerfHeaderBucket, + Serf_ProcessResponseHeader, + this ); + } + } +} + +void SerfGetReqProcImpl::processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ) +{ + OUString aHeaderName( OUString::createFromAscii( inHeaderName ) ); + + bool bStoreHeaderField = false; + + if ( mpHeaderNames->empty() ) + { + // store all header fields + bStoreHeaderField = true; + } + else + { + // store only header fields which are requested + bStoreHeaderField = std::any_of(mpHeaderNames->begin(), mpHeaderNames->end(), + [&aHeaderName](const OUString& rHeaderName) { + // header names are case insensitive + return rHeaderName.equalsIgnoreAsciiCase( aHeaderName ); + }); + } + + if ( bStoreHeaderField ) + { + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Name = aHeaderName; + thePropertyValue.Value <<= OUString::createFromAscii( inHeaderValue ); + mpResource->properties.push_back( thePropertyValue ); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx new file mode 100644 index 000000000..d043f3087 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfGetReqProcImpl.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfGetReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const rtl::Reference< SerfInputStream > & xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ); + + SerfGetReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + virtual ~SerfGetReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + + void processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ); + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + rtl::Reference< SerfInputStream > xInputStream; + css::uno::Reference< css::io::XOutputStream > xOutputStream; + const std::vector< OUString > * mpHeaderNames; + DAVResource* mpResource; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFGETREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx new file mode 100644 index 000000000..a771570da --- /dev/null +++ b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.cxx @@ -0,0 +1,128 @@ +/* -*- 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 "SerfHeadReqProcImpl.hxx" + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfHeadReqProcImpl::SerfHeadReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpHeaderNames( &inHeaderNames ) + , mpResource( &ioResource ) +{ +} + +SerfHeadReqProcImpl::~SerfHeadReqProcImpl() +{ +} + +serf_bucket_t * SerfHeadReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "HEAD", + getPathStr(), + + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfHeadReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do +} + +namespace +{ + apr_status_t Serf_ProcessResponseHeader( void* inUserData, + const char* inHeaderName, + const char* inHeaderValue ) + { + SerfHeadReqProcImpl* pReqProcImpl = static_cast< SerfHeadReqProcImpl* >( inUserData ); + pReqProcImpl->processSingleResponseHeader( inHeaderName, + inHeaderValue ); + + return APR_SUCCESS; + } +} // end of anonymous namespace + +void SerfHeadReqProcImpl::handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) +{ + // read response header, if requested + if ( mpHeaderNames != nullptr && mpResource != nullptr ) + { + serf_bucket_t* SerfHeaderBucket = serf_bucket_response_get_headers( inSerfResponseBucket ); + if ( SerfHeaderBucket != nullptr ) + { + serf_bucket_headers_do( SerfHeaderBucket, + Serf_ProcessResponseHeader, + this ); + } + } +} + +void SerfHeadReqProcImpl::processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ) +{ + OUString aHeaderName( OUString::createFromAscii( inHeaderName ) ); + + bool bStoreHeaderField = false; + + if ( mpHeaderNames->empty() ) + { + // store all header fields + bStoreHeaderField = true; + } + else + { + // store only header fields which are requested + bStoreHeaderField = std::any_of(mpHeaderNames->begin(), mpHeaderNames->end(), + [&aHeaderName](const OUString& rHeaderName) { + // header names are case insensitive + return rHeaderName.equalsIgnoreAsciiCase( aHeaderName ); + }); + } + + if ( bStoreHeaderField ) + { + DAVPropertyValue thePropertyValue; + thePropertyValue.IsCaseSensitive = false; + thePropertyValue.Name = aHeaderName; + thePropertyValue.Value <<= OUString::createFromAscii( inHeaderValue ); + mpResource->properties.push_back( thePropertyValue ); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfHeadReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.hxx new file mode 100644 index 000000000..b1a03a07e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfHeadReqProcImpl.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfHeadReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfHeadReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource ); + + virtual ~SerfHeadReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + + void processSingleResponseHeader( const char* inHeaderName, + const char* inHeaderValue ); + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const std::vector< OUString > * mpHeaderNames; + DAVResource* mpResource; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFHEADREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfInputStream.cxx b/ucb/source/ucp/webdav/SerfInputStream.cxx new file mode 100644 index 000000000..498ed26e6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfInputStream.cxx @@ -0,0 +1,159 @@ +/* -*- 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 "SerfInputStream.hxx" + +#include <cppuhelper/queryinterface.hxx> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <string.h> + +using namespace cppu; +using namespace com::sun::star::io; +using namespace com::sun::star::uno; +using namespace http_dav_ucp; + +// Constructor + +SerfInputStream::SerfInputStream() +: mLen( 0 ), + mPos( 0 ) +{ +} + + +// Destructor + +SerfInputStream::~SerfInputStream() +{ +} + + +// AddToStream +// Allows the caller to add some data to the "end" of the stream + +void SerfInputStream::AddToStream( const char * inBuf, sal_Int32 inLen ) +{ + mInputBuffer.realloc( sal::static_int_cast<sal_Int32>(mLen) + inLen ); + memcpy( mInputBuffer.getArray() + mLen, inBuf, inLen ); + mLen += inLen; +} + + +// queryInterface + +Any SerfInputStream::queryInterface( const Type &type ) +{ + Any aRet = ::cppu::queryInterface( type, + static_cast< XInputStream * >( this ), + static_cast< XSeekable * >( this ) ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( type ); +} + + +// readBytes +// "Reads" the specified number of bytes from the stream + +sal_Int32 SAL_CALL SerfInputStream::readBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + // Work out how much we're actually going to write + sal_Int32 theBytes2Read = nBytesToRead; + sal_Int32 theBytesLeft = sal::static_int_cast<sal_Int32>(mLen - mPos); + if ( theBytes2Read > theBytesLeft ) + theBytes2Read = theBytesLeft; + + // Realloc buffer. + aData.realloc( theBytes2Read ); + + // Write the data + memcpy( + aData.getArray(), mInputBuffer.getConstArray() + mPos, theBytes2Read ); + + // Update our stream position for next time + mPos += theBytes2Read; + + return theBytes2Read; +} + + +// readSomeBytes + +sal_Int32 SAL_CALL SerfInputStream::readSomeBytes( + css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + // Warning: What should this be doing ? + return readBytes( aData, nMaxBytesToRead ); +} + + +// skipBytes +// Moves the current stream position forward + +void SAL_CALL SerfInputStream::skipBytes( sal_Int32 nBytesToSkip ) +{ + mPos += nBytesToSkip; + if ( mPos >= mLen ) + mPos = mLen; +} + + +// available +// Returns the number of unread bytes currently remaining on the stream + +sal_Int32 SAL_CALL SerfInputStream::available( ) +{ + return std::min<sal_Int64>(SAL_MAX_INT32, mLen - mPos); +} + + +// closeInput + +void SAL_CALL SerfInputStream::closeInput() +{ +} + + +// seek + +void SAL_CALL SerfInputStream::seek( sal_Int64 location ) +{ + if ( location < 0 || location > mLen ) + throw css::lang::IllegalArgumentException(); + mPos = location; +} + + +// getPosition + +sal_Int64 SAL_CALL SerfInputStream::getPosition() +{ + return mPos; +} + + +// getLength + +sal_Int64 SAL_CALL SerfInputStream::getLength() +{ + return mLen; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfInputStream.hxx b/ucb/source/ucp/webdav/SerfInputStream.hxx new file mode 100644 index 000000000..ca9520ed8 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfInputStream.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX + +#include <sal/types.h> +#include <cppuhelper/weak.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> + + +namespace http_dav_ucp +{ + + +// SerfInputStream +// A simple XInputStream implementation provided specifically for use +// by the DAVSession::GET method. + +class SerfInputStream : public css::io::XInputStream, + public css::io::XSeekable, + public ::cppu::OWeakObject +{ + private: + css::uno::Sequence< sal_Int8 > mInputBuffer; + sal_Int64 mLen; + sal_Int64 mPos; + + public: + SerfInputStream(); + virtual ~SerfInputStream() override; + + // Add some data to the end of the stream + void AddToStream( const char * inBuf, sal_Int32 inLen ); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & type ) override; + + virtual void SAL_CALL acquire() + throw () override + { OWeakObject::acquire(); } + + virtual void SAL_CALL release() + throw() override + { OWeakObject::release(); } + + + // XInputStream + virtual sal_Int32 SAL_CALL readBytes( + css::uno::Sequence< sal_Int8 > & aData, + sal_Int32 nBytesToRead ) override; + + virtual sal_Int32 SAL_CALL readSomeBytes( + css::uno::Sequence< sal_Int8 > & aData, + sal_Int32 nMaxBytesToRead ) override; + + virtual void SAL_CALL skipBytes( sal_Int32 nBytesToSkip ) override; + + virtual sal_Int32 SAL_CALL available() override; + + virtual void SAL_CALL closeInput() override; + + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + + virtual sal_Int64 SAL_CALL getPosition() override; + + virtual sal_Int64 SAL_CALL getLength() override; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFINPUTSTREAM_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx new file mode 100644 index 000000000..713bf5299 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockReqProcImpl.cxx @@ -0,0 +1,203 @@ +/* -*- 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 "SerfLockReqProcImpl.hxx" + +#include "AprEnv.hxx" +#include "SerfSession.hxx" +#include "DAVException.hxx" + +#include "webdavresponseparser.hxx" +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +namespace http_dav_ucp +{ + +SerfLockReqProcImpl::SerfLockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + SerfSession& rSession, + const css::ucb::Lock& rLock, + sal_Int32* plastChanceToSendRefreshRequest ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , m_rSession( rSession ) + , m_aLock( rLock ) + , m_plastChanceToSendRefreshRequest( plastChanceToSendRefreshRequest ) + , m_xInputStream( new SerfInputStream() ) +{ +} + +SerfLockReqProcImpl::~SerfLockReqProcImpl() +{ +} + +serf_bucket_t * SerfLockReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + OStringBuffer aBody("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<lockinfo xmlns='DAV:'>\n <lockscope>"); + + // Set the lock scope + switch ( m_aLock.Scope ) + { + case css::ucb::LockScope_EXCLUSIVE: + aBody.append("<exclusive/>"); + break; + case css::ucb::LockScope_SHARED: + aBody.append("<shared/>"); + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + aBody.append("</lockscope>\n <locktype><write/></locktype>\n"); + + // Set the lock owner + OUString aValue; + if ((m_aLock.Owner >>= aValue) && !aValue.isEmpty()) + { + aBody.append(" <owner>"); + aBody.append(OUStringToOString(aValue, RTL_TEXTENCODING_UTF8)); + aBody.append("</owner>\n"); + } + aBody.append("</lockinfo>\n"); + + const OString aBodyText(aBody.makeStringAndClear()); + serf_bucket_t* body_bkt = nullptr; + + if (!m_plastChanceToSendRefreshRequest) + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "LOCK", + getPathStr(), + body_bkt, + pSerfBucketAlloc ); + if (!m_plastChanceToSendRefreshRequest) + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + const char * depth = nullptr; + switch( m_aLock.Depth ) + { + case css::ucb::LockDepth_ZERO: + depth = "0"; + break; + case css::ucb::LockDepth_ONE: + depth = "1"; + break; + case css::ucb::LockDepth_INFINITY: + depth = "infinity"; + break; + default: + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + if (!m_plastChanceToSendRefreshRequest) + { + serf_bucket_headers_set( hdrs_bkt, "Depth", depth ); + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + else + { + const OString sToken( "(<" + OUStringToOString( apr_environment::AprEnv::getAprEnv()-> + getSerfLockStore()->getLockToken( OUString::createFromAscii(getPathStr())), + RTL_TEXTENCODING_UTF8 ) + ">)" ); + serf_bucket_headers_set( hdrs_bkt, "If", sToken.getStr() ); + } + + // Set the lock timeout + if (m_aLock.Timeout == -1) + serf_bucket_headers_set( hdrs_bkt, "Timeout", "Infinite" ); + else if (m_aLock.Timeout > 0) + { + const OString aTimeValue("Second-" + OString::number(m_aLock.Timeout)); + serf_bucket_headers_set( hdrs_bkt, "Timeout", aTimeValue.getStr() ); + } + else + serf_bucket_headers_set( hdrs_bkt, "Timeout", "Second-180" ); + + osl_getSystemTime( &m_aStartCall ); + + return req_bkt; +} + +void SerfLockReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( m_xInputStream.is() ) + { + m_xInputStream->AddToStream( data, len ); + } +} + +void SerfLockReqProcImpl::handleEndOfResponseData( serf_bucket_t * ) +{ + const std::vector< css::ucb::Lock > aLocks( parseWebDAVLockResponse( m_xInputStream.get() ) ); + + if (!aLocks.empty()) + { + for (size_t i = 0; i < aLocks.size(); ++i) + { + sal_Int64 timeout = aLocks[i].Timeout; + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != -1 ) + { + sal_Int32 calltime = aEnd.Seconds - m_aStartCall.Seconds; + if ( calltime <= timeout ) + lastChanceToSendRefreshRequest = aEnd.Seconds + timeout - calltime; + else + SAL_WARN("ucb.ucp.webdav", "No chance to refresh lock before timeout!" ); + } + if (m_plastChanceToSendRefreshRequest) + { + *m_plastChanceToSendRefreshRequest = lastChanceToSendRefreshRequest; + assert(aLocks.size() == 1); + // We are just refreshing lock, do not add it into SerfLockStore + break; + } + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->addLock( + OUString::createFromAscii(getPathStr()), + aLocks[i].LockTokens[0], + &m_rSession, lastChanceToSendRefreshRequest ); + SAL_INFO("ucb.ucp.webdav", "SerfSession::LOCK: created lock for " + << getPathStr() << ". token: " << aLocks[i].LockTokens[0]); + } + } + else + { + SAL_INFO("ucb.ucp.webdav", "SerfSession::LOCK: obtaining lock failed!"); + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfLockReqProcImpl.hxx new file mode 100644 index 000000000..29d1361eb --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockReqProcImpl.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" +#include "SerfInputStream.hxx" + +#include <com/sun/star/ucb/Lock.hpp> +#include <osl/time.h> + +namespace http_dav_ucp +{ + +class SerfSession; + +class SerfLockReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfLockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + SerfSession& rSession, + const css::ucb::Lock& rLock, + sal_Int32* plastChanceToSendRefreshRequest = nullptr ); + + virtual ~SerfLockReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +private: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + + SerfSession& m_rSession; + css::ucb::Lock m_aLock; + // if m_plastChanceToSendRefreshRequest is not 0 we are sending just refresh request + sal_Int32* m_plastChanceToSendRefreshRequest; + TimeValue m_aStartCall; + rtl::Reference< SerfInputStream > m_xInputStream; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockStore.cxx b/ucb/source/ucp/webdav/SerfLockStore.cxx new file mode 100644 index 000000000..ec2f6ae1b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockStore.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/time.h> +#include <osl/thread.hxx> +#include "SerfSession.hxx" +#include "SerfLockStore.hxx" + +using namespace http_dav_ucp; + +namespace http_dav_ucp { + +class TickerThread : public osl::Thread +{ + bool m_bFinish; + SerfLockStore & m_rLockStore; + +public: + + explicit TickerThread( SerfLockStore & rLockStore ) + : osl::Thread(), m_bFinish( false ), m_rLockStore( rLockStore ) {} + + void finish() { m_bFinish = true; } + +protected: + + virtual void SAL_CALL run() override; +}; + +} // namespace http_dav_ucp + + +void TickerThread::run() +{ + osl_setThreadName("http_dav_ucp::TickerThread"); + + SAL_INFO("ucb.ucp.webdav", "TickerThread: start." ); + + // we have to go through the loop more often to be able to finish ~quickly + const int nNth = 25; + + int nCount = nNth; + while ( !m_bFinish ) + { + if ( nCount-- <= 0 ) + { + m_rLockStore.refreshLocks(); + nCount = nNth; + } + + TimeValue aTV; + aTV.Seconds = 0; + aTV.Nanosec = 1000000000 / nNth; + wait( aTV ); + } + + SAL_INFO("ucb.ucp.webdav", "TickerThread: stop." ); +} + + +SerfLockStore::SerfLockStore() + : m_pTickerThread( nullptr ) + , m_bFinishing( false ) +{ +} + + +SerfLockStore::~SerfLockStore() +{ + stopTicker(); + m_bFinishing = true; + + // release active locks, if any. + SAL_WARN_IF( !m_aLockInfoMap.empty(), "ucb.ucp.webdav", + "SerfLockStore::~SerfLockStore - Releasing active locks!" ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + rLockInfo.second.m_xSession->UNLOCK( rLockInfo.first ); + } +} + +bool SerfLockStore::finishing() const +{ + return m_bFinishing; +} + +void SerfLockStore::startTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( !m_pTickerThread ) + { + m_pTickerThread = new TickerThread( *this ); + m_pTickerThread->create(); + } +} + + +void SerfLockStore::stopTicker() +{ + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_pTickerThread ) + { + m_pTickerThread->finish(); + m_pTickerThread->join(); + delete m_pTickerThread; + m_pTickerThread = nullptr; + } +} + +OUString SerfLockStore::getLockToken( const OUString& rLock ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + LockInfoMap::const_iterator it( m_aLockInfoMap.find( rLock ) ); + if ( it != m_aLockInfoMap.end() ) + return (*it).second.m_sToken; + + SAL_WARN("ucb.ucp.webdav", "SerfLockStore::getLockToken: lock not found!" ); + return OUString(); +} + +void SerfLockStore::addLock( const OUString& rLock, + const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap[ rLock ] + = LockInfo( sToken, xSession, nLastChanceToSendRefreshRequest ); + + startTicker(); +} + + +void SerfLockStore::updateLock( const OUString& rLock, + sal_Int32 nLastChanceToSendRefreshRequest ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + LockInfoMap::iterator it( m_aLockInfoMap.find( rLock ) ); + SAL_WARN_IF( it == m_aLockInfoMap.end(), "ucb.ucp.webdav", + "SerfLockStore::updateLock: lock not found!" ); + + if ( it != m_aLockInfoMap.end() ) + { + (*it).second.m_nLastChanceToSendRefreshRequest + = nLastChanceToSendRefreshRequest; + } +} + + +void SerfLockStore::removeLock( const OUString& rLock ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + m_aLockInfoMap.erase( rLock ); + + if ( m_aLockInfoMap.empty() ) + stopTicker(); +} + + +void SerfLockStore::refreshLocks() +{ + osl::MutexGuard aGuard( m_aMutex ); + + for ( auto& rLockInfo : m_aLockInfoMap ) + { + LockInfo & rInfo = rLockInfo.second; + if ( rInfo.m_nLastChanceToSendRefreshRequest != -1 ) + { + // 30 seconds or less remaining until lock expires? + TimeValue t1; + osl_getSystemTime( &t1 ); + if ( rInfo.m_nLastChanceToSendRefreshRequest - 30 + <= sal_Int32( t1.Seconds ) ) + { + // refresh the lock. + sal_Int32 nlastChanceToSendRefreshRequest = -1; + if ( rInfo.m_xSession->LOCK( + rLockInfo.first, &nlastChanceToSendRefreshRequest ) ) + { + rInfo.m_nLastChanceToSendRefreshRequest + = nlastChanceToSendRefreshRequest; + } + else + { + // refresh failed. stop auto-refresh. + rInfo.m_nLastChanceToSendRefreshRequest = -1; + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfLockStore.hxx b/ucb/source/ucp/webdav/SerfLockStore.hxx new file mode 100644 index 000000000..6a927562b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfLockStore.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX + +#include <map> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include "SerfSession.hxx" + +namespace http_dav_ucp +{ + +class TickerThread; + +struct LockInfo +{ + OUString m_sToken; + rtl::Reference< SerfSession > m_xSession; + sal_Int32 m_nLastChanceToSendRefreshRequest; + + LockInfo() + : m_nLastChanceToSendRefreshRequest( -1 ) {} + + LockInfo( const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + sal_Int32 nLastChanceToSendRefreshRequest ) + : m_sToken( sToken ), + m_xSession( xSession ), + m_nLastChanceToSendRefreshRequest( nLastChanceToSendRefreshRequest ) {} +}; + +typedef std::map< OUString, LockInfo > LockInfoMap; + +class SerfLockStore +{ + osl::Mutex m_aMutex; + TickerThread * m_pTickerThread; + bool m_bFinishing; + LockInfoMap m_aLockInfoMap; + +public: + SerfLockStore(); + ~SerfLockStore(); + + bool finishing() const; + OUString getLockToken( const OUString& rLock ); + + void addLock( const OUString& rLock, + const OUString& sToken, + rtl::Reference< SerfSession > const & xSession, + // time in seconds since Jan 1 1970 + // -1: infinite lock, no refresh + sal_Int32 nLastChanceToSendRefreshRequest ); + + void updateLock( const OUString& rLock, + sal_Int32 nLastChanceToSendRefreshRequest ); + + void removeLock( const OUString& rLock ); + + void refreshLocks(); + +private: + void startTicker(); + void stopTicker(); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFLOCKSTORE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx new file mode 100644 index 000000000..4641b3f35 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.cxx @@ -0,0 +1,67 @@ +/* -*- 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 "SerfMkColReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfMkColReqProcImpl::SerfMkColReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : SerfRequestProcessorImpl( inPath,inRequestHeaders ) +{ +} + +SerfMkColReqProcImpl::~SerfMkColReqProcImpl() +{ +} + +serf_bucket_t * SerfMkColReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "MKCOL", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + return req_bkt; +} + +void SerfMkColReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfMkColReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx new file mode 100644 index 000000000..08269ef47 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMkColReqProcImpl.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfMkColReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfMkColReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfMkColReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMKCOLREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx new file mode 100644 index 000000000..fc775abd3 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.cxx @@ -0,0 +1,82 @@ +/* -*- 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 "SerfMoveReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfMoveReqProcImpl::SerfMoveReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ) + : SerfRequestProcessorImpl( inSourcePath, inRequestHeaders ) + , mDestPathStr( inDestinationPath ) + , mbOverwrite( inOverwrite ) +{ +} + +SerfMoveReqProcImpl::~SerfMoveReqProcImpl() +{ +} + +serf_bucket_t * SerfMoveReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "MOVE", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // MOVE specific header fields + serf_bucket_headers_set( hdrs_bkt, "Destination", mDestPathStr ); + if ( mbOverwrite ) + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "T" ); + } + else + { + serf_bucket_headers_set( hdrs_bkt, "Overwrite", "F" ); + } + + return req_bkt; +} + +void SerfMoveReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfMoveReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfMoveReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.hxx new file mode 100644 index 000000000..73a8574b4 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfMoveReqProcImpl.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfMoveReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfMoveReqProcImpl( const char* inSourcePath, + const DAVRequestHeaders& inRequestHeaders, + const char* inDestinationPath, + const bool inOverwrite ); + + virtual ~SerfMoveReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mDestPathStr; + const bool mbOverwrite; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFMOVEREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPostReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPostReqProcImpl.cxx new file mode 100644 index 000000000..bbef1970e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPostReqProcImpl.cxx @@ -0,0 +1,126 @@ +/* -*- 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 "SerfPostReqProcImpl.hxx" + +#include <serf.h> + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfPostReqProcImpl::SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const rtl::Reference< SerfInputStream > & xioInStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpPostData( inData ) + , mnPostDataLen( inDataLen ) + , mpContentType( inContentType ) + , mpReferer( inReferer ) + , xInputStream( xioInStrm ) + , xOutputStream() +{ +} + +SerfPostReqProcImpl::SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpPostData( inData ) + , mnPostDataLen( inDataLen ) + , mpContentType( inContentType ) + , mpReferer( inReferer ) + , xInputStream() + , xOutputStream( xioOutStrm ) +{ +} + +SerfPostReqProcImpl::~SerfPostReqProcImpl() +{ +} + +serf_bucket_t * SerfPostReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // create body bucket + serf_bucket_t* body_bkt = nullptr; + if ( mpPostData != nullptr && mnPostDataLen > 0 ) + { + body_bkt = SERF_BUCKET_SIMPLE_STRING_LEN( mpPostData, mnPostDataLen, pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "POST", + getPathStr(), + body_bkt, + serf_request_get_alloc( inSerfRequest ) ); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + handleChunkedEncoding(req_bkt, mnPostDataLen); + + // request specific header fields + if ( mpContentType != nullptr ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", mpContentType ); + } + if ( mpReferer != nullptr ) + { + serf_bucket_headers_set( hdrs_bkt, "Referer", mpReferer ); + } + + return req_bkt; +} + +void SerfPostReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } + else if ( xOutputStream.is() ) + { + const uno::Sequence< sal_Int8 > aDataSeq( reinterpret_cast<const sal_Int8 *>(data), len ); + xOutputStream->writeBytes( aDataSeq ); + } +} + +void SerfPostReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPostReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPostReqProcImpl.hxx new file mode 100644 index 000000000..aebd60b36 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPostReqProcImpl.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> + +namespace http_dav_ucp +{ + +class SerfPostReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const rtl::Reference< SerfInputStream > & xioInStrm ); + + SerfPostReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const char* inContentType, + const char* inReferer, + const css::uno::Reference< css::io::XOutputStream > & xioOutStrm ); + + virtual ~SerfPostReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mpPostData; + apr_size_t mnPostDataLen; + const char* mpContentType; + const char* mpReferer; + rtl::Reference< SerfInputStream > xInputStream; + css::uno::Reference< css::io::XOutputStream > xOutputStream; + +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPOSTREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx new file mode 100644 index 000000000..39a471fee --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.cxx @@ -0,0 +1,195 @@ +/* -*- 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 "SerfPropFindReqProcImpl.hxx" +#include "DAVProperties.hxx" + +#include "webdavresponseparser.hxx" +#include <rtl/strbuf.hxx> + + +using namespace com::sun::star; + +namespace http_dav_ucp +{ + +SerfPropFindReqProcImpl::SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mDepthStr( nullptr ) + , mpPropNames( &inPropNames ) + , mpResources( &ioResources ) + , mpResInfo( nullptr ) + , mbOnlyPropertyNames( false ) + , xInputStream( new SerfInputStream() ) +{ + init( inDepth ); +} + +SerfPropFindReqProcImpl::SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mDepthStr( nullptr ) + , mpPropNames( nullptr ) + , mpResources( nullptr ) + , mpResInfo( &ioResInfo ) + , mbOnlyPropertyNames( true ) + , xInputStream( new SerfInputStream() ) +{ + init( inDepth ); +} + +void SerfPropFindReqProcImpl::init( const Depth inDepth ) +{ + switch ( inDepth ) + { + case DAVZERO: + mDepthStr = "0"; + break; + case DAVONE: + mDepthStr = "1"; + break; + case DAVINFINITY: + mDepthStr = "infinity"; + break; + } +} + +SerfPropFindReqProcImpl::~SerfPropFindReqProcImpl() +{ +} + +#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" +#define PROPFIND_TRAILER "</propfind>" + +serf_bucket_t * SerfPropFindReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // body bucket - certain properties OR all properties OR only property names + serf_bucket_t* body_bkt = nullptr; + OString aBodyText; + { + OStringBuffer aBuffer; + aBuffer.append( PROPFIND_HEADER ); + + // create and fill body bucket with requested properties + const int nPropCount = ( !mbOnlyPropertyNames && mpPropNames ) + ? mpPropNames->size() + : 0; + if ( nPropCount > 0 ) + { + aBuffer.append( "<prop>" ); + SerfPropName thePropName; + for ( int theIndex = 0; theIndex < nPropCount; theIndex ++ ) + { + // split fullname into namespace and name! + DAVProperties::createSerfPropName( (*mpPropNames)[ theIndex ], + thePropName ); + + /* <*propname* xmlns="*propns*" /> */ + aBuffer.append( "<" ); + aBuffer.append( thePropName.name ); + aBuffer.append( " xmlns=\"" ); + aBuffer.append( thePropName.nspace ); + aBuffer.append( "\"/>" ); + } + + aBuffer.append( "</prop>" ); + } + else + { + if ( mbOnlyPropertyNames ) + { + aBuffer.append( "<propname/>" ); + } + else + { + aBuffer.append( "<allprop/>" ); + } + } + + aBuffer.append( PROPFIND_TRAILER ); + aBodyText = aBuffer.makeStringAndClear(); + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PROPFIND", + getPathStr(), + body_bkt, + pSerfBucketAlloc ); + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + if (hdrs_bkt != nullptr) + { + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + serf_bucket_headers_set( hdrs_bkt, "Depth", mDepthStr ); + if (hdrs_bkt!=nullptr && body_bkt != nullptr && aBodyText.getLength() > 0 ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + } + else + { + assert(!"Headers Bucket missing"); + } + + return req_bkt; +} + +void SerfPropFindReqProcImpl::processChunkOfResponseData( const char* data, + apr_size_t len ) +{ + if ( xInputStream.is() ) + { + xInputStream->AddToStream( data, len ); + } +} + +void SerfPropFindReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + if ( mbOnlyPropertyNames ) + { + const std::vector< DAVResourceInfo > rResInfo( parseWebDAVPropNameResponse( xInputStream.get() ) ); + *mpResInfo = rResInfo; + } + else + { + const std::vector< DAVResource > rResources( parseWebDAVPropFindResponse( xInputStream.get() ) ); + *mpResources = rResources; + } +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx new file mode 100644 index 000000000..2acabda00 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropFindReqProcImpl.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include <rtl/ustring.hxx> +#include "DAVTypes.hxx" +#include "DAVResource.hxx" + +#include "SerfInputStream.hxx" + +namespace http_dav_ucp +{ + +class SerfPropFindReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources ); + + SerfPropFindReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo ); + + virtual ~SerfPropFindReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + void init( const Depth inDepth ); + + const char* mDepthStr; + const std::vector< OUString > * mpPropNames; + std::vector< DAVResource > * mpResources; + std::vector< DAVResourceInfo > * mpResInfo; + + const bool mbOnlyPropertyNames; + rtl::Reference< SerfInputStream > xInputStream; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPFINDREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx new file mode 100644 index 000000000..4540ccbd0 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.cxx @@ -0,0 +1,189 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include "DAVProperties.hxx" +#include "UCBDeadPropertyValue.hxx" + +#include "SerfPropPatchReqProcImpl.hxx" + +namespace http_dav_ucp +{ + +SerfPropPatchReqProcImpl::SerfPropPatchReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< ProppatchValue > & inProperties ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpProperties( &inProperties ) +{ +} + +SerfPropPatchReqProcImpl::~SerfPropPatchReqProcImpl() +{ +} + +#define PROPPATCH_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propertyupdate xmlns=\"DAV:\">" +#define PROPPATCH_TRAILER "</propertyupdate>" + +serf_bucket_t * SerfPropPatchReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // body bucket + serf_bucket_t* body_bkt = nullptr; + OString aBodyText; + { + // create and fill body bucket with properties to be set or removed + static const struct + { + const char *str; + sal_Int32 len; + } + OpCode [] = { + { RTL_CONSTASCII_STRINGPARAM( "set" ) }, + { RTL_CONSTASCII_STRINGPARAM( "remove" ) } + }; + const int nPropCount = ( mpProperties != nullptr ) + ? mpProperties->size() + : 0; + if ( nPropCount > 0 ) + { + OUStringBuffer aBuffer; + // add PropPatch xml header in front + aBuffer.append( PROPPATCH_HEADER ); + + // <*operation code*><prop> + + ProppatchOperation lastOp = (*mpProperties)[ 0 ].operation; + aBuffer.append( "<" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( "><prop>" ); + + SerfPropName thePropName; + for ( int n = 0; n < nPropCount; ++n ) + { + const ProppatchValue & rProperty = (*mpProperties)[ n ]; + // split fullname into namespace and name! + DAVProperties::createSerfPropName( rProperty.name, + thePropName ); + + if ( rProperty.operation != lastOp ) + { + // </prop></*last operation code*><*operation code><prop> + aBuffer.append( "</prop></" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( "><" ); + aBuffer.appendAscii( OpCode[rProperty.operation].str, OpCode[rProperty.operation].len ); + aBuffer.append( "><prop>" ); + } + + // <*propname* xmlns="*propns*" + aBuffer.append( "<" ); + aBuffer.appendAscii( thePropName.name ); + aBuffer.append( " xmlns=\"" ); + aBuffer.appendAscii( thePropName.nspace ); + aBuffer.append( "\"" ); + + if ( rProperty.operation == PROPSET ) + { + // >*property value*</*propname*> + aBuffer.append( ">" ); + + OUString aStringValue; + if ( DAVProperties::isUCBDeadProperty( thePropName ) ) + { + UCBDeadPropertyValue::toXML( rProperty.value, + aStringValue ); + } + else + { + rProperty.value >>= aStringValue; + } + aBuffer.append( aStringValue ); + aBuffer.append( "</" ); + aBuffer.appendAscii( thePropName.name ); + aBuffer.append( ">" ); + } + else + { + aBuffer.append( "/>" ); + } + + lastOp = rProperty.operation; + } + + // </prop></*last operation code*> + aBuffer.append( "</prop></" ); + aBuffer.appendAscii( OpCode[lastOp].str, OpCode[lastOp].len ); + aBuffer.append( ">" ); + + // add PropPatch xml trailer at end + aBuffer.append( PROPPATCH_TRAILER ); + + aBodyText = OUStringToOString( aBuffer.makeStringAndClear(), RTL_TEXTENCODING_UTF8 ); + body_bkt = serf_bucket_simple_copy_create( aBodyText.getStr(), + aBodyText.getLength(), + pSerfBucketAlloc ); + } + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PROPPATCH", + getPathStr(), + body_bkt, + pSerfBucketAlloc ) ; + handleChunkedEncoding(req_bkt, aBodyText.getLength()); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + if (hdrs_bkt != nullptr) + { + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // request specific header fields + if ( body_bkt != nullptr && aBodyText.getLength() > 0 ) + { + serf_bucket_headers_set( hdrs_bkt, "Content-Type", "application/xml" ); + } + } + else + { + assert(!"Headers Bucket missing"); + } + + return req_bkt; +} + +void SerfPropPatchReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfPropPatchReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.hxx new file mode 100644 index 000000000..330e0d953 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPropPatchReqProcImpl.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +#include <vector> +#include "DAVTypes.hxx" + +namespace http_dav_ucp +{ + +class SerfPropPatchReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPropPatchReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const std::vector< ProppatchValue > & inProperties ); + + virtual ~SerfPropPatchReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const std::vector< ProppatchValue > * mpProperties; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPROPPATCHREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx new file mode 100644 index 000000000..466f7a10c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPutReqProcImpl.cxx @@ -0,0 +1,90 @@ +/* -*- 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 <rtl/ustring.hxx> + +#include "SerfPutReqProcImpl.hxx" + +#include <serf.h> + +namespace http_dav_ucp +{ + +SerfPutReqProcImpl::SerfPutReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const OUString& sToken ) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , mpData( inData ) + , mnDataLen( inDataLen ) + , msToken( sToken ) +{ +} + +SerfPutReqProcImpl::~SerfPutReqProcImpl() +{ +} + +serf_bucket_t * SerfPutReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + serf_bucket_alloc_t* pSerfBucketAlloc = serf_request_get_alloc( inSerfRequest ); + + // create body bucket + serf_bucket_t* body_bkt = nullptr; + if ( mpData != nullptr && mnDataLen > 0 ) + { + body_bkt = SERF_BUCKET_SIMPLE_STRING_LEN( mpData, mnDataLen, pSerfBucketAlloc ); + } + + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "PUT", + getPathStr(), + body_bkt, + serf_request_get_alloc( inSerfRequest ) ); + handleChunkedEncoding(req_bkt, mnDataLen); + + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // 'If' header with token, so that we can save document locked by us + const OString sIfHeader( "<" + rtl::OStringView(getPathStr()) + "> (<" + OUStringToOString( + msToken, RTL_TEXTENCODING_UTF8) + ">)" ); + serf_bucket_headers_set( hdrs_bkt, "If", sIfHeader.getStr() ); + + return req_bkt; +} + +void SerfPutReqProcImpl::processChunkOfResponseData( const char* /*data*/, + apr_size_t /*len*/ ) +{ + // nothing to do; +} + +void SerfPutReqProcImpl::handleEndOfResponseData( serf_bucket_t * /*inSerfResponseBucket*/ ) +{ + // nothing to do; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfPutReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfPutReqProcImpl.hxx new file mode 100644 index 000000000..8eeb791ee --- /dev/null +++ b/ucb/source/ucp/webdav/SerfPutReqProcImpl.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfPutReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfPutReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const char* inData, + apr_size_t inDataLen, + const OUString& sToken ); + + + virtual ~SerfPutReqProcImpl() override; + + virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) override; + +protected: + virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) override; + + virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) override; + +private: + const char* mpData; + apr_size_t mnDataLen; + OUString msToken; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFPUTREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessor.cxx b/ucb/source/ucp/webdav/SerfRequestProcessor.cxx new file mode 100644 index 000000000..21cc15ec0 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessor.cxx @@ -0,0 +1,596 @@ +/* -*- 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 "SerfRequestProcessor.hxx" + +#include "AprEnv.hxx" +#include "SerfCallbacks.hxx" +#include "SerfSession.hxx" +#include "SerfPropFindReqProcImpl.hxx" +#include "SerfPropPatchReqProcImpl.hxx" +#include "SerfGetReqProcImpl.hxx" +#include "SerfHeadReqProcImpl.hxx" +#include "SerfPutReqProcImpl.hxx" +#include "SerfPostReqProcImpl.hxx" +#include "SerfDeleteReqProcImpl.hxx" +#include "SerfMkColReqProcImpl.hxx" +#include "SerfCopyReqProcImpl.hxx" +#include "SerfMoveReqProcImpl.hxx" +#include "SerfLockReqProcImpl.hxx" +#include "SerfUnlockReqProcImpl.hxx" + +#include <apr_strings.h> + +namespace http_dav_ucp +{ + +SerfRequestProcessor::SerfRequestProcessor( SerfSession& rSerfSession, + const OUString & inPath, + const bool bUseChunkedEncoding ) + : mrSerfSession( rSerfSession ) + , mPathStr( nullptr ) + , mbUseChunkedEncoding( bUseChunkedEncoding ) + , mDestPathStr( nullptr ) + , mContentType( nullptr ) + , mReferer( nullptr ) + , mpProcImpl( nullptr ) + , mbProcessingDone( false ) + , mpDAVException() + , mnHTTPStatusCode( SC_NONE ) + , mHTTPStatusCodeText() + , mRedirectLocation() + , mnSuccessfulCredentialAttempts( 0 ) + , mbInputOfCredentialsAborted( false ) + , mbSetupSerfRequestCalled( false ) + , mbAcceptSerfResponseCalled( false ) + , mbHandleSerfResponseCalled( false ) +{ + mPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inPath, RTL_TEXTENCODING_UTF8 ).getStr() ); +} + +SerfRequestProcessor::~SerfRequestProcessor() +{ + delete mpProcImpl; + delete mpDAVException; +} + +void SerfRequestProcessor::prepareProcessor() +{ + delete mpDAVException; + mpDAVException = nullptr; + mnHTTPStatusCode = SC_NONE; + mHTTPStatusCodeText.clear(); + mRedirectLocation.clear(); + + mnSuccessfulCredentialAttempts = 0; + mbInputOfCredentialsAborted = false; + mbSetupSerfRequestCalled = false; + mbAcceptSerfResponseCalled = false; + mbHandleSerfResponseCalled = false; +} + +// PROPFIND - allprop & named +bool SerfRequestProcessor::processPropFind( const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropFindReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inDepth, + inPropNames, + ioResources ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PROPFIND - property names +bool SerfRequestProcessor::processPropFind( const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropFindReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inDepth, + ioResInfo ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PROPPATCH +bool SerfRequestProcessor::processPropPatch( const std::vector< ProppatchValue > & inProperties, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfPropPatchReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inProperties ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET +bool SerfRequestProcessor::processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioInStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET inclusive header fields +bool SerfRequestProcessor::processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioInStrm, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET +bool SerfRequestProcessor::processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioOutStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// GET inclusive header fields +bool SerfRequestProcessor::processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfGetReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + xioOutStrm, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// HEAD +bool SerfRequestProcessor::processHead( const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfHeadReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inHeaderNames, + ioResource ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// PUT +bool SerfRequestProcessor::processPut( const char* inData, + apr_size_t inDataLen, + apr_status_t& outSerfStatus ) +{ + // get the lock from lock store + const OUString sToken( + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->getLockToken( + OUString::createFromAscii(mPathStr)) ); + + mpProcImpl = new SerfPutReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + sToken ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// POST +bool SerfRequestProcessor::processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ) +{ + mContentType = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inContentType, RTL_TEXTENCODING_UTF8 ).getStr() ); + mReferer = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inReferer, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfPostReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + mContentType, + mReferer, + xioInStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// POST +bool SerfRequestProcessor::processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ) +{ + mContentType = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inContentType, RTL_TEXTENCODING_UTF8 ).getStr() ); + mReferer = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inReferer, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfPostReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + inData, + inDataLen, + mContentType, + mReferer, + xioOutStrm ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// DELETE +bool SerfRequestProcessor::processDelete( apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfDeleteReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// MKCOL +bool SerfRequestProcessor::processMkCol( apr_status_t& outSerfStatus ) +{ + mpProcImpl = new SerfMkColReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// COPY +bool SerfRequestProcessor::processCopy( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ) +{ + mDestPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inDestinationPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfCopyReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mDestPathStr, + inOverwrite ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + +// MOVE +bool SerfRequestProcessor::processMove( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ) +{ + mDestPathStr = apr_pstrdup( SerfSession::getAprPool(), + OUStringToOString( inDestinationPath, RTL_TEXTENCODING_UTF8 ).getStr() ); + mpProcImpl = new SerfMoveReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mDestPathStr, + inOverwrite ); + outSerfStatus = runProcessor(); + + return outSerfStatus == APR_SUCCESS; +} + + +bool SerfRequestProcessor::processLock( const css::ucb::Lock & rLock, sal_Int32 *plastChanceToSendRefreshRequest ) +{ + mpProcImpl = new SerfLockReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + mrSerfSession, + rLock, + plastChanceToSendRefreshRequest ); + + return runProcessor() == APR_SUCCESS; +} + +bool SerfRequestProcessor::processUnlock() +{ + // get the lock from lock store + const OUString sToken( + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->getLockToken( + OUString::createFromAscii(mPathStr)) ); + if ( sToken.isEmpty() ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + mpProcImpl = new SerfUnlockReqProcImpl( mPathStr, + mrSerfSession.getRequestEnvironment().m_aRequestHeaders, + sToken ); + + return runProcessor() == APR_SUCCESS; +} + +apr_status_t SerfRequestProcessor::runProcessor() +{ + prepareProcessor(); + + // activate chunked encoding, if requested + if ( mbUseChunkedEncoding ) + { + mpProcImpl->activateChunkedEncoding(); + } + + // create serf request + serf_connection_request_create( mrSerfSession.getSerfConnection(), + Serf_SetupRequest, + this ); + + // perform serf request + mbProcessingDone = false; + apr_status_t status = APR_SUCCESS; + serf_context_t* pSerfContext = mrSerfSession.getSerfContext(); + apr_pool_t* pAprPool = SerfSession::getAprPool(); + while ( true ) + { + status = serf_context_run( pSerfContext, + SERF_DURATION_FOREVER, + pAprPool ); + if ( APR_STATUS_IS_TIMEUP( status ) ) + { + continue; + } + if ( status != APR_SUCCESS ) + { + break; + } + if ( mbProcessingDone ) + { + break; + } + } + + postprocessProcessor( status ); + + return status; +} + +void SerfRequestProcessor::postprocessProcessor( const apr_status_t inStatus ) +{ + if ( inStatus == APR_SUCCESS ) + { + return; + } + + switch ( inStatus ) + { + case APR_EGENERAL: + case SERF_ERROR_AUTHN_FAILED: + // general error; <mnHTTPStatusCode> provides more information + { + switch ( mnHTTPStatusCode ) + { + case SC_NONE: + if ( !mbSetupSerfRequestCalled ) + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_LOOKUP, + SerfUri::makeConnectionEndPointString( mrSerfSession.getHostName(), + mrSerfSession.getPort() ) ); + } + else if ( mbInputOfCredentialsAborted ) + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_NOAUTH, + SerfUri::makeConnectionEndPointString( mrSerfSession.getHostName(), + mrSerfSession.getPort() ) ); + } + else + { + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR, + mHTTPStatusCodeText, + mnHTTPStatusCode ); + } + break; + case SC_MOVED_PERMANENTLY: + case SC_MOVED_TEMPORARILY: + case SC_SEE_OTHER: + case SC_TEMPORARY_REDIRECT: + mpDAVException = new DAVException( DAVException::DAV_HTTP_REDIRECT, + mRedirectLocation ); + break; + default: + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR, + mHTTPStatusCodeText, + mnHTTPStatusCode ); + break; + } + } + break; + + default: + mpDAVException = new DAVException( DAVException::DAV_HTTP_ERROR ); + break; + } + +} + +apr_status_t SerfRequestProcessor::provideSerfCredentials( char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ) +{ + // as each successful provided credentials are tried twice - see below - the + // number of real attempts is half of the value of <mnSuccessfulCredentialAttempts> + if ( (mnSuccessfulCredentialAttempts / 2) >= 5 || + mbInputOfCredentialsAborted ) + { + mbInputOfCredentialsAborted = true; + return SERF_ERROR_AUTHN_FAILED; + } + + // because serf keeps credentials only for a connection in case of digest authentication + // we give each successful provided credentials a second try in order to workaround the + // situation that the connection for which the credentials have been provided has been closed + // before the provided credentials could be applied for the request. + apr_status_t status = mrSerfSession.provideSerfCredentials( (mnSuccessfulCredentialAttempts % 2) == 1, + outUsername, + outPassword, + inRequest, + inCode, + inAuthProtocol, + inRealm, + inAprPool ); + if ( status != APR_SUCCESS ) + { + mbInputOfCredentialsAborted = true; + } + else + { + ++mnSuccessfulCredentialAttempts; + } + + return status; +} + +apr_status_t SerfRequestProcessor::setupSerfRequest( serf_request_t * inSerfRequest, + serf_bucket_t ** outSerfRequestBucket, + serf_response_acceptor_t * outSerfResponseAcceptor, + void ** outSerfResponseAcceptorBaton, + serf_response_handler_t * outSerfResponseHandler, + void ** outSerfResponseHandlerBaton, + apr_pool_t * /*inAprPool*/ ) +{ + mbSetupSerfRequestCalled = true; + *outSerfRequestBucket = mpProcImpl->createSerfRequestBucket( inSerfRequest ); + + // apply callbacks for accepting response and handling response + *outSerfResponseAcceptor = Serf_AcceptResponse; + *outSerfResponseAcceptorBaton = this; + *outSerfResponseHandler = Serf_HandleResponse; + *outSerfResponseHandlerBaton = this; + + return APR_SUCCESS; +} + +serf_bucket_t* SerfRequestProcessor::acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t * inAprPool ) +{ + mbAcceptSerfResponseCalled = true; + return mrSerfSession.acceptSerfResponse( inSerfRequest, + inSerfStreamBucket, + inAprPool ); +} + +apr_status_t SerfRequestProcessor::handleSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool ) +{ + mbHandleSerfResponseCalled = true; + + // some general response handling and error handling + { + if ( !inSerfResponseBucket ) + { + /* A NULL response can come back if the request failed completely */ + mbProcessingDone = true; + return APR_EGENERAL; + } + + serf_status_line sl; + apr_status_t status = serf_bucket_response_status( inSerfResponseBucket, &sl ); + if ( status ) + { + mbProcessingDone = false; // allow another try in order to get a response + return status; + } + // TODO - check, if response status code handling is correct + mnHTTPStatusCode = ( sl.version != 0 && sl.code >= 0 ) + ? static_cast< sal_uInt16 >( sl.code ) + : SC_NONE; + if ( sl.reason ) + { + mHTTPStatusCodeText = OUString::createFromAscii( sl.reason ); + } + if ( ( sl.version == 0 || sl.code < 0 ) || + mnHTTPStatusCode >= 300 ) + { + if ( mnHTTPStatusCode == 301 || + mnHTTPStatusCode == 302 || + mnHTTPStatusCode == 303 || + mnHTTPStatusCode == 307 ) + { + // new location for certain redirections + serf_bucket_t *headers = serf_bucket_response_get_headers( inSerfResponseBucket ); + const char* location = serf_bucket_headers_get( headers, "Location" ); + if ( location ) + { + mRedirectLocation = OUString::createFromAscii( location ); + } + mbProcessingDone = true; + return APR_EGENERAL; + } + else if ( mrSerfSession.isHeadRequestInProgress() && + ( mnHTTPStatusCode == 401 || mnHTTPStatusCode == 407 ) ) + { + // keep going as authentication is not required on HEAD request. + // the response already contains header fields. + } + else + { + mbProcessingDone = true; + return APR_EGENERAL; + } + } + } + + // request specific processing of the response bucket + apr_status_t status = APR_SUCCESS; + mbProcessingDone = mpProcImpl->processSerfResponseBucket( inSerfRequest, + inSerfResponseBucket, + inAprPool, + status ); + + return status; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessor.hxx b/ucb/source/ucp/webdav/SerfRequestProcessor.hxx new file mode 100644 index 000000000..dea753fed --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessor.hxx @@ -0,0 +1,191 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX + +#include <apr_errno.h> +#include <apr_pools.h> + +#include <serf.h> + +#include "DAVTypes.hxx" +#include "DAVResource.hxx" +#include "DAVException.hxx" + +#include "SerfInputStream.hxx" +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ucb/Lock.hpp> + +#include <rtl/ref.hxx> + +namespace http_dav_ucp +{ + +class SerfSession; +class SerfRequestProcessorImpl; + +class SerfRequestProcessor +{ +public: + SerfRequestProcessor( SerfSession& rSerfSession, + const OUString & inPath, + const bool bUseChunkedEncoding ); + ~SerfRequestProcessor(); + + // PROPFIND - allprop & named + bool processPropFind( const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + apr_status_t& outSerfStatus ); + + // PROPFIND - property names + bool processPropFind( const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + apr_status_t& outSerfStatus ); + + // PROPPATCH + bool processPropPatch( const std::vector< ProppatchValue > & inProperties, + apr_status_t& outSerfStatus ); + + // GET + bool processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ); + + // GET inclusive header fields + bool processGet( const rtl::Reference< SerfInputStream >& xioInStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // GET + bool processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ); + + // GET inclusive header fields + bool processGet( const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // HEAD + bool processHead( const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + apr_status_t& outSerfStatus ); + + // PUT + bool processPut( const char* inData, + apr_size_t inDataLen, + apr_status_t& outSerfStatus ); + + // POST + bool processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const rtl::Reference< SerfInputStream >& xioInStrm, + apr_status_t& outSerfStatus ); + + // POST + bool processPost( const char* inData, + apr_size_t inDataLen, + const OUString & inContentType, + const OUString & inReferer, + const css::uno::Reference< css::io::XOutputStream >& xioOutStrm, + apr_status_t& outSerfStatus ); + + // DELETE + bool processDelete( apr_status_t& outSerfStatus ); + + // MKCOL + bool processMkCol( apr_status_t& outSerfStatus ); + + // COPY + bool processCopy( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ); + + // MOVE + bool processMove( const OUString & inDestinationPath, + const bool inOverwrite, + apr_status_t& outSerfStatus ); + + //LOCK + bool processLock( const css::ucb::Lock & rLock, sal_Int32 *plastChanceToSendRefreshRequest = nullptr ); + + //UNLOCK + bool processUnlock(); + + apr_status_t provideSerfCredentials( char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ); + + apr_status_t setupSerfRequest( serf_request_t * inSerfRequest, + serf_bucket_t ** outSerfRequestBucket, + serf_response_acceptor_t * outSerfResponseAcceptor, + void ** outSerfResponseAcceptorBaton, + serf_response_handler_t * outSerfResponseHandler, + void ** outSerfResponseHandlerBaton, + apr_pool_t * inAprPool ); + + serf_bucket_t* acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* inAprPool ); + + apr_status_t handleSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool ); + +//private: + void prepareProcessor(); + apr_status_t runProcessor(); + void postprocessProcessor( const apr_status_t inStatus ); + + SerfSession& mrSerfSession; + const char* mPathStr; + const bool mbUseChunkedEncoding; + const char* mDestPathStr; + const char* mContentType; + const char* mReferer; + SerfRequestProcessorImpl* mpProcImpl; + + bool mbProcessingDone; + + DAVException* mpDAVException; + sal_uInt16 mnHTTPStatusCode; + OUString mHTTPStatusCodeText; + OUString mRedirectLocation; + + sal_uInt8 mnSuccessfulCredentialAttempts; + bool mbInputOfCredentialsAborted; + bool mbSetupSerfRequestCalled; + bool mbAcceptSerfResponseCalled; + bool mbHandleSerfResponseCalled; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSOR_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx new file mode 100644 index 000000000..dffd5e907 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.cxx @@ -0,0 +1,151 @@ +/* -*- 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 "SerfRequestProcessorImpl.hxx" +#include <sal/log.hxx> + +namespace +{ +// Define a magic value that is used by serf to reset chunked +// encoding. The value definition is not supported by serf, hence the +// definition here. +static const apr_int64_t SERF_UNKNOWN_LENGTH (-1); +} + +namespace http_dav_ucp +{ + +SerfRequestProcessorImpl::SerfRequestProcessorImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ) + : mPathStr( inPath ) + , mrRequestHeaders( inRequestHeaders ) + , mbUseChunkedEncoding( false ) +{ +} + +SerfRequestProcessorImpl::~SerfRequestProcessorImpl() +{ +} + +const char* SerfRequestProcessorImpl::getPathStr() const +{ + return mPathStr; +} + +void SerfRequestProcessorImpl::activateChunkedEncoding() +{ + mbUseChunkedEncoding = true; +} + +bool SerfRequestProcessorImpl::useChunkedEncoding() const +{ + return mbUseChunkedEncoding; +} + + +void SerfRequestProcessorImpl::handleChunkedEncoding ( + serf_bucket_t* pRequestBucket, + apr_int64_t nLength) const +{ + if (pRequestBucket != nullptr) + { + if (useChunkedEncoding()) + { + // Activate chunked encoding. + serf_bucket_request_set_CL(pRequestBucket, SERF_UNKNOWN_LENGTH); + } + else + { + // Deactivate chunked encoding by setting the length. + serf_bucket_request_set_CL(pRequestBucket, nLength); + } + } +} + + +void SerfRequestProcessorImpl::setRequestHeaders( serf_bucket_t* inoutSerfHeaderBucket ) +{ + bool bHasUserAgent( false ); + + for ( const auto& rHeader : mrRequestHeaders ) + { + const OString aHeader = OUStringToOString( rHeader.first, RTL_TEXTENCODING_UTF8 ); + const OString aValue = OUStringToOString( rHeader.second, RTL_TEXTENCODING_UTF8 ); + + SAL_INFO("ucb.ucp.webdav", "Request Header - \"" << aHeader << ": " << aValue << "\""); + if ( !bHasUserAgent ) + bHasUserAgent = rHeader.first == "User-Agent"; + + serf_bucket_headers_setc( inoutSerfHeaderBucket, + aHeader.getStr(), + aValue.getStr() ); + } + + if ( !bHasUserAgent ) + { + serf_bucket_headers_set( inoutSerfHeaderBucket, + "User-Agent", "LibreOffice" ); + } + + serf_bucket_headers_set( inoutSerfHeaderBucket, "Accept-Encoding", "gzip"); +} + +bool SerfRequestProcessorImpl::processSerfResponseBucket( serf_request_t * /*inSerfRequest*/, + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * /*inAprPool*/, + apr_status_t & outStatus ) +{ + const char* data; + apr_size_t len; + + while (true) { + outStatus = serf_bucket_read(inSerfResponseBucket, 8096, &data, &len); + if (SERF_BUCKET_READ_ERROR(outStatus)) + { + return true; + } + + if ( len > 0 ) + { + processChunkOfResponseData( data, len ); + } + + /* are we done yet? */ + if (APR_STATUS_IS_EOF(outStatus)) + { + handleEndOfResponseData( inSerfResponseBucket ); + + outStatus = APR_EOF; + return true; + } + + /* have we drained the response so far? */ + if ( APR_STATUS_IS_EAGAIN( outStatus ) ) + { + return false; + } + } + + /* NOTREACHED */ + return true; +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx new file mode 100644 index 000000000..3909dca5a --- /dev/null +++ b/ucb/source/ucp/webdav/SerfRequestProcessorImpl.hxx @@ -0,0 +1,79 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX + +#include <serf.h> + +#include <sal/types.h> +#include "DAVRequestEnvironment.hxx" + +namespace http_dav_ucp +{ + +class SerfRequestProcessorImpl +{ +public: + SerfRequestProcessorImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders ); + + virtual ~SerfRequestProcessorImpl(); + + /*pure*/ virtual + serf_bucket_t * createSerfRequestBucket( serf_request_t * inSerfRequest ) = 0; + + bool processSerfResponseBucket( serf_request_t * inSerfRequest, + + serf_bucket_t * inSerfResponseBucket, + apr_pool_t * inAprPool, + apr_status_t & outStatus ); + + void activateChunkedEncoding(); + + /** Turn chunked encoding on or off, depending on the result of + useChunkedEncoding(). + */ + void handleChunkedEncoding ( + serf_bucket_t* pRequestBucket, + apr_int64_t nLength) const; + +protected: + void setRequestHeaders( serf_bucket_t* inoutSerfHeaderBucket ); + + /*pure*/ virtual + void processChunkOfResponseData( const char* data, apr_size_t len ) = 0; + + /*pure*/ virtual + void handleEndOfResponseData( serf_bucket_t * inSerfResponseBucket ) = 0; + + const char* getPathStr() const; + bool useChunkedEncoding() const; + +private: + const char* mPathStr; + const DAVRequestHeaders& mrRequestHeaders; + bool mbUseChunkedEncoding; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFREQUESTPROCESSORIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfSession.cxx b/ucb/source/ucp/webdav/SerfSession.cxx new file mode 100644 index 000000000..8f7a7e38c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfSession.cxx @@ -0,0 +1,1489 @@ +/* -*- 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 <vector> +#include <string.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <ucbhelper/simplecertificatevalidationrequest.hxx> + +#include "AprEnv.hxx" +#include <apr_strings.h> + +#include "DAVAuthListener.hxx" +#include "SerfSession.hxx" +#include "SerfUri.hxx" +#include "SerfRequestProcessor.hxx" +#include "SerfCallbacks.hxx" +#include "SerfInputStream.hxx" + +#include <com/sun/star/xml/crypto/SEInitializer.hpp> +#include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/security/CertificateValidity.hpp> +#include <com/sun/star/security/CertificateContainerStatus.hpp> +#include <com/sun/star/security/CertificateContainer.hpp> +#include <com/sun/star/security/XCertificateContainer.hpp> +#include <com/sun/star/security/CertAltNameEntry.hpp> +#include <com/sun/star/security/XSanExtension.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/IOException.hpp> +#define OID_SUBJECT_ALTERNATIVE_NAME "2.5.29.17" + +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/xml/crypto/XSEInitializer.hpp> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +// Constructor + +SerfSession::SerfSession( + const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const ucbhelper::InternetProxyDecider & rProxyDecider ) + : DAVSession( rSessionFactory ) + , m_aMutex() + , m_aUri( inUri ) + , m_aProxyName() + , m_nProxyPort( 0 ) + , m_pSerfConnection( nullptr ) + , m_pSerfContext( nullptr ) + , m_bIsHeadRequestInProgress( false ) + , m_bUseChunkedEncoding( false ) + , m_bNoOfTransferEncodingSwitches( 0 ) + , m_rProxyDecider( rProxyDecider ) + , m_aEnv() +{ + m_pSerfContext = serf_context_create( getAprPool() ); + + m_pSerfBucket_Alloc = serf_bucket_allocator_create( getAprPool(), nullptr, nullptr ); +} + + +// Destructor + +SerfSession::~SerfSession( ) +{ + if ( m_pSerfConnection ) + { + serf_connection_close( m_pSerfConnection ); + m_pSerfConnection = nullptr; + } +} + + +void SerfSession::Init( const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + m_aEnv = rEnv; + Init(); +} + + +void SerfSession::Init() +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + bool bCreateNewSession = false; + + if ( m_pSerfConnection == nullptr ) + { + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // Not yet initialized. Create new session. + bCreateNewSession = true; + } + else + { + const ucbhelper::InternetProxyServer & rProxyCfg = getProxySettings(); + + if ( ( rProxyCfg.aName != m_aProxyName ) + || ( rProxyCfg.nPort != m_nProxyPort ) ) + { + m_aProxyName = rProxyCfg.aName; + m_nProxyPort = rProxyCfg.nPort; + + // new session needed, destroy old first + serf_connection_close( m_pSerfConnection ); + m_pSerfConnection = nullptr; + bCreateNewSession = true; + } + } + + if ( bCreateNewSession ) + { + // TODO - close_connection callback + apr_status_t status = serf_connection_create2( &m_pSerfConnection, + m_pSerfContext, + m_aUri.getAprUri(), + Serf_ConnectSetup, this, + nullptr /* close connection callback */, nullptr /* close connection baton */, + getAprPool() ); + + if ( m_pSerfConnection == nullptr ||status != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_SESSION_CREATE, + SerfUri::makeConnectionEndPointString( m_aUri.GetHost(), m_aUri.GetPort() ) ); + } + + // Register the session with the lock store +// m_aSerfLockStore.registerSession( m_pSerfConnection ); + + if ( m_aProxyName.getLength() ) + { + apr_sockaddr_t *proxy_address = nullptr; + status = apr_sockaddr_info_get( &proxy_address, + OUStringToOString( m_aProxyName, RTL_TEXTENCODING_UTF8 ).getStr(), + APR_UNSPEC, + static_cast<apr_port_t>(m_nProxyPort), + 0, getAprPool() ); + + if ( status != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_SESSION_CREATE, + SerfUri::makeConnectionEndPointString( m_aUri.GetHost(), m_aUri.GetPort() ) ); + } + + serf_config_proxy( m_pSerfContext, proxy_address ); + } + + + serf_config_credentials_callback( m_pSerfContext, Serf_Credentials ); + + m_bUseChunkedEncoding = isSSLNeeded(); + } +} + +apr_pool_t* SerfSession::getAprPool() +{ + return apr_environment::AprEnv::getAprEnv()->getAprPool(); +} + +serf_bucket_alloc_t* SerfSession::getSerfBktAlloc() +{ + return m_pSerfBucket_Alloc; +} + +serf_context_t* SerfSession::getSerfContext() +{ + return m_pSerfContext; +} + +serf_connection_t* SerfSession::getSerfConnection() +{ + return m_pSerfConnection; +} + +bool SerfSession::isHeadRequestInProgress() +{ + return m_bIsHeadRequestInProgress; +} + +bool SerfSession::isSSLNeeded() +{ + return m_aUri.GetScheme().equalsIgnoreAsciiCase( "https" ); +} + +char* SerfSession::getHostinfo() +{ + return m_aUri.getAprUri().hostinfo; +} + + +// virtual +bool SerfSession::CanUse( const OUString & inUri ) +{ + try + { + SerfUri theUri( inUri ); + if ( ( theUri.GetPort() == m_aUri.GetPort() ) && + ( theUri.GetHost() == m_aUri.GetHost() ) && + ( theUri.GetScheme() == m_aUri.GetScheme() ) ) + { + return true; + } + } + catch ( DAVException const & ) + { + return false; + } + return false; +} + + +// virtual +bool SerfSession::UsesProxy() +{ + Init(); + return ( m_aProxyName.getLength() > 0 ); +} + +apr_status_t SerfSession::setupSerfConnection( apr_socket_t * inAprSocket, + serf_bucket_t **outSerfInputBucket, + serf_bucket_t **outSerfOutputBucket, + apr_pool_t* /*inAprPool*/ ) +{ + serf_bucket_t *tmpInputBkt; + tmpInputBkt = serf_context_bucket_socket_create( getSerfContext(), + inAprSocket, + getSerfBktAlloc() ); + + if ( isSSLNeeded() ) + { + tmpInputBkt = serf_bucket_ssl_decrypt_create( tmpInputBkt, + nullptr, + getSerfBktAlloc() ); + /** Set the callback that is called to authenticate the + certificate (chain). + */ + serf_ssl_server_cert_chain_callback_set( + serf_bucket_ssl_decrypt_context_get(tmpInputBkt), + nullptr, + Serf_CertificateChainValidation, + this); + serf_ssl_set_hostname( serf_bucket_ssl_decrypt_context_get( tmpInputBkt ), + getHostinfo() ); + + *outSerfOutputBucket = serf_bucket_ssl_encrypt_create( *outSerfOutputBucket, + serf_bucket_ssl_decrypt_context_get( tmpInputBkt ), + getSerfBktAlloc() ); + } + + *outSerfInputBucket = tmpInputBkt; + + return APR_SUCCESS; +} + +apr_status_t SerfSession::provideSerfCredentials( bool bGiveProvidedCredentialsASecondTry, + char ** outUsername, + char ** outPassword, + serf_request_t * /*inRequest*/, + int /*inCode*/, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ) +{ + DAVAuthListener * pListener = getRequestEnvironment().m_xAuthListener.get(); + if ( !pListener ) + { + // abort + return SERF_ERROR_AUTHN_FAILED; + } + + OUString theUserName; + OUString thePassWord; + try + { + SerfUri uri( getRequestEnvironment().m_aRequestURI ); + OUString aUserInfo( uri.GetUserInfo() ); + if ( aUserInfo.getLength() ) + { + sal_Int32 nPos = aUserInfo.indexOf( '@' ); + if ( nPos == -1 ) + { + theUserName = aUserInfo; + } + else + { + theUserName = aUserInfo.copy( 0, nPos ); + thePassWord = aUserInfo.copy( nPos + 1 ); + } + } + } + catch ( DAVException const & ) + { + // abort + return SERF_ERROR_AUTHN_FAILED; + } + + const bool bCanUseSystemCreds = ( ( strcasecmp( inAuthProtocol, "NTLM" ) == 0 ) || + ( strcasecmp( inAuthProtocol, "Negotiate" ) == 0 ) ); + + int theRetVal = pListener->authenticate( OUString::createFromAscii( inRealm ), + getHostName(), + theUserName, + thePassWord, + bCanUseSystemCreds, + bGiveProvidedCredentialsASecondTry ); + + if ( theRetVal == 0 ) + { + *outUsername = apr_pstrdup( inAprPool, OUStringToOString( theUserName, RTL_TEXTENCODING_UTF8 ).getStr() ); + *outPassword = apr_pstrdup( inAprPool, OUStringToOString( thePassWord, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + + return theRetVal != 0 ? SERF_ERROR_AUTHN_FAILED : APR_SUCCESS; +} + +apr_status_t SerfSession::verifySerfCertificateChain ( + int, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + int nCertificateChainLength) +{ + // Check arguments. + if (pCertificateChainBase64Encoded == nullptr || nCertificateChainLength<=0) + { + assert(pCertificateChainBase64Encoded != nullptr); + assert(nCertificateChainLength>0); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // When called from SerfLockStore::~SerfLockStore(), + // css::xml::crypto::SEInitializer::create() will fail + // but we want to send unlock commands anyway, + // so just ignore certificates and return here. + if (apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->finishing()) + return APR_SUCCESS; + + // Create some crypto objects to decode and handle the base64 + // encoded certificate chain. + uno::Reference< security::XCertificateContainer > xCertificateContainer; + uno::Reference< xml::crypto::XXMLSecurityContext > xSecurityContext; + uno::Reference< xml::crypto::XSecurityEnvironment > xSecurityEnv; + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = + ::comphelper::getProcessComponentContext(); + // Create a certificate container. + xCertificateContainer = security::CertificateContainer::create( xContext ); + + css::uno::Reference< css::xml::crypto::XSEInitializer > xSEInitializer = + css::xml::crypto::SEInitializer::create( xContext ); + + xSecurityContext = xSEInitializer->createSecurityContext( OUString() ); + if (xSecurityContext.is()) + xSecurityEnv = xSecurityContext->getSecurityEnvironment(); + + if ( ! xSecurityContext.is() || ! xSecurityEnv.is()) + { + // Do we have to dispose xSEInitializer or xCertificateContainer? + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + catch ( uno::Exception const &) + { + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // Decode the server certificate. + const char* sBase64EncodedServerCertificate ( + serf_ssl_cert_export( + pCertificateChainBase64Encoded[0], + getAprPool())); + uno::Reference< security::XCertificate > xServerCertificate( + xSecurityEnv->createCertificateFromAscii( + OUString::createFromAscii(sBase64EncodedServerCertificate))); + if ( ! xServerCertificate.is()) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + + // Get the subject from the server certificate. + OUString sServerCertificateSubject (xServerCertificate->getSubjectName()); + sal_Int32 nIndex = 0; + while (nIndex >= 0) + { + const OUString sToken (sServerCertificateSubject.getToken(0, ',', nIndex)); + if (sToken.startsWith("CN=")) + { + sServerCertificateSubject = sToken.copy(3); + break; + } + else if (sToken.startsWith(" CN=")) + { + sServerCertificateSubject = sToken.copy(4); + break; + } + } + + // When the certificate container already contains a (trusted) + // entry for the server then we do not have to authenticate any + // certificate. + const security::CertificateContainerStatus eStatus ( + xCertificateContainer->hasCertificate( + getHostName(), sServerCertificateSubject ) ); + if (eStatus != security::CertificateContainerStatus_NOCERT) + { + return eStatus == security::CertificateContainerStatus_TRUSTED + ? APR_SUCCESS + : SERF_SSL_CERT_UNKNOWN_FAILURE; + } + + // The shortcut failed, so try to verify the whole chain. This is + // done outside the isDomainMatch() block because the result is + // used by the interaction handler. + std::vector< uno::Reference< security::XCertificate > > aChain; + for (nIndex = 1; nIndex < nCertificateChainLength; ++nIndex) + { + const char* sBase64EncodedCertificate ( + serf_ssl_cert_export( + pCertificateChainBase64Encoded[nIndex], + getAprPool())); + uno::Reference< security::XCertificate > xCertificate( + xSecurityEnv->createCertificateFromAscii( + OUString::createFromAscii(sBase64EncodedCertificate))); + if ( ! xCertificate.is()) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + aChain.push_back(xCertificate); + } + const sal_Int64 nVerificationResult (xSecurityEnv->verifyCertificate( + xServerCertificate, + ::comphelper::containerToSequence(aChain))); + + // When the certificate matches the host name then we can use the + // result of the verification. + bool bHostnameMatchesCertHostnames = false; + { + uno::Sequence< uno::Reference< security::XCertificateExtension > > extensions = xServerCertificate->getExtensions(); + uno::Sequence< security::CertAltNameEntry > altNames; + for (sal_Int32 i = 0 ; i < extensions.getLength(); ++i) + { + uno::Reference< security::XCertificateExtension >element = extensions[i]; + + const OString aId ( reinterpret_cast<const char *>(const_cast<const signed char *>(element->getExtensionId().getArray())), element->getExtensionId().getLength()); + if ( aId.equals( OID_SUBJECT_ALTERNATIVE_NAME ) ) + { + uno::Reference< security::XSanExtension > sanExtension ( element, uno::UNO_QUERY ); + altNames = sanExtension->getAlternativeNames(); + break; + } + } + + uno::Sequence< OUString > certHostNames(altNames.getLength() + 1); + certHostNames[0] = sServerCertificateSubject; + for( int n = 0; n < altNames.getLength(); ++n ) + { + if (altNames[n].Type == security::ExtAltNameType_DNS_NAME) + { + altNames[n].Value >>= certHostNames[n+1]; + } + } + + for ( int i = 0; i < certHostNames.getLength() && !bHostnameMatchesCertHostnames; ++i ) + { + bHostnameMatchesCertHostnames = isDomainMatch( certHostNames[i] ); + } + + } + if ( bHostnameMatchesCertHostnames ) + { + + if (nVerificationResult == 0) + { + // Certificate (chain) is valid. + xCertificateContainer->addCertificate(getHostName(), sServerCertificateSubject, true); + return APR_SUCCESS; + } + else if ((nVerificationResult & security::CertificateValidity::CHAIN_INCOMPLETE) != 0) + { + // We do not have enough information for verification, + // neither automatically (as we just discovered) nor + // manually (so there is no point in showing any dialog.) + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + else if ((nVerificationResult & + (security::CertificateValidity::INVALID | security::CertificateValidity::REVOKED)) != 0) + { + // Certificate (chain) is invalid. + xCertificateContainer->addCertificate(getHostName(), sServerCertificateSubject, false); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + else + { + // For all other we have to ask the user. + } + } + + // We have not been able to automatically verify (or falsify) the + // certificate chain. To resolve this we have to ask the user. + const uno::Reference< ucb::XCommandEnvironment > xEnv( getRequestEnvironment().m_xEnv ); + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH( xEnv->getInteractionHandler() ); + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::SimpleCertificateValidationRequest > + xRequest( new ucbhelper::SimpleCertificateValidationRequest( + static_cast<sal_Int32>(nVerificationResult), xServerCertificate, getHostName() ) ); + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + uno::Reference< task::XInteractionApprove > xApprove( xSelection.get(), uno::UNO_QUERY ); + if ( xApprove.is() ) + { + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, true ); + return APR_SUCCESS; + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, false ); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + } + else + { + // Don't trust cert + xCertificateContainer->addCertificate( getHostName(), sServerCertificateSubject, false ); + return SERF_SSL_CERT_UNKNOWN_FAILURE; + } + } + + return SERF_SSL_CERT_UNKNOWN_FAILURE; +} + +serf_bucket_t* SerfSession::acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* /*inAprPool*/ ) +{ + // get the per-request bucket allocator + serf_bucket_alloc_t* SerfBktAlloc = serf_request_get_alloc( inSerfRequest ); + + // create a barrier bucket so the response doesn't eat us! + serf_bucket_t *responseBkt = serf_bucket_barrier_create( inSerfStreamBucket, + SerfBktAlloc ); + + // create response bucket + responseBkt = serf_bucket_response_create( responseBkt, + SerfBktAlloc ); + + if ( isHeadRequestInProgress() ) + { + // advise the response bucket that this was from a HEAD request and that it should not expect to see a response body. + serf_bucket_response_set_head( responseBkt ); + } + + return responseBkt; +} + +SerfRequestProcessor* SerfSession::createReqProc( const OUString & inPath ) +{ + return new SerfRequestProcessor( *this, + inPath, + m_bUseChunkedEncoding ); +} + + +// PROPFIND - allprop & named + +void SerfSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropFind( inDepth, + inPropNames, + ioResources, + status ); + + if ( status == APR_SUCCESS && + aReqProc->mpDAVException == nullptr && + ioResources.empty() ) + { + m_aEnv = DAVRequestEnvironment(); + throw DAVException( DAVException::DAV_HTTP_ERROR, inPath, APR_EGENERAL ); + } + HandleError( aReqProc ); +} + + +// PROPFIND - propnames + +void SerfSession::PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo > & ioResInfo, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropFind( inDepth, + ioResInfo, + status ); + + if ( status == APR_SUCCESS && + aReqProc->mpDAVException == nullptr && + ioResInfo.empty() ) + { + m_aEnv = DAVRequestEnvironment(); + throw DAVException( DAVException::DAV_HTTP_ERROR, inPath, APR_EGENERAL ); + } + HandleError( aReqProc ); +} + + +// PROPPATCH + +void SerfSession::PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processPropPatch( inValues, + status ); + + HandleError( aReqProc ); +} + + +// HEAD + +void SerfSession::HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + m_bIsHeadRequestInProgress = true; + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processHead( inHeaderNames, + ioResource, + status ); + + m_bIsHeadRequestInProgress = false; + + HandleError( aReqProc ); +} + + +// GET + +uno::Reference< io::XInputStream > +SerfSession::GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processGet( xInputStream, + status ); + + HandleError( aReqProc ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// GET + +void SerfSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + apr_status_t status = APR_SUCCESS; + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processGet( ioOutputStream, + status ); + + HandleError( aReqProc ); +} + + +// GET + +uno::Reference< io::XInputStream > +SerfSession::GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processGet( xInputStream, + inHeaderNames, + ioResource, + status ); + + HandleError( aReqProc ); + + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// GET + +void SerfSession::GET( const OUString & inPath, + uno::Reference< io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + ioResource.uri = inPath; + ioResource.properties.clear(); + apr_status_t status = APR_SUCCESS; + aReqProc->processGet( ioOutputStream, + inHeaderNames, + ioResource, + status ); + + HandleError( aReqProc ); +} + + +// PUT + +void SerfSession::PUT( const OUString & inPath, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, false ) ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPut( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + status ); + + HandleError( aReqProc ); +} + + +// POST + +uno::Reference< io::XInputStream > +SerfSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + rtl::Reference< SerfInputStream > xInputStream( new SerfInputStream ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPost( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + rContentType, + rReferer, + xInputStream, + status ); + + HandleError( aReqProc ); + return uno::Reference< io::XInputStream >( xInputStream.get() ); +} + + +// POST + +void SerfSession::POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const uno::Reference< io::XInputStream > & inInputStream, + uno::Reference< io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + uno::Sequence< sal_Int8 > aDataToSend; + if ( !getDataFromInputStream( inInputStream, aDataToSend, true ) ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processPost( reinterpret_cast< const char * >( aDataToSend.getConstArray() ), + aDataToSend.getLength(), + rContentType, + rReferer, + oOutputStream, + status ); + + HandleError( aReqProc ); +} + + +// MKCOL + +void SerfSession::MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processMkCol( status ); + + HandleError( aReqProc ); +} + + +// COPY + +void SerfSession::COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + SerfUri theSourceUri( inSourceURL ); + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( theSourceUri.GetPath() ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processCopy( inDestinationURL, inOverWrite, status ); + + HandleError( aReqProc ); +} + + +// MOVE + +void SerfSession::MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + SerfUri theSourceUri( inSourceURL ); + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( theSourceUri.GetPath() ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processMove( inDestinationURL, inOverWrite, status ); + + HandleError( aReqProc ); +} + + +// DESTROY + +void SerfSession::DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + apr_status_t status = APR_SUCCESS; + aReqProc->processDelete( status ); + + HandleError( aReqProc ); +} + + +/* +namespace +{ + sal_Int32 lastChanceToSendRefreshRequest( TimeValue const & rStart, + int timeout ) + { + TimeValue aEnd; + osl_getSystemTime( &aEnd ); + + // Try to estimate a safe absolute time for sending the + // lock refresh request. + sal_Int32 lastChanceToSendRefreshRequest = -1; + if ( timeout != NE_TIMEOUT_INFINITE ) + { + sal_Int32 calltime = aEnd.Seconds - rStart.Seconds; + if ( calltime <= timeout ) + { + lastChanceToSendRefreshRequest + = aEnd.Seconds + timeout - calltime; + } + else + { + SAL_INFO("ucb.ucp.webdav", "No chance to refresh lock before timeout!" ); + } + } + return lastChanceToSendRefreshRequest; + } + +} // namespace +*/ + +// LOCK (set new lock) + +void SerfSession::LOCK( const OUString & inPath, + ucb::Lock & rLock, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processLock( rLock ); + + HandleError( aReqProc ); +} + + +// LOCK (refresh existing lock) + +sal_Int64 SerfSession::LOCK( const OUString & /*inPath*/, + sal_Int64 nTimeout, + const DAVRequestEnvironment & /*rEnv*/ ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + return nTimeout; + /* + // Try to get the neon lock from lock store + SerfLock * theLock + = m_aSerfLockStore.findByUri( makeAbsoluteURL( inPath ) ); + if ( !theLock ) + throw DAVException( DAVException::DAV_NOT_LOCKED ); + + Init( rEnv ); + + // refresh existing lock. + theLock->timeout = static_cast< long >( nTimeout ); + + TimeValue startCall; + osl_getSystemTime( &startCall ); + + int theRetVal = ne_lock_refresh( m_pHttpSession, theLock ); + + if ( theRetVal == NE_OK ) + { + m_aSerfLockStore.updateLock( theLock, + lastChanceToSendRefreshRequest( + startCall, theLock->timeout ) ); + } + + HandleError( theRetVal, inPath, rEnv ); + + return theLock->timeout; + */ +} + + +// LOCK (refresh existing lock) + +bool SerfSession::LOCK( const OUString& rLock, + sal_Int32 *plastChanceToSendRefreshRequest ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( rLock ) ); + aReqProc->processLock( ucb::Lock(), plastChanceToSendRefreshRequest ); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "Refreshing LOCK of " << rLock << " succeeded." ); + return true; + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "Refreshing LOCK of " << rLock << " failed!" ); + return false; + } +} + + +// UNLOCK + +void SerfSession::UNLOCK( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + Init( rEnv ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( inPath ) ); + aReqProc->processUnlock(); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << inPath << " succeeded." ); + apr_environment::AprEnv::getAprEnv()->getSerfLockStore()->removeLock( inPath ); + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << inPath << " failed!" ); + } +} + + +// UNLOCK + +void SerfSession::UNLOCK( const OUString& rLock ) +{ + osl::Guard< osl::Mutex > theGuard( m_aMutex ); + + std::shared_ptr<SerfRequestProcessor> aReqProc( createReqProc( rLock ) ); + aReqProc->processUnlock(); + + try + { + HandleError( aReqProc ); + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << rLock << " succeeded." ); + } + catch(...) + { + SAL_INFO("ucb.ucp.webdav", "UNLOCK of " << rLock << " failed!" ); + } +} + + +void SerfSession::abort() +{ + // 11.11.09 (tkr): The following code lines causing crashes if + // closing a ongoing connection. It turned out that this existing + // solution doesn't work in multi-threading environments. + // So I disabled them in 3.2. . Issue #73893# should fix it in OOo 3.3. + //if ( m_pHttpSession ) + // ne_close_connection( m_pHttpSession ); +} + + +ucbhelper::InternetProxyServer SerfSession::getProxySettings() const +{ + if ( m_aUri.GetScheme() == "http" || m_aUri.GetScheme() == "https" ) + { + return m_rProxyDecider.getProxy( m_aUri.GetScheme(), + m_aUri.GetHost(), + m_aUri.GetPort() ); + } + else + { + // TODO: figure out, if this case can occur + return m_rProxyDecider.getProxy( m_aUri.GetScheme(), + OUString() /* not used */, + -1 /* not used */ ); + } +} + +/* + +namespace { + +bool containsLocktoken( const uno::Sequence< ucb::Lock > & rLocks, + const char * token ) +{ + for ( sal_Int32 n = 0; n < rLocks.getLength(); ++n ) + { + const uno::Sequence< OUString > & rTokens + = rLocks[ n ].LockTokens; + for ( sal_Int32 m = 0; m < rTokens.getLength(); ++m ) + { + if ( rTokens[ m ].equalsAscii( token ) ) + return true; + } + } + return false; +} + +} // namespace +*/ + + +bool SerfSession::removeExpiredLocktoken( const OUString & /*inURL*/, + const DAVRequestEnvironment & /*rEnv*/ ) +{ + return true; + /* + SerfLock * theLock = m_aSerfLockStore.findByUri( inURL ); + if ( !theLock ) + return false; + + // do a lockdiscovery to check whether this lock is still valid. + try + { + // @@@ Alternative: use ne_lock_discover() => less overhead + + std::vector< DAVResource > aResources; + std::vector< OUString > aPropNames; + aPropNames.push_back( DAVProperties::LOCKDISCOVERY ); + + PROPFIND( rEnv.m_aRequestURI, DAVZERO, aPropNames, aResources, rEnv ); + + if ( aResources.empty() ) + return false; + + std::vector< DAVPropertyValue >::const_iterator it + = aResources[ 0 ].properties.begin(); + std::vector< DAVPropertyValue >::const_iterator end + = aResources[ 0 ].properties.end(); + + while ( it != end ) + { + if ( (*it).Name.equals( DAVProperties::LOCKDISCOVERY ) ) + { + uno::Sequence< ucb::Lock > aLocks; + if ( !( (*it).Value >>= aLocks ) ) + return false; + + if ( !containsLocktoken( aLocks, theLock->token ) ) + { + // expired! + break; + } + + // still valid. + return false; + } + ++it; + } + + // No lockdiscovery prop in propfind result / locktoken not found + // in propfind result -> not locked + SAL_INFO("ucb.ucp.webdav", "SerfSession::removeExpiredLocktoken: Removing " + " expired lock token for " << inURL << ". token: " << theLock->token ); + + m_aSerfLockStore.removeLock( theLock ); + ne_lock_destroy( theLock ); + return true; + } + catch ( DAVException const & ) + { + } + return false; + */ +} + + +// HandleError +// Common Error Handler + +void SerfSession::HandleError( std::shared_ptr<SerfRequestProcessor> rReqProc ) +{ + m_aEnv = DAVRequestEnvironment(); + + if ( rReqProc->mpDAVException ) + { + DAVException* mpDAVExp( rReqProc->mpDAVException ); + + serf_connection_reset( getSerfConnection() ); + + if ( mpDAVExp->getStatus() == 413 && + m_bNoOfTransferEncodingSwitches < 2 ) + { + m_bUseChunkedEncoding = !m_bUseChunkedEncoding; + ++m_bNoOfTransferEncodingSwitches; + } + + throw DAVException( mpDAVExp->getError(), + mpDAVExp->getData(), + mpDAVExp->getStatus() ); + } + + /* + // Map error code to DAVException. + switch ( nError ) + { + case NE_OK: + return; + + case NE_ERROR: // Generic error + { + OUString aText = OUString::createFromAscii( + ne_get_error( m_pHttpSession ) ); + + sal_uInt16 code = makeStatusCode( aText ); + + if ( code == SC_LOCKED ) + { + if ( m_aSerfLockStore.findByUri( + makeAbsoluteURL( inPath ) ) == 0 ) + { + // locked by 3rd party + throw DAVException( DAVException::DAV_LOCKED ); + } + else + { + // locked by ourself + throw DAVException( DAVException::DAV_LOCKED_SELF ); + } + } + + // Special handling for 400 and 412 status codes, which may indicate + // that a lock previously obtained by us has been released meanwhile + // by the server. Unfortunately, RFC is not clear at this point, + // thus server implementations behave different... + else if ( code == SC_BAD_REQUEST || code == SC_PRECONDITION_FAILED ) + { + if ( removeExpiredLocktoken( makeAbsoluteURL( inPath ), rEnv ) ) + throw DAVException( DAVException::DAV_LOCK_EXPIRED ); + } + + throw DAVException( DAVException::DAV_HTTP_ERROR, aText, code ); + } + case NE_LOOKUP: // Name lookup failed. + throw DAVException( DAVException::DAV_HTTP_LOOKUP, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_AUTH: // User authentication failed on server + throw DAVException( DAVException::DAV_HTTP_AUTH, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_PROXYAUTH: // User authentication failed on proxy + throw DAVException( DAVException::DAV_HTTP_AUTHPROXY, + SerfUri::makeConnectionEndPointString( + m_aProxyName, m_nProxyPort ) ); + + case NE_CONNECT: // Could not connect to server + throw DAVException( DAVException::DAV_HTTP_CONNECT, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_TIMEOUT: // Connection timed out + throw DAVException( DAVException::DAV_HTTP_TIMEOUT, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_FAILED: // The precondition failed + throw DAVException( DAVException::DAV_HTTP_FAILED, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_RETRY: // Retry request (ne_end_request ONLY) + throw DAVException( DAVException::DAV_HTTP_RETRY, + SerfUri::makeConnectionEndPointString( + m_aHostName, m_nPort ) ); + + case NE_REDIRECT: + { + SerfUri aUri( ne_redirect_location( m_pHttpSession ) ); + throw DAVException( + DAVException::DAV_HTTP_REDIRECT, aUri.GetURI() ); + } + default: + { + SAL_INFO("ucb.ucp.webdav", "SerfSession::HandleError : Unknown Serf error code!" ); + throw DAVException( DAVException::DAV_HTTP_ERROR, + OUString::createFromAscii( + ne_get_error( m_pHttpSession ) ) ); + } + } + */ +} + + +// static +bool +SerfSession::getDataFromInputStream( + const uno::Reference< io::XInputStream > & xStream, + uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ) +{ + if ( xStream.is() ) + { + uno::Reference< io::XSeekable > xSeekable( xStream, uno::UNO_QUERY ); + if ( xSeekable.is() ) + { + try + { + sal_Int32 nSize + = sal::static_int_cast<sal_Int32>(xSeekable->getLength()); + sal_Int32 nRead + = xStream->readBytes( rData, nSize ); + + if ( nRead == nSize ) + { + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nSize + 1 ); + rData[ nSize ] = sal_Int8( 0 ); + } + return true; + } + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // getLength, readBytes + } + } + else + { + try + { + uno::Sequence< sal_Int8 > aBuffer; + sal_Int32 nPos = 0; + + sal_Int32 nRead = xStream->readSomeBytes( aBuffer, 65536 ); + while ( nRead > 0 ) + { + if ( rData.getLength() < ( nPos + nRead ) ) + rData.realloc( nPos + nRead ); + + aBuffer.realloc( nRead ); + memcpy( rData.getArray() + nPos, aBuffer.getConstArray(), nRead ); + nPos += nRead; + + aBuffer.realloc( 0 ); + nRead = xStream->readSomeBytes( aBuffer, 65536 ); + } + + if ( bAppendTrailingZeroByte ) + { + rData.realloc( nPos + 1 ); + rData[ nPos ] = sal_Int8( 0 ); + } + return true; + } + catch ( io::NotConnectedException const & ) + { + // readBytes + } + catch ( io::BufferSizeExceededException const & ) + { + // readBytes + } + catch ( io::IOException const & ) + { + // readBytes + } + } + } + return false; +} + + +bool +SerfSession::isDomainMatch( const OUString & certHostName ) +{ + OUString hostName = getHostName(); + + if (hostName.equalsIgnoreAsciiCase( certHostName ) ) + return true; + + if ( certHostName.startsWith( "*" ) && + hostName.getLength() >= certHostName.getLength() ) + { + OUString cmpStr = certHostName.copy( 1 ); + + if ( hostName.matchIgnoreAsciiCase( + cmpStr, hostName.getLength() - cmpStr.getLength() ) ) + return true; + } + return false; +} + +/* + +OUString SerfSession::makeAbsoluteURL( OUString const & rURL ) const +{ + try + { + // Is URL relative or already absolute? + if ( rURL[ 0 ] != '/' ) + { + // absolute. + return OUString( rURL ); + } + else + { + ne_uri aUri; + memset( &aUri, 0, sizeof( aUri ) ); + + ne_fill_server_uri( m_pHttpSession, &aUri ); + aUri.path + = ne_strdup( OUStringToOString( + rURL, RTL_TEXTENCODING_UTF8 ).getStr() ); + SerfUri aSerfUri( &aUri ); + ne_uri_free( &aUri ); + return aSerfUri.GetURI(); + } + } + catch ( DAVException const & ) + { + } + // error. + return OUString(); +} +*/ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfSession.hxx b/ucb/source/ucp/webdav/SerfSession.hxx new file mode 100644 index 000000000..8c2a14439 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfSession.hxx @@ -0,0 +1,262 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX + +#include <osl/mutex.hxx> +#include <memory> +#include <vector> +#include "DAVSession.hxx" +#include "SerfUri.hxx" + +#include <serf.h> + +namespace ucbhelper { class ProxyDecider; } + +namespace http_dav_ucp +{ + +class SerfRequestProcessor; + + +// SerfSession +// A DAVSession implementation using the neon/expat library + + +class SerfSession : public DAVSession +{ +private: + osl::Mutex m_aMutex; + + SerfUri m_aUri; + + OUString m_aProxyName; + sal_Int32 m_nProxyPort; + + serf_connection_t* m_pSerfConnection; + serf_context_t* m_pSerfContext; + serf_bucket_alloc_t* m_pSerfBucket_Alloc; + bool m_bIsHeadRequestInProgress; + bool m_bUseChunkedEncoding; + sal_Int16 m_bNoOfTransferEncodingSwitches; + + const ucbhelper::InternetProxyDecider & m_rProxyDecider; + + DAVRequestEnvironment m_aEnv; + + char* getHostinfo(); + bool isSSLNeeded(); + + SerfRequestProcessor* createReqProc( const OUString & inPath ); + +protected: + virtual ~SerfSession() override; + +public: + /// @throws DAVException + SerfSession( const rtl::Reference< DAVSessionFactory > & rSessionFactory, + const OUString& inUri, + const ucbhelper::InternetProxyDecider & rProxyDecider ); + + // Serf library callbacks + apr_status_t setupSerfConnection( apr_socket_t * inAprSocket, + serf_bucket_t **outSerfInputBucket, + serf_bucket_t **outSerfOutputBucket, + apr_pool_t* inAprPool ); + + apr_status_t provideSerfCredentials( bool bGiveProvidedCredentialsASecondTry, + char ** outUsername, + char ** outPassword, + serf_request_t * inRequest, + int inCode, + const char *inAuthProtocol, + const char *inRealm, + apr_pool_t *inAprPool ); + + apr_status_t verifySerfCertificateChain ( + int nFailures, + const serf_ssl_certificate_t * const * pCertificateChainBase64Encoded, + int nCertificateChainLength); + + serf_bucket_t* acceptSerfResponse( serf_request_t * inSerfRequest, + serf_bucket_t * inSerfStreamBucket, + apr_pool_t* inAprPool ); + + // Serf-related data structures + static apr_pool_t* getAprPool(); + serf_bucket_alloc_t* getSerfBktAlloc(); + serf_context_t* getSerfContext(); + serf_connection_t* getSerfConnection(); + + // DAVSession methods + virtual bool CanUse( const OUString & inUri ) override; + + virtual bool UsesProxy() override; + + const DAVRequestEnvironment & getRequestEnvironment() const + { return m_aEnv; } + + // allprop & named + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + const std::vector< OUString > & inPropNames, + std::vector< DAVResource > & ioResources, + const DAVRequestEnvironment & rEnv ) override; + + // propnames + virtual void + PROPFIND( const OUString & inPath, + const Depth inDepth, + std::vector< DAVResourceInfo >& ioResInfo, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PROPPATCH( const OUString & inPath, + const std::vector< ProppatchValue > & inValues, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + HEAD( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + bool isHeadRequestInProgress(); + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + GET( const OUString & inPath, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + GET( const OUString & inPath, + css::uno::Reference< css::io::XOutputStream > & ioOutputStream, + const std::vector< OUString > & inHeaderNames, + DAVResource & ioResource, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + PUT( const OUString & inPath, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual css::uno::Reference< css::io::XInputStream > + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + POST( const OUString & inPath, + const OUString & rContentType, + const OUString & rReferer, + const css::uno::Reference< css::io::XInputStream > & inInputStream, + css::uno::Reference< css::io::XOutputStream > & oOutputStream, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + MKCOL( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + virtual void + COPY( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite = false ) override; + + virtual void + MOVE( const OUString & inSourceURL, + const OUString & inDestinationURL, + const DAVRequestEnvironment & rEnv, + bool inOverWrite = false ) override; + + virtual void DESTROY( const OUString & inPath, + const DAVRequestEnvironment & rEnv ) override; + + // set new lock. + virtual void LOCK( const OUString & inURL, + css::ucb::Lock & inLock, + const DAVRequestEnvironment & rEnv ) override; + + // refresh existing lock. + virtual sal_Int64 LOCK( const OUString & inURL, + sal_Int64 nTimeout, + const DAVRequestEnvironment & rEnv ) override; + + virtual void UNLOCK( const OUString & inURL, + const DAVRequestEnvironment & rEnv ) override; + + // helpers + virtual void abort() override; + + const OUString & getHostName() const { return m_aUri.GetHost(); } + int getPort() const { return m_aUri.GetPort(); } + + bool isDomainMatch( const OUString & certHostName ); + +private: + friend class SerfLockStore; + + /// @throws DAVException + void Init(); + + /// @throws DAVException + void Init( const DAVRequestEnvironment & rEnv ); + + /// @throws DAVException + void HandleError( std::shared_ptr<SerfRequestProcessor> rReqProc ); + + ucbhelper::InternetProxyServer getProxySettings() const; + + static bool removeExpiredLocktoken( const OUString & inURL, + const DAVRequestEnvironment & rEnv ); + + // refresh lock, called by SerfLockStore::refreshLocks + bool LOCK( const OUString& rLock, sal_Int32 *plastChanceToSendRefreshRequest ); + + // unlock, called by SerfLockStore::~SerfLockStore + void UNLOCK( const OUString& rLock ); + + // Helper: XInputStream -> Sequence< sal_Int8 > + static bool getDataFromInputStream( + const css::uno::Reference< + css::io::XInputStream > & xStream, + css::uno::Sequence< sal_Int8 > & rData, + bool bAppendTrailingZeroByte ); +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFSESSION_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx new file mode 100644 index 000000000..53826ace6 --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.cxx @@ -0,0 +1,68 @@ +/* -*- 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 "SerfUnlockReqProcImpl.hxx" + +namespace http_dav_ucp +{ + +SerfUnlockReqProcImpl::SerfUnlockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const OUString& sToken) + : SerfRequestProcessorImpl( inPath, inRequestHeaders ) + , m_sToken( sToken ) +{ +} + +SerfUnlockReqProcImpl::~SerfUnlockReqProcImpl() +{ +} + +serf_bucket_t * SerfUnlockReqProcImpl::createSerfRequestBucket( serf_request_t * inSerfRequest ) +{ + // create serf request + serf_bucket_t *req_bkt = serf_request_bucket_request_create( inSerfRequest, + "UNLOCK", + getPathStr(), + nullptr, + serf_request_get_alloc( inSerfRequest ) ); + // set request header fields + serf_bucket_t* hdrs_bkt = serf_bucket_request_get_headers( req_bkt ); + + // general header fields provided by caller + setRequestHeaders( hdrs_bkt ); + + // token header field + serf_bucket_headers_set( hdrs_bkt, "Lock-Token", + OUStringToOString(m_sToken, RTL_TEXTENCODING_UTF8).getStr() ); + + return req_bkt; +} + +void SerfUnlockReqProcImpl::processChunkOfResponseData( const char* , apr_size_t ) +{ +} + +void SerfUnlockReqProcImpl::handleEndOfResponseData( serf_bucket_t * ) +{ +} + +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.hxx b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.hxx new file mode 100644 index 000000000..afef3f28b --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUnlockReqProcImpl.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX + +#include "SerfRequestProcessorImpl.hxx" + +namespace http_dav_ucp +{ + +class SerfUnlockReqProcImpl : public SerfRequestProcessorImpl +{ +public: + SerfUnlockReqProcImpl( const char* inPath, + const DAVRequestHeaders& inRequestHeaders, + const OUString& sToken); + + virtual ~SerfUnlockReqProcImpl() override; + + virtual serf_bucket_t *createSerfRequestBucket( + serf_request_t * inSerfRequest ) override; + +private: + virtual void processChunkOfResponseData( + const char* data, apr_size_t len ) override; + + virtual void handleEndOfResponseData( + serf_bucket_t * inSerfResponseBucket ) override; + + OUString m_sToken; +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFUNLOCKREQPROCIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUri.cxx b/ucb/source/ucp/webdav/SerfUri.cxx new file mode 100644 index 000000000..dab11a64c --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUri.cxx @@ -0,0 +1,248 @@ +/* -*- 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 <rtl/uri.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include "SerfUri.hxx" +#include "DAVException.hxx" +#include "AprEnv.hxx" + +#include <urihelper.hxx> + +using namespace http_dav_ucp; + + +SerfUri::SerfUri( const apr_uri_t * inUri ) + : mAprUri( *inUri ) + , mURI() + , mScheme() + , mUserInfo() + , mHostName() + , mPort() + , mPath() +{ + if ( inUri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + char * uri = apr_uri_unparse( apr_environment::AprEnv::getAprEnv()->getAprPool(), &mAprUri, 0 ); + + if ( uri == nullptr ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + init( &mAprUri ); + + calculateURI(); +} + +SerfUri::SerfUri( const OUString & inUri ) + : mAprUri() + , mURI() + , mScheme() + , mUserInfo() + , mHostName() + , mPort() + , mPath() +{ + if ( inUri.getLength() <= 0 ) + throw DAVException( DAVException::DAV_INVALID_ARG ); + + // #i77023# + OUString aEscapedUri( ucb_impl::urihelper::encodeURI( inUri ) ); + + OString theInputUri( + aEscapedUri.getStr(), aEscapedUri.getLength(), RTL_TEXTENCODING_UTF8 ); + + if ( apr_uri_parse( apr_environment::AprEnv::getAprEnv()->getAprPool(), + theInputUri.getStr(), &mAprUri ) != APR_SUCCESS ) + { + throw DAVException( DAVException::DAV_INVALID_ARG ); + } + if ( !mAprUri.port ) + { + mAprUri.port = apr_uri_port_of_scheme( mAprUri.scheme ); + } + if ( !mAprUri.path ) + { + mAprUri.path = const_cast<char *>("/"); + } + + init( &mAprUri ); + + calculateURI(); +} + +void SerfUri::init( const apr_uri_t * pUri ) +{ + mScheme = OStringToOUString( pUri->scheme, RTL_TEXTENCODING_UTF8 ); + mUserInfo = OStringToOUString( pUri->user, RTL_TEXTENCODING_UTF8 ); + mHostName = OStringToOUString( pUri->hostname, RTL_TEXTENCODING_UTF8 ); + mPort = pUri->port; + mPath = OStringToOUString( pUri->path, RTL_TEXTENCODING_UTF8 ); + + if ( pUri->query ) + { + mPath += "?"; + mPath += OStringToOUString( pUri->query, RTL_TEXTENCODING_UTF8 ); + } + + if ( pUri->fragment ) + { + mPath += "#"; + mPath += OStringToOUString( pUri->fragment, RTL_TEXTENCODING_UTF8 ); + } +} + +void SerfUri::calculateURI () +{ + OUStringBuffer aBuf( mScheme ); + aBuf.append( "://" ); + if ( mUserInfo.getLength() > 0 ) + { + aBuf.append( mUserInfo ); + aBuf.append( "@" ); + } + // Is host a numeric IPv6 address? + if ( ( mHostName.indexOf( ':' ) != -1 ) && + ( mHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( mHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( mHostName ); + } + + // append port, but only, if not default port. + bool bAppendPort = true; + switch ( mPort ) + { + case DEFAULT_HTTP_PORT: + bAppendPort = (mScheme != "http"); + break; + + case DEFAULT_HTTPS_PORT: + bAppendPort = (mScheme != "https"); + break; + } + if ( bAppendPort ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( mPort ) ); + } + aBuf.append( mPath ); + + mURI = aBuf.makeStringAndClear(); +} + +OUString SerfUri::GetPathBaseName () const +{ + sal_Int32 nPos = mPath.lastIndexOf ('/'); + sal_Int32 nTrail = 0; + if (nPos == mPath.getLength () - 1) + { + // Trailing slash found. Skip. + nTrail = 1; + nPos = mPath.lastIndexOf ('/', nPos); + } + if (nPos != -1) + { + OUString aTemp( + mPath.copy (nPos + 1, mPath.getLength () - nPos - 1 - nTrail) ); + + // query, fragment present? + nPos = aTemp.indexOf( '?' ); + if ( nPos == -1 ) + nPos = aTemp.indexOf( '#' ); + + if ( nPos != -1 ) + aTemp = aTemp.copy( 0, nPos ); + + return aTemp; + } + else + return "/"; +} + +bool SerfUri::operator== ( const SerfUri & rOther ) const +{ + return ( mURI == rOther.mURI ); +} + +OUString SerfUri::GetPathBaseNameUnescaped () const +{ + return unescape( GetPathBaseName() ); +} + +void SerfUri::AppendPath (const OUString& rPath) +{ + if (mPath.lastIndexOf ('/') != mPath.getLength () - 1) + mPath += "/"; + + mPath += rPath; + calculateURI (); +}; + +// static +OUString SerfUri::escapeSegment( const OUString& segment ) +{ + return rtl::Uri::encode( segment, + rtl_UriCharClassPchar, + rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString SerfUri::unescape( const OUString& segment ) +{ + return rtl::Uri::decode( segment, + rtl_UriDecodeWithCharset, + RTL_TEXTENCODING_UTF8 ); +} + +// static +OUString SerfUri::makeConnectionEndPointString( + const OUString & rHostName, int nPort ) +{ + OUStringBuffer aBuf; + + // Is host a numeric IPv6 address? + if ( ( rHostName.indexOf( ':' ) != -1 ) && + ( rHostName[ 0 ] != '[' ) ) + { + aBuf.append( "[" ); + aBuf.append( rHostName ); + aBuf.append( "]" ); + } + else + { + aBuf.append( rHostName ); + } + + if ( ( nPort != DEFAULT_HTTP_PORT ) && ( nPort != DEFAULT_HTTPS_PORT ) ) + { + aBuf.append( ":" ); + aBuf.append( OUString::number( sal_Int32( nPort ) ) ); + } + return aBuf.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/SerfUri.hxx b/ucb/source/ucp/webdav/SerfUri.hxx new file mode 100644 index 000000000..f47b2892e --- /dev/null +++ b/ucb/source/ucp/webdav/SerfUri.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX + +#include <apr_uri.h> +#include <rtl/ustring.hxx> +#include "DAVException.hxx" + +namespace http_dav_ucp +{ + +#define DEFAULT_HTTP_PORT 80 +#define DEFAULT_HTTPS_PORT 443 + + +// SerfUri +// A URI implementation for use with the neon/expat library + +class SerfUri +{ + private: + apr_uri_t mAprUri; + OUString mURI; + OUString mScheme; + OUString mUserInfo; + OUString mHostName; + sal_Int32 mPort; + OUString mPath; + + void init( const apr_uri_t * pUri ); + void calculateURI (); + + public: + /// @throws DAVException + explicit SerfUri( const OUString & inUri ); + /// @throws DAVException + explicit SerfUri( const apr_uri_t * inUri ); + + bool operator== ( const SerfUri & rOther ) const; + bool operator!= ( const SerfUri & rOther ) const + { return !operator==( rOther ); } + + apr_uri_t& getAprUri() + { + return mAprUri; + } + const OUString & GetURI() const + { return mURI; }; + const OUString & GetScheme() const + { return mScheme; }; + const OUString & GetUserInfo() const + { return mUserInfo; }; + const OUString & GetHost() const + { return mHostName; }; + sal_Int32 GetPort() const + { return mPort; }; + const OUString & GetPath() const + { return mPath; }; + + OUString GetPathBaseName() const; + + OUString GetPathBaseNameUnescaped() const; + + void SetScheme (const OUString& scheme) + { mScheme = scheme; calculateURI (); }; + + void AppendPath (const OUString& rPath); + + static OUString escapeSegment( const OUString& segment ); + static OUString unescape( const OUString& string ); + + // "host:port", omit ":port" for port 80 and 443 + static OUString makeConnectionEndPointString( + const OUString & rHostName, + int nPort ); + OUString makeConnectionEndPointString() const + { return makeConnectionEndPointString( GetHost(), GetPort() ); } +}; + +} // namespace http_dav_ucp + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_SERFURI_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx b/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx new file mode 100644 index 000000000..0f3543012 --- /dev/null +++ b/ucb/source/ucp/webdav/UCBDeadPropertyValue.cxx @@ -0,0 +1,526 @@ +/* -*- 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 <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include "UCBDeadPropertyValue.hxx" + +using namespace http_dav_ucp; +using namespace ::com::sun::star; + + +// static +const OUString UCBDeadPropertyValue::aTypeString + = "string"; +const OUString UCBDeadPropertyValue::aTypeLong + = "long"; +const OUString UCBDeadPropertyValue::aTypeShort + = "short"; +const OUString UCBDeadPropertyValue::aTypeBoolean + = "boolean"; +const OUString UCBDeadPropertyValue::aTypeChar + = "char"; +const OUString UCBDeadPropertyValue::aTypeByte + = "byte"; +const OUString UCBDeadPropertyValue::aTypeHyper + = "hyper"; +const OUString UCBDeadPropertyValue::aTypeFloat + = "float"; +const OUString UCBDeadPropertyValue::aTypeDouble + = "double"; + +// static +const OUString UCBDeadPropertyValue::aXMLPre + = "<ucbprop><type>"; +const OUString UCBDeadPropertyValue::aXMLMid + = "</type><value>"; +const OUString UCBDeadPropertyValue::aXMLEnd + = "</value></ucbprop>"; + +/* + +#define STATE_TOP (1) + +#define STATE_UCBPROP (STATE_TOP) +#define STATE_TYPE (STATE_TOP + 1) +#define STATE_VALUE (STATE_TOP + 2) + +extern "C" int UCBDeadPropertyValue_startelement_callback( + void *, + int parent, + const char * nspace, + const char *name, + const char ** ) +{ + if ( name != 0 ) + { + switch ( parent ) + { + case NE_XML_STATEROOT: + if ( strcmp( name, "ucbprop" ) == 0 ) + return STATE_UCBPROP; + break; + + case STATE_UCBPROP: + if ( strcmp( name, "type" ) == 0 ) + return STATE_TYPE; + else if ( strcmp( name, "value" ) == 0 ) + return STATE_VALUE; + break; + } + } + return NE_XML_DECLINE; +} + + +extern "C" int UCBDeadPropertyValue_chardata_callback( + void *userdata, + int state, + const char *buf, + size_t len ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + SAL_WARN_IF( pCtx->pType, "ucb.ucp.webdav", + "UCBDeadPropertyValue_endelement_callback - " + "Type already set!" ); + pCtx->pType + = new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + break; + + case STATE_VALUE: + SAL_WARN_IF( pCtx->pValue, "ucb.ucp.webdav", + "UCBDeadPropertyValue_endelement_callback - " + "Value already set!" ); + pCtx->pValue + = new OUString( buf, len, RTL_TEXTENCODING_ASCII_US ); + break; + } + return 0; // zero to continue, non-zero to abort parsing +} + + +extern "C" int UCBDeadPropertyValue_endelement_callback( + void *userdata, + int state, + const char *, + const char * ) +{ + UCBDeadPropertyValueParseContext * pCtx + = static_cast< UCBDeadPropertyValueParseContext * >( userdata ); + + switch ( state ) + { + case STATE_TYPE: + if ( !pCtx->pType ) + return 1; // abort + break; + + case STATE_VALUE: + if ( !pCtx->pValue ) + return 1; // abort + break; + + case STATE_UCBPROP: + if ( !pCtx->pType || ! pCtx->pValue ) + return 1; // abort + break; + } + return 0; // zero to continue, non-zero to abort parsing +} +*/ + + +static OUString encodeValue( const OUString & rValue ) +{ + // Note: I do not use the usual & + < + > encoding, because + // I want to prevent any XML parser from trying to 'understand' + // the value. This caused problems: + + // Example: + // - Unencoded property value: x<z + // PROPPATCH: + // - Encoded property value: x<z + // - UCBDeadPropertyValue::toXML result: + // <ucbprop><type>string</type><value>x<z</value></ucbprop> + // PROPFIND: + // - parser replaces < by > ==> error (not well formed) + + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nCount = rValue.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const sal_Unicode c = pValue[ n ]; + + if ( '%' == c ) + aResult.append( "%per;" ); + else if ( '<' == c ) + aResult.append( "%lt;" ); + else if ( '>' == c ) + aResult.append( "%gt;" ); + else + aResult.append( c ); + } + return aResult.makeStringAndClear(); +} + +/* + +static OUString decodeValue( const OUString & rValue ) +{ + OUStringBuffer aResult; + const sal_Unicode * pValue = rValue.getStr(); + + sal_Int32 nPos = 0; + sal_Int32 nEnd = rValue.getLength(); + + while ( nPos < nEnd ) + { + sal_Unicode c = pValue[ nPos ]; + + if ( '%' == c ) + { + nPos++; + + if ( nPos == nEnd ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + c = pValue[ nPos ]; + + if ( 'p' == c ) + { + // %per; + + if ( nPos > nEnd - 4 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 'e' == pValue[ nPos + 1 ] ) + && + ( 'r' == pValue[ nPos + 2 ] ) + && + ( ';' == pValue[ nPos + 3 ] ) ) + { + aResult.append( '%' ); + nPos += 3; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else if ( 'l' == c ) + { + // %lt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '<' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else if ( 'g' == c ) + { + // %gt; + + if ( nPos > nEnd - 3 ) + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + + if ( ( 't' == pValue[ nPos + 1 ] ) + && + ( ';' == pValue[ nPos + 2 ] ) ) + { + aResult.append( '>' ); + nPos += 2; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::decodeValue - syntax error!" ); + return OUString(); + } + } + else + aResult.append( c ); + + nPos++; + } + + return OUString( aResult ); +} +*/ + + +// static +bool UCBDeadPropertyValue::supportsType( const uno::Type & rType ) +{ + if ( ( rType != cppu::UnoType<OUString>::get() ) + && + ( rType != cppu::UnoType<sal_Int32>::get() ) + && + ( rType != cppu::UnoType<sal_Int16>::get() ) + && + ( rType != cppu::UnoType<bool>::get() ) + && + ( rType != cppu::UnoType<cppu::UnoCharType>::get() ) + && + ( rType != cppu::UnoType<sal_Int8>::get() ) + && + ( rType != cppu::UnoType<sal_Int64>::get() ) + && + ( rType != cppu::UnoType<float>::get() ) + && + ( rType != cppu::UnoType<double>::get() ) ) + { + return false; + } + + return true; +} + + +// static +bool UCBDeadPropertyValue::createFromXML( const OString & /*rInData*/, + uno::Any & /*rOutData*/ ) +{ + bool success = false; + + /* + ne_xml_parser * parser = ne_xml_create(); + if ( parser ) + { + UCBDeadPropertyValueParseContext aCtx; + ne_xml_push_handler( parser, + UCBDeadPropertyValue_startelement_callback, + UCBDeadPropertyValue_chardata_callback, + UCBDeadPropertyValue_endelement_callback, + &aCtx ); + + ne_xml_parse( parser, rInData.getStr(), rInData.getLength() ); + + success = !ne_xml_failed( parser ); + + ne_xml_destroy( parser ); + + if ( success ) + { + if ( aCtx.pType && aCtx.pValue ) + { + // Decode aCtx.pValue! It may contain XML reserved chars. + OUString aStringValue = decodeValue( *aCtx.pValue ); + if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeString ) ) + { + rOutData <<= aStringValue; + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeLong ) ) + { + rOutData <<= aStringValue.toInt32(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeShort ) ) + { + rOutData <<= sal_Int16( aStringValue.toInt32() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeBoolean ) ) + { + if ( aStringValue.equalsIgnoreAsciiCase( + OUString( "true" ) ) ) + rOutData <<= sal_Bool( sal_True ); + else + rOutData <<= sal_Bool( sal_False ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeChar ) ) + { + rOutData <<= aStringValue.toChar(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeByte ) ) + { + rOutData <<= sal_Int8( aStringValue.toChar() ); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeHyper ) ) + { + rOutData <<= aStringValue.toInt64(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeFloat ) ) + { + rOutData <<= aStringValue.toFloat(); + } + else if ( aCtx.pType->equalsIgnoreAsciiCase( aTypeDouble ) ) + { + rOutData <<= aStringValue.toDouble(); + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::createFromXML - " + "Unsupported property type!" ); + success = false; + } + } + else + success = false; + } + } + */ + return success; +} + + +// static +bool UCBDeadPropertyValue::toXML( const uno::Any & rInData, + OUString & rOutData ) +{ + // <ucbprop><type>the_type</type><value>the_value</value></ucbprop> + + // Check property type. Extract type and value as string. + + const uno::Type& rType = rInData.getValueType(); + OUString aStringValue; + OUString aStringType; + + if ( rType == cppu::UnoType<OUString>::get() ) + { + // string + rInData >>= aStringValue; + aStringType = aTypeString; + } + else if ( rType == cppu::UnoType<sal_Int32>::get() ) + { + // long + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeLong; + } + else if ( rType == cppu::UnoType<sal_Int16>::get() ) + { + // short + sal_Int32 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeShort; + } + else if ( rType == cppu::UnoType<bool>::get() ) + { + // boolean + bool bValue = false; + rInData >>= bValue; + aStringValue = OUString::boolean( bValue ); + aStringType = aTypeBoolean; + } + else if ( rType == cppu::UnoType<cppu::UnoCharType>::get() ) + { + // char + sal_Unicode cValue = 0; + rInData >>= cValue; + aStringValue = OUString( cValue ); + aStringType = aTypeChar; + } + else if ( rType == cppu::UnoType<sal_Int8>::get() ) + { + // byte + sal_Int8 nValue = 0; + rInData >>= nValue; + aStringValue = OUString( sal_Unicode( nValue ) ); + aStringType = aTypeByte; + } + else if ( rType == cppu::UnoType<sal_Int64>::get() ) + { + // hyper + sal_Int64 nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeHyper; + } + else if ( rType == cppu::UnoType<float>::get() ) + { + // float + float nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeFloat; + } + else if ( rType == cppu::UnoType<double>::get() ) + { + // double + double nValue = 0; + rInData >>= nValue; + aStringValue = OUString::number( nValue ); + aStringType = aTypeDouble; + } + else + { + SAL_WARN( "ucb.ucp.webdav", + "UCBDeadPropertyValue::toXML - " + "Unsupported property type!" ); + return false; + } + + // Encode value! It must not contain XML reserved chars! + aStringValue = encodeValue( aStringValue ); + + rOutData = aXMLPre; + rOutData += aStringType; + rOutData += aXMLMid; + rOutData += aStringValue; + rOutData += aXMLEnd; + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/UCBDeadPropertyValue.hxx b/ucb/source/ucp/webdav/UCBDeadPropertyValue.hxx new file mode 100644 index 000000000..dc066f48a --- /dev/null +++ b/ucb/source/ucp/webdav/UCBDeadPropertyValue.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX + +#include <rtl/string.hxx> +#include <com/sun/star/uno/Any.hxx> + +namespace http_dav_ucp +{ + +class UCBDeadPropertyValue +{ +private: + static const OUString aTypeString; + static const OUString aTypeLong; + static const OUString aTypeShort; + static const OUString aTypeBoolean; + static const OUString aTypeChar; + static const OUString aTypeByte; + static const OUString aTypeHyper; + static const OUString aTypeFloat; + static const OUString aTypeDouble; + + static const OUString aXMLPre; + static const OUString aXMLMid; + static const OUString aXMLEnd; + +public: + static bool supportsType( const css::uno::Type & rType ); + + static bool createFromXML( const OString & rInData, + css::uno::Any & rOutData ); + static bool toXML( const css::uno::Any & rInData, + OUString & rOutData ); +}; + +} + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_UCBDEADPROPERTYVALUE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/ucpdav1.component b/ucb/source/ucp/webdav/ucpdav1.component new file mode 100644 index 000000000..50a3d87b2 --- /dev/null +++ b/ucb/source/ucp/webdav/ucpdav1.component @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + + + +<component loader="com.sun.star.loader.SharedLibrary" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.WebDAVContentProvider"> + <service name="com.sun.star.ucb.WebDAVContentProvider"/> + </implementation> +</component> diff --git a/ucb/source/ucp/webdav/webdavcontent.cxx b/ucb/source/ucp/webdav/webdavcontent.cxx new file mode 100644 index 000000000..6c0198199 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontent.cxx @@ -0,0 +1,3292 @@ +/* -*- 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 <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/macros.hxx> +#include <ucbhelper/propertyvalueset.hxx> +#include <ucbhelper/simpleinteractionrequest.hxx> +#include <ucbhelper/cancelcommandexecution.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/OpenCommandArgument2.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 "SerfUri.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.push_back( + DAVRequestHeader( + OUString( "Range" ), + OUString( "bytes=0-0" ))); + + 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.push_back( OUString( "Accept-Ranges" ) ); + aHeaderNames.push_back( OUString( "Content-Range" ) ); + } + try + { + uno::Reference< io::XInputStream > xIn = xResAccess->GET( 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.get() ) + xProps->addProperties( + rProps, + ContentProperties( aResource ) ); + else + xProps.reset ( new ContentProperties( aResource ) ); + } + catch ( DAVException const & ex ) + { + aLastException = ex; + } +} +} + + +// 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_pProvider( pProvider ), + m_bTransient( false ), + m_bLocked( false ), + m_bCollection( false ), + m_bDidGetOrHead( false ) +{ + try + { + m_xResAccess.reset( new DAVResourceAccess( + rxContext, + rSessionFactory, + Identifier->getContentIdentifier() ) ); + + SerfUri 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_pProvider( pProvider ), + m_bTransient( true ), + m_bLocked( false ), + m_bCollection( isCollection ), + m_bDidGetOrHead( false ) +{ + try + { + 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() +{ + if (m_bLocked) + unlock(uno::Reference< ucb::XCommandEnvironment >()); +} + + +// XInterface methods. + + +// virtual +void SAL_CALL Content::acquire() + throw( ) +{ + ContentImplHelper::acquire(); +} + + +// virtual +void SAL_CALL Content::release() + throw( ) +{ + 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< beans::XPropertySet > const xProps( + m_xContext, uno::UNO_QUERY_THROW ); + uno::Reference< uno::XComponentContext > xCtx; + xCtx.set( xProps->getPropertyValue( "DefaultContext" ), + uno::UNO_QUERY_THROW ); + + uno::Reference< task::XInteractionHandler > xIH( + task::PasswordContainerInteractionHandler::create( xCtx ) ); + + // 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( + xCtx, + 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 "com.sun.star.comp.ucb.WebDAVContent"; +} + + +// 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.Name + << ", 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + if ( !aProperties.getLength() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "No properties!", + static_cast< cppu::OWeakObject * >( this ), + -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::OpenCommandArgument2 aOpenCommand; + if ( !( aCommand.Argument >>= aOpenCommand ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet = open( aOpenCommand, Environment ); + + if ( (aOpenCommand.Mode == ucb::OpenMode::DOCUMENT || + aOpenCommand.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE) && + supportsExclusiveWriteLock( Environment ) ) + lock( Environment ); + } + else if ( aCommand.Name == "insert" ) + { + + // insert + + + ucb::InsertCommandArgument arg; + if ( !( aCommand.Argument >>= arg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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 ) ); + } + 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::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + transfer( transferArgs, Environment ); + } + else if ( aCommand.Name == "post" ) + { + + // post + + + ucb::PostCommandArgument2 aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + post( aArg, Environment ); + } + else if ( aCommand.Name == "lock" && + supportsExclusiveWriteLock( Environment ) ) + { + + // lock + + + lock( Environment ); + } + else if ( aCommand.Name == "unlock" && + supportsExclusiveWriteLock( Environment ) ) + { + + // unlock + + + unlock( Environment ); + } + else if ( aCommand.Name == "createNewContent" && + isFolder( Environment ) ) + { + + // createNewContent + + + ucb::ContentInfo aArg; + if ( !( aCommand.Argument >>= aArg ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + Environment ); + // Unreachable + } + + aRet <<= createNewContent( aArg ); + } + else if ( aCommand.Name == "addProperty" ) + { + ucb::PropertyCommandArgument aPropArg; + if ( !( aCommand.Argument >>= aPropArg )) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch ( const beans::IllegalTypeException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + catch ( const lang::IllegalArgumentException&e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else if ( aCommand.Name == "removeProperty" ) + { + OUString sPropName; + if ( !( aCommand.Argument >>= sPropName ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( lang::IllegalArgumentException( + "Wrong argument type!", + static_cast< cppu::OWeakObject * >( this ), + -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::makeAny( e ), Environment ); + } + catch( const beans::NotRemoveableException &e ) + { + ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment ); + } + } + else + { + + // Unsupported command + + + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::UnsupportedCommandException( + aCommand.Name, + static_cast< cppu::OWeakObject * >( this ) ) ), + 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 ) ); + } + DAVResourceAccess::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( + "\"addProperty\" with empty Property.Name", + static_cast< ::cppu::OWeakObject * >( this ), + -1 ); + + // Check property type. + if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) ) + throw beans::IllegalTypeException( + "\"addProperty\" unsupported Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + + // check default value + if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type ) + throw beans::IllegalTypeException( + "\"addProperty\" DefaultValue does not match Property.Type", + static_cast< ::cppu::OWeakObject * >( this ) ); + + + // 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 ) ); + } + 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( + static_cast< cppu::OWeakObject * >( this ), + 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 & rType = getResourceType( xEnv ); + switch ( rType ) + { + 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 ) ); + } + 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( + static_cast< cppu::OWeakObject * >( this ), + 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 & rType = getResourceType( xEnv ); + switch ( rType ) + { + 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( "Title", aProp ); + + uno::Sequence< beans::Property > aDocProps( 1 ); + aDocProps.getArray()[ 0 ] = aProp; + aSeq.getArray()[ 0 ].Properties = 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 ] = aProp; + aSeq.getArray()[ 1 ].Properties = 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.get() ); +} + + +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 = SerfUri::unescape( m_aEscapedTitle ); + xContext.set( m_xContext ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + + // First, ask cache... + if ( m_xCachedProps.get() ) + { + 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 ( !m_bTransient && !bHasAll ) + { + // Obtain values from server... + + + // First, identify whether resource is DAV or not + bool bNetworkAccessAllowed = true; + const ResourceType & rType = getResourceType( xEnv, xResAccess, &bNetworkAccessAllowed ); + + if ( DAV == rType ) + { + // cache lookup... getResourceType may fill the props cache via + // PROPFIND! + if ( m_xCachedProps.get() ) + { + 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; + + uno::Sequence< beans::Property > aProperties( + rProperties.getLength() ); + + if ( !m_aFailedPropNames.empty() ) + { + sal_Int32 nProps = 0; + sal_Int32 nCount = rProperties.getLength(); + for ( sal_Int32 n = 0; n < nCount; ++n ) + { + const OUString & rName = rProperties[ n ].Name; + + if ( std::none_of(m_aFailedPropNames.begin(), m_aFailedPropNames.end(), + [&rName](const OUString& rPropName) { return rPropName == rName; }) ) + { + aProperties[ nProps ] = rProperties[ n ]; + nProps++; + } + } + + aProperties.realloc( nProps ); + } + else + { + 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 ( xProps.get()) + 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.get() + && xProps->containsAllNames( + rProperties, aMissingProps ) ) + || !m_bDidGetOrHead ) + { + // Possibly the missing props can be obtained using a HEAD + // request. + + std::vector< OUString > aHeaderNames; + ContentProperties::UCBNamesToHTTPNames( + rProperties, + aHeaderNames, + true /* bIncludeUnmatched */ ); + + if ( !aHeaderNames.empty() ) + { + try + { + DAVResource resource; + xResAccess->HEAD( aHeaderNames, resource, xEnv ); + m_bDidGetOrHead = true; + + if ( xProps.get() ) + 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; + + // 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 + // TODO SC_NOT_FOUND is only for google-code server + if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED || + aLastException.getStatus() == SC_METHOD_NOT_ALLOWED || + aLastException.getStatus() == SC_NOT_FOUND ) + { + lcl_sendPartialGETRequest( bError, + aLastException, + aMissingProps, + aHeaderNames, + xResAccess, + xProps, + xEnv ); + m_bDidGetOrHead = !bError; + } + + if ( bError ) + { + if ( !shouldAccessNetworkAfterException( aLastException ) ) + { + cancelCommandExecution( aLastException, xEnv ); + // unreachable + } + } + } + } + } + } + + // might trigger HTTP redirect. + // Therefore, title must be updated here. + SerfUri aUri( xResAccess->getURL() ); + aUnescapedTitle = aUri.GetPathBaseNameUnescaped(); + + if ( rType == 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. + + if ( rType == DAV ) + { + //xProps.reset( + // new ContentProperties( aUnescapedTitle ) ); + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + } + else + { + if ( !xProps.get() ) + xProps.reset( new ContentProperties( aUnescapedTitle, false ) ); + else + xProps->addProperty( + "Title", + uno::makeAny( aUnescapedTitle ), + true ); + + xProps->addProperty( + "IsFolder", + uno::makeAny( false ), + true ); + xProps->addProperty( + "IsDocument", + uno::makeAny( true ), + true ); + xProps->addProperty( + "ContentType", + uno::makeAny( OUString(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 ) ); + } + + 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( + "BaseURI", + uno::makeAny( getBaseURI( xResAccess ) ), + true ); + } + else if ( rName == "CreatableContentsInfo" ) + { + // Add CreatableContentsInfo property, if requested. + bool bFolder = false; + xProps->getValue( "IsFolder" ) + >>= bFolder; + xProps->addProperty( + "CreatableContentsInfo", + uno::makeAny( 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.get() ) + m_xCachedProps.reset( new CachableContentProperties( *xProps ) ); + else + m_xCachedProps->addProperties( *xProps ); + + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + m_aEscapedTitle = SerfUri::escapeSegment( 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() ); + uno::Sequence< beans::PropertyChangeEvent > aChanges( rValues.getLength() ); + sal_Int32 nChanged = 0; + + beans::PropertyChangeEvent aEvent; + aEvent.Source = static_cast< cppu::OWeakObject * >( this ); + 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! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + + // Mandatory props. + + + if ( rName == "ContentType" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsDocument" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "IsFolder" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "Title" ) + { + OUString aNewValue; + if ( rValue.Value >>= aNewValue ) + { + // No empty titles! + if ( aNewValue.getLength() > 0 ) + { + try + { + SerfUri 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 & ) + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Invalid content identifier!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= lang::IllegalArgumentException( + "Empty title not allowed!", + static_cast< cppu::OWeakObject * >( this ), + -1 ); + } + } + else + { + aRet[ n ] <<= beans::IllegalTypeException( + "Property value has wrong type!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + else + { + + // Optional props. + + + OUString aSpecialName; + bool bIsSpecial = DAVProperties::isUCBSpecialProperty( rName, aSpecialName ); + + if ( !xInfo.is() ) + xInfo = getPropertySetInfo( xEnv, + false /* don't cache data */ ); + + if ( !xInfo->hasPropertyByName( bIsSpecial ? aSpecialName : rName ) ) + { + // Check, whether property exists. Skip otherwise. + // PROPPATCH::set would add the property automatically, which + // is not allowed for "setPropertyValues" command! + aRet[ n ] <<= beans::UnknownPropertyException( + "Property is unknown!", + static_cast< cppu::OWeakObject * >( this ) ); + continue; + } + + if ( rName == "Size" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateCreated" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "DateModified" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else if ( rName == "MediaType" ) + { + // Read-only property! + // (but could be writable, if 'getcontenttype' would be) + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + if ( rName == "CreatableContentsInfo" ) + { + // Read-only property! + aRet[ n ] <<= lang::IllegalAccessException( + "Property is read-only!", + static_cast< cppu::OWeakObject * >( this ) ); + } + else + { + if ( getResourceType( xEnv, xResAccess ) == DAV ) + { + // Property value will be set on server. + ProppatchValue aValue( PROPSET, rName, rValue.Value ); + aProppatchValues.push_back( aValue ); + + // remember position within sequence of values (for + // error handling). + aProppatchPropsPositions.push_back( n ); + } + else + { + // Property value will be stored in local property store. + if ( !bTriedToGetAdditionalPropSet && + !xAdditionalPropSet.is() ) + { + xAdditionalPropSet + = getAdditionalPropertySet( false ); + bTriedToGetAdditionalPropSet = true; + } + + if ( xAdditionalPropSet.is() ) + { + try + { + uno::Any aOldValue + = xAdditionalPropSet->getPropertyValue( rName ); + if ( aOldValue != rValue.Value ) + { + xAdditionalPropSet->setPropertyValue( + rName, rValue.Value ); + + aEvent.PropertyName = rName; + aEvent.OldValue = aOldValue; + aEvent.NewValue = rValue.Value; + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + } + catch ( beans::UnknownPropertyException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::WrappedTargetException const & e ) + { + aRet[ n ] <<= e; + } + catch ( beans::PropertyVetoException const & e ) + { + aRet[ n ] <<= e; + } + catch ( lang::IllegalArgumentException const & e ) + { + aRet[ n ] <<= e; + } + } + else + { + aRet[ n ] <<= uno::Exception( + "No property set for storing the value!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + } + } + } // for + + if ( !bTransient && (!aProppatchValues.empty()) ) + { + try + { + // Set property values at server. + xResAccess->PROPPATCH( aProppatchValues, xEnv ); + + for ( const auto& rProppatchValue : aProppatchValues ) + { + aEvent.PropertyName = rProppatchValue.name; + aEvent.OldValue = uno::Any(); // @@@ too expensive to obtain! + aEvent.NewValue = rProppatchValue.value; + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + } + catch ( DAVException const & e ) + { +// SAL_WARN( "ucb.ucp.webdav", +// "Content::setPropertyValues - PROPPATCH failed!" ); + +#if 1 + cancelCommandExecution( e, xEnv ); + // unreachable +#else + // Note: PROPPATCH either sets ALL property values OR NOTHING. + + std::vector< sal_Int32 >::const_iterator it + = aProppatchPropsPositions.begin(); + std::vector< sal_Int32 >::const_iterator end + = aProppatchPropsPositions.end(); + + while ( it != end ) + { + // Set error. + aRet[ (*it) ] <<= MapDAVException( e, true ); + ++it; + } +#endif + } + } + + if ( bExchange ) + { + // Assemble new content identifier... + + OUString aNewURL = getParentURL(); + if ( aNewURL.lastIndexOf( '/' ) != ( aNewURL.getLength() - 1 ) ) + aNewURL += "/"; + + aNewURL += SerfUri::escapeSegment( aNewTitle ); + + uno::Reference< ucb::XContentIdentifier > xNewId + = new ::ucbhelper::ContentIdentifier( aNewURL ); + uno::Reference< ucb::XContentIdentifier > xOldId = xIdentifier; + + try + { + SerfUri sourceURI( xOldId->getContentIdentifier() ); + SerfUri targetURI( xNewId->getContentIdentifier() ); + targetURI.SetScheme( sourceURI.GetScheme() ); + + xResAccess->MOVE( + sourceURI.GetPath(), targetURI.GetURI(), false, xEnv ); + // @@@ Should check for resources that could not be moved + // (due to source access or target overwrite) and send + // this information through the interaction handler. + + // @@@ Existing content should be checked to see if it needs + // to be deleted at the source + + // @@@ Existing content should be checked to see if it has + // been overwritten at the target + + if ( exchangeIdentity( xNewId ) ) + { + xResAccess->setURL( aNewURL ); + +// DAV resources store all additional props on server! +// // Adapt Additional Core Properties. +// renameAdditionalPropertySet( xOldId->getContentIdentifier(), +// xNewId->getContentIdentifier(), +// true ); + } + else + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] <<= uno::Exception( + "Exchange failed!", + static_cast< cppu::OWeakObject * >( this ) ); + } + } + catch ( DAVException const & e ) + { + // Do not set new title! + aNewTitle.clear(); + + // Set error . + aRet[ nTitlePos ] = MapDAVException( e, true ); + } + } + + if ( aNewTitle.getLength() ) + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + aEvent.PropertyName = "Title"; + aEvent.OldValue <<= aOldTitle; + aEvent.NewValue <<= aNewTitle; + + m_aEscapedTitle = SerfUri::escapeSegment( aNewTitle ); + + aChanges.getArray()[ nChanged ] = aEvent; + nChanged++; + } + + if ( nChanged > 0 ) + { + aChanges.realloc( nChanged ); + notifyPropertiesChange( aChanges ); + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + + return aRet; +} + + +uno::Any Content::open( + const ucb::OpenCommandArgument2 & rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + uno::Any aRet; + + bool bOpenFolder = ( ( rArg.Mode == ucb::OpenMode::ALL ) || + ( rArg.Mode == ucb::OpenMode::FOLDERS ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENTS ) ); + if ( bOpenFolder ) + { + if ( isFolder( xEnv ) ) + { + // Open collection. + + uno::Reference< ucb::XDynamicResultSet > xSet + = new DynamicResultSet( m_xContext, this, rArg, xEnv ); + aRet <<= xSet; + } + else + { + // Error: Not a folder! + + OUString aMsg( "Non-folder resource cannot be opened as folder! Wrong Open Mode!" ); + + ucbhelper::cancelCommandExecution( + uno::makeAny( + lang::IllegalArgumentException( + aMsg, + static_cast< cppu::OWeakObject * >( this ), + -1 ) ), + xEnv ); + // Unreachable + } + } + + if ( rArg.Sink.is() ) + { + // Open document. + + if ( ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_NONE ) || + ( rArg.Mode == ucb::OpenMode::DOCUMENT_SHARE_DENY_WRITE ) ) + { + // Currently(?) unsupported. + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedOpenModeException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + sal_Int16( rArg.Mode ) ) ), + xEnv ); + // Unreachable + } + + uno::Reference< io::XOutputStream > xOut( rArg.Sink, uno::UNO_QUERY ); + if ( xOut.is() ) + { + // PUSH: write data + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::MutexGuard aGuard( m_aMutex ); + + xResAccess.reset( + new DAVResourceAccess( *m_xResAccess ) ); + } + + DAVResource aResource; + std::vector< OUString > aHeaders; + + xResAccess->GET( xOut, aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if ( !m_xCachedProps.get()) + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( aResource ) ) ); + else + m_xCachedProps->addProperties( ContentProperties( aResource ) ); + + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + uno::Reference< io::XActiveDataSink > xDataSink( rArg.Sink, uno::UNO_QUERY ); + if ( xDataSink.is() ) + { + // PULL: wait for client read + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + + xResAccess.reset( + new DAVResourceAccess( *m_xResAccess ) ); + } + + // fill inputstream sync; return if all data present + DAVResource aResource; + std::vector< OUString > aHeaders; + + uno::Reference< io::XInputStream > xIn + = xResAccess->GET( aHeaders, aResource, xEnv ); + m_bDidGetOrHead = true; + + { + osl::MutexGuard aGuard( m_aMutex ); + + // cache headers. + if ( !m_xCachedProps.get()) + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( aResource ) ) ); + else + m_xCachedProps->addProperties( + aResource.properties ); + + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + + xDataSink->setInputStream( xIn ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv ); + // Unreachable + } + } + else + { + // Note: aOpenCommand.Sink may contain an XStream + // implementation. Support for this type of + // sink is optional... + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArg.Sink ) ), + xEnv ); + // Unreachable + } + } + } + + return aRet; +} + + +void Content::post( + const ucb::PostCommandArgument2 & rArg, + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + uno::Reference< io::XActiveDataSink > xSink( rArg.Sink, uno::UNO_QUERY ); + if ( xSink.is() ) + { + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset( + new DAVResourceAccess( *m_xResAccess ) ); + } + + uno::Reference< io::XInputStream > xResult + = xResAccess->POST( rArg.MediaType, + rArg.Referer, + rArg.Source, + xEnv ); + + { + osl::MutexGuard aGuard( m_aMutex ); + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + + xSink->setInputStream( xResult ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv, true ); + // Unreachable + } + } + else + { + uno::Reference< io::XOutputStream > xResult( rArg.Sink, uno::UNO_QUERY ); + if ( xResult.is() ) + { + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset( + new DAVResourceAccess( *m_xResAccess ) ); + } + + xResAccess->POST( rArg.MediaType, + rArg.Referer, + rArg.Source, + xResult, + xEnv ); + + { + osl::MutexGuard aGuard( m_aMutex ); + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, xEnv, true ); + // Unreachable + } + } + else + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedDataSinkException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArg.Sink ) ), + xEnv ); + // Unreachable + } + } +} + + +void Content::queryChildren( ContentRefList& rChildren ) +{ + // Obtain a list with a snapshot of all currently instantiated contents + // from provider and extract the contents which are direct children + // of this content. + + ::ucbhelper::ContentRefList aAllContents; + m_xProvider->queryExistingContents( aAllContents ); + + OUString aURL = m_xIdentifier->getContentIdentifier(); + sal_Int32 nURLPos = aURL.lastIndexOf( '/' ); + + if ( nURLPos != ( aURL.getLength() - 1 ) ) + { + // No trailing slash found. Append. + aURL += "/"; + } + + sal_Int32 nLen = aURL.getLength(); + + for ( const auto& rChild : aAllContents ) + { + ::ucbhelper::ContentImplHelperRef xChild = rChild; + OUString aChildURL + = xChild->getIdentifier()->getContentIdentifier(); + + // Is aURL a prefix of aChildURL? + if ( ( aChildURL.getLength() > nLen ) && + ( aChildURL.startsWith( aURL ) ) ) + { + sal_Int32 nPos = nLen; + nPos = aChildURL.indexOf( '/', nPos ); + + if ( ( nPos == -1 ) || + ( nPos == ( aChildURL.getLength() - 1 ) ) ) + { + // No further slashes / only a final slash. It's a child! + rChildren.push_back( + ::http_dav_ucp::Content::ContentRef( + static_cast< ::http_dav_ucp::Content * >( + xChild.get() ) ) ); + } + } + } +} + + +void Content::insert( + const uno::Reference< io::XInputStream > & xInputStream, + bool bReplaceExisting, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + bool bTransient, bCollection; + OUString aEscapedTitle; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + bTransient = m_bTransient; + bCollection = m_bCollection; + aEscapedTitle = m_aEscapedTitle; + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + // Check, if all required properties are present. + + if ( aEscapedTitle.isEmpty() ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::insert - Title missing!" ); + + uno::Sequence<OUString> aProps { "Title" }; + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::MissingPropertiesException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + aProps ) ), + Environment ); + // Unreachable + } + + if ( !bReplaceExisting ) + { + /* [RFC 2616] - HTTP + + The PUT method requests that the enclosed entity be stored under the + supplied Request-URI. If the Request-URI refers to an already + existing resource, the enclosed entity SHOULD be considered as a + modified version of the one residing on the origin server. + */ + + /* [RFC 2518] - WebDAV + + MKCOL creates a new collection resource at the location specified by + the Request-URI. If the resource identified by the Request-URI is + non-null then the MKCOL MUST fail. + */ + + // ==> Complain on PUT, continue on MKCOL. + if ( !bTransient || !bCollection ) + { +#undef ERROR + ucb::UnsupportedNameClashException aEx( + "Unable to write without overwrite!", + static_cast< cppu::OWeakObject * >( this ), + ucb::NameClash::ERROR ); + + uno::Reference< task::XInteractionHandler > xIH; + + if ( Environment.is() ) + xIH = Environment->getInteractionHandler(); + + if ( xIH.is() ) + { + uno::Any aExAsAny( uno::makeAny( aEx ) ); + + rtl::Reference< ucbhelper::SimpleInteractionRequest > xRequest + = new ucbhelper::SimpleInteractionRequest( + aExAsAny, + ContinuationFlags::Approve + | ContinuationFlags::Disapprove ); + xIH->handle( xRequest.get() ); + + const ContinuationFlags nResp = xRequest->getResponse(); + + switch ( nResp ) + { + case ContinuationFlags::NONE: + // Not handled; throw. + throw aEx; +// break; + + case ContinuationFlags::Approve: + // Continue -> Overwrite. + bReplaceExisting = true; + break; + + case ContinuationFlags::Disapprove: + // Abort. + throw ucb::CommandFailedException( + OUString(), + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + + default: + SAL_WARN( "ucb.ucp.webdav", + "Content::insert - " + "Unknown interaction selection!" ); + throw ucb::CommandFailedException( + "Unknown interaction selection!", + uno::Reference< uno::XInterface >(), + aExAsAny ); +// break; + } + } + else + { + // No IH; throw. + throw aEx; + } + } + } + + if ( bTransient ) + { + // Assemble new content identifier... + OUString aURL = getParentURL(); + if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += aEscapedTitle; + + try + { + xResAccess->setURL( aURL ); + + if ( bCollection ) + xResAccess->MKCOL( Environment ); + else + xResAccess->PUT( xInputStream, Environment ); + } + catch ( DAVException const & except ) + { + if ( bCollection ) + { + if ( except.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // [RFC 2518] - WebDAV + // 405 (Method Not Allowed) - MKCOL can only be + // executed on a deleted/non-existent resource. + + if ( bReplaceExisting ) + { + // Destroy old resource. + try + { + xResAccess->DESTROY( Environment ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + + // Insert (recursion!). + insert( xInputStream, bReplaceExisting, Environment ); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( + new DAVResourceAccess( *xResAccess ) ); + } + + // Success! + return; + } + else + { + OUString aTitle; + try + { + SerfUri aURI( aURL ); + aTitle = aURI.GetPathBaseNameUnescaped(); + } + catch ( DAVException const & ) + { + } + + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aTitle ) ), + Environment ); + // Unreachable + } + } + } + + cancelCommandExecution( except, Environment, true ); + // Unreachable + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xIdentifier + = new ::ucbhelper::ContentIdentifier( aURL ); + } + + inserted(); + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_bTransient = false; + } + } + else + { + if ( !xInputStream.is() ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::MissingInputStreamException( + OUString(), + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + try + { + xResAccess->PUT( xInputStream, Environment ); + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } +} + + +void Content::transfer( + const ucb::TransferInfo & rArgs, + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + uno::Reference< uno::XComponentContext > xContext; + uno::Reference< ucb::XContentIdentifier > xIdentifier; + uno::Reference< ucb::XContentProvider > xProvider; + std::unique_ptr< DAVResourceAccess > xResAccess; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + xContext.set( m_xContext ); + xIdentifier.set( m_xIdentifier ); + xProvider.set( m_xProvider.get() ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + OUString aTargetURI; + try + { + SerfUri sourceURI( rArgs.SourceURL ); + SerfUri targetURI( xIdentifier->getContentIdentifier() ); + aTargetURI = targetURI.GetPathBaseNameUnescaped(); + + // Check source's and target's URL scheme + + OUString aScheme = sourceURI.GetScheme().toAsciiLowerCase(); + if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if ( aScheme == DAV_URL_SCHEME ) + { + sourceURI.SetScheme( HTTP_URL_SCHEME ); + } + else if ( aScheme == DAVS_URL_SCHEME ) + { + sourceURI.SetScheme( HTTPS_URL_SCHEME ); + } + else if (aScheme == WEBDAV_URL_SCHEME) + { + sourceURI.SetScheme(HTTP_URL_SCHEME); + } + else if (aScheme == WEBDAVS_URL_SCHEME) + { + sourceURI.SetScheme(HTTPS_URL_SCHEME); + } + else + { + if ( aScheme != HTTP_URL_SCHEME && aScheme != HTTPS_URL_SCHEME ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::InteractiveBadTransferURLException( + "Unsupported URL scheme!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + } + + aScheme = targetURI.GetScheme().toAsciiLowerCase(); + if ( aScheme == VNDSUNSTARWEBDAV_URL_SCHEME) + targetURI.SetScheme( HTTP_URL_SCHEME ); + else if ( aScheme == VNDSUNSTARWEBDAVS_URL_SCHEME) + targetURI.SetScheme( HTTPS_URL_SCHEME ); + else if ( aScheme == DAV_URL_SCHEME ) + targetURI.SetScheme( HTTP_URL_SCHEME ); + else if ( aScheme == DAVS_URL_SCHEME ) + targetURI.SetScheme( HTTPS_URL_SCHEME ); + else if (aScheme == WEBDAV_URL_SCHEME) + targetURI.SetScheme(HTTP_URL_SCHEME); + else if (aScheme == WEBDAVS_URL_SCHEME) + targetURI.SetScheme(HTTPS_URL_SCHEME); + + // @@@ This implementation of 'transfer' only works + // if the source and target are located at same host. + // (Neon does not support cross-server copy/move) + + // Check for same host + + if ( sourceURI.GetHost().getLength() && + ( sourceURI.GetHost() != targetURI.GetHost() ) ) + { + ucbhelper::cancelCommandExecution( + uno::makeAny( ucb::InteractiveBadTransferURLException( + "Different hosts!", + static_cast< cppu::OWeakObject * >( this ) ) ), + Environment ); + // Unreachable + } + + OUString aTitle = rArgs.NewTitle; + + if ( aTitle.isEmpty() ) + aTitle = sourceURI.GetPathBaseNameUnescaped(); + + if ( aTitle == "/" ) + { + // kso: ??? + aTitle.clear(); + } + + targetURI.AppendPath( aTitle ); + + OUString aTargetURL = xIdentifier->getContentIdentifier(); + if ( ( aTargetURL.lastIndexOf( '/' ) + 1 ) + != aTargetURL.getLength() ) + aTargetURL += "/"; + + aTargetURL += aTitle; + + uno::Reference< ucb::XContentIdentifier > xTargetId + = new ::ucbhelper::ContentIdentifier( aTargetURL ); + + DAVResourceAccess aSourceAccess( xContext, + xResAccess->getSessionFactory(), + sourceURI.GetURI() ); + + if ( rArgs.MoveData ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( rArgs.SourceURL ); + + // Note: The static cast is okay here, because its sure that + // xProvider is always the WebDAVContentProvider. + rtl::Reference< Content > xSource + = static_cast< Content * >( + xProvider->queryContent( xId ).get() ); + + // [RFC 2518] - WebDAV + // If a resource exists at the destination and the Overwrite + // header is "T" then prior to performing the move the server + // MUST perform a DELETE with "Depth: infinity" on the + // destination resource. If the Overwrite header is set to + // "F" then the operation will fail. + + aSourceAccess.MOVE( sourceURI.GetPath(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + + if ( xSource.is() ) + { + // Propagate destruction to listeners. + xSource->destroy( true ); + } + +// DAV resources store all additional props on server! +// // Rename own and all children's Additional Core Properties. +// renameAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// true ); + } + else + { + // [RFC 2518] - WebDAV + // If a resource exists at the destination and the Overwrite + // header is "T" then prior to performing the copy the server + // MUST perform a DELETE with "Depth: infinity" on the + // destination resource. If the Overwrite header is set to + // "F" then the operation will fail. + + aSourceAccess.COPY( sourceURI.GetPath(), + targetURI.GetURI(), + rArgs.NameClash + == ucb::NameClash::OVERWRITE, + Environment ); + +// DAV resources store all additional props on server! +// // Copy own and all children's Additional Core Properties. +// copyAdditionalPropertySet( xId->getContentIdentifier(), +// xTargetId->getContentIdentifier(), +// true ); + } + + // Note: The static cast is okay here, because its sure that + // xProvider is always the WebDAVContentProvider. + rtl::Reference< Content > xTarget + = static_cast< Content * >( + xProvider->queryContent( xTargetId ).get() ); + + // Announce transferred content in its new folder. + xTarget->inserted(); + } + catch ( ucb::IllegalIdentifierException const & ) + { + // queryContent + } + catch ( DAVException const & e ) + { + // [RFC 2518] - WebDAV + // 412 (Precondition Failed) - The server was unable to maintain + // the liveness of the properties listed in the propertybehavior + // XML element or the Overwrite header is "F" and the state of + // the destination resource is non-null. + + if ( e.getStatus() == SC_PRECONDITION_FAILED ) + { + switch ( rArgs.NameClash ) + { + case 0/*ucb::NameClash::ERROR*/: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::NameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aTargetURI ) ), + Environment ); + // Unreachable + } + [[fallthrough]]; + + case ucb::NameClash::OVERWRITE: + break; + + case ucb::NameClash::KEEP: // deprecated + case ucb::NameClash::RENAME: + case ucb::NameClash::ASK: + default: + { + ucbhelper::cancelCommandExecution( + uno::makeAny( + ucb::UnsupportedNameClashException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + rArgs.NameClash ) ), + Environment ); + // Unreachable + } + } + } + + cancelCommandExecution( e, Environment, true ); + // Unreachable + } + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } +} + + +void Content::destroy( bool bDeletePhysical ) +{ + // @@@ take care about bDeletePhysical -> trashcan support + + uno::Reference< ucb::XContent > xThis = this; + + deleted(); + + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // Process instantiated children... + + ::http_dav_ucp::Content::ContentRefList aChildren; + queryChildren( aChildren ); + + for ( auto& rChild : aChildren ) + { + rChild->destroy( bDeletePhysical ); + } +} + + +bool Content::supportsExclusiveWriteLock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + if ( getResourceType( Environment ) == DAV ) + { + if ( m_xCachedProps.get() ) + { + uno::Sequence< ucb::LockEntry > aSupportedLocks; + if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK ) + >>= aSupportedLocks ) + { + for ( sal_Int32 n = 0; n < aSupportedLocks.getLength(); ++n ) + { + if ( aSupportedLocks[ n ].Scope + == ucb::LockScope_EXCLUSIVE && + aSupportedLocks[ n ].Type + == ucb::LockType_WRITE ) + return true; + } + } + } + } + return false; +} + + +void Content::lock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + uno::Any aOwnerAny; + aOwnerAny <<= OUString( "http://ucb.openoffice.org" ); + + ucb::Lock aLock( + ucb::LockScope_EXCLUSIVE, + ucb::LockType_WRITE, + ucb::LockDepth_ZERO, + aOwnerAny, + 180, // lock timeout in secs + //-1, // infinite lock + uno::Sequence< OUString >() ); + + xResAccess->LOCK( aLock, Environment ); + m_bLocked = true; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, false ); + // Unreachable + } +} + + +void Content::unlock( + const uno::Reference< ucb::XCommandEnvironment >& Environment ) +{ + try + { + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + + xResAccess->UNLOCK( Environment ); + m_bLocked = false; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + } + catch ( DAVException const & e ) + { + cancelCommandExecution( e, Environment, false ); + // Unreachable + } +} + + +bool Content::exchangeIdentity( + const uno::Reference< ucb::XContentIdentifier >& xNewId ) +{ + if ( !xNewId.is() ) + return false; + + osl::ClearableGuard< osl::Mutex > aGuard( m_aMutex ); + + uno::Reference< ucb::XContent > xThis = this; + + // Already persistent? + if ( m_bTransient ) + { + SAL_WARN( "ucb.ucp.webdav", "Content::exchangeIdentity - Not persistent!" ); + return false; + } + + // Exchange own identitity. + + // Fail, if a content with given id already exists. +// if ( !hasData( xNewId ) ) + { + OUString aOldURL = m_xIdentifier->getContentIdentifier(); + + aGuard.clear(); + if ( exchange( xNewId ) ) + { + // Process instantiated children... + + ContentRefList aChildren; + queryChildren( aChildren ); + + for ( const auto& rChild : aChildren ) + { + ContentRef xChild = rChild; + + // Create new content identifier for the child... + uno::Reference< ucb::XContentIdentifier > + xOldChildId = xChild->getIdentifier(); + OUString aOldChildURL + = xOldChildId->getContentIdentifier(); + OUString aNewChildURL + = aOldChildURL.replaceAt( + 0, + aOldURL.getLength(), + xNewId->getContentIdentifier() ); + uno::Reference< ucb::XContentIdentifier > xNewChildId + = new ::ucbhelper::ContentIdentifier( aNewChildURL ); + + if ( !xChild->exchangeIdentity( xNewChildId ) ) + return false; + } + return true; + } + } + + SAL_WARN( "ucb.ucp.webdav", + "Content::exchangeIdentity - " + "Panic! Cannot exchange identity!" ); + return false; +} + + +bool Content::isFolder( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + { + osl::MutexGuard aGuard( m_aMutex ); + + if ( m_bTransient ) + return m_bCollection; + } + + uno::Sequence< beans::Property > aProperties( 1 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 0 ].Handle = -1; + uno::Reference< sdbc::XRow > xRow( getPropertyValues( aProperties, xEnv ) ); + if ( xRow.is() ) + { + try + { + return xRow->getBoolean( 1 ); + } + catch ( sdbc::SQLException const & ) + { + } + } + + return false; +} + + +uno::Any Content::MapDAVException( const DAVException & e, bool bWrite ) +{ + // Map DAVException... + uno::Any aException; + + OUString aURL; + if ( m_bTransient ) + { + aURL = getParentURL(); + if ( aURL.lastIndexOf( '/' ) != ( aURL.getLength() - 1 ) ) + aURL += "/"; + + aURL += m_aEscapedTitle; + } + else + { + aURL = m_xIdentifier->getContentIdentifier(); + } + + switch ( e.getStatus() ) + { + case SC_NOT_FOUND: + { + uno::Sequence< uno::Any > aArgs( 1 ); + aArgs[ 0 ] <<= beans::PropertyValue( + "Uri", -1, + uno::makeAny(aURL), + beans::PropertyState_DIRECT_VALUE); + + aException <<= + ucb::InteractiveAugmentedIOException( + "Not found!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + ucb::IOErrorCode_NOT_EXISTING, + aArgs ); + return aException; + } + default: + break; + } + + switch ( e.getError() ) + { + case DAVException::DAV_HTTP_ERROR: + { + if ( bWrite ) + aException <<= + ucb::InteractiveNetworkWriteException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + else + aException <<= + ucb::InteractiveNetworkReadException( + e.getData(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + } + + case DAVException::DAV_HTTP_LOOKUP: + aException <<= + ucb::InteractiveNetworkResolveNameException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_AUTH: +// break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_AUTHPROXY: +// break; + + case DAVException::DAV_HTTP_CONNECT: + aException <<= + ucb::InteractiveNetworkConnectException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + e.getData() ); + break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_TIMEOUT: +// break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_HTTP_REDIRECT: +// break; + +// @@@ No matching InteractiveNetwork*Exception +// case DAVException::DAV_SESSION_CREATE: +// break; + + case DAVException::DAV_INVALID_ARG: + aException <<= + lang::IllegalArgumentException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + -1 ); + break; + + case DAVException::DAV_LOCKED: +#if 1 + aException <<= + ucb::InteractiveLockingLockedException( + "Locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + false ); // not SelfOwned +#else + { + uno::Sequence< uno::Any > aArgs( 1 ); + aArgs[ 0 ] <<= beans::PropertyValue( + OUString("Uri"), -1, + uno::makeAny(aURL), + beans::PropertyState_DIRECT_VALUE); + + aException <<= + ucb::InteractiveAugmentedIOException( + OUString( "Locked!" ), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + ucb::IOErrorCode_LOCKING_VIOLATION, + aArgs ); + } +#endif + break; + + case DAVException::DAV_LOCKED_SELF: + aException <<= + ucb::InteractiveLockingLockedException( + "Locked (self)!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL, + true ); // SelfOwned + break; + + case DAVException::DAV_NOT_LOCKED: + aException <<= + ucb::InteractiveLockingNotLockedException( + "Not locked!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + case DAVException::DAV_LOCK_EXPIRED: + aException <<= + ucb::InteractiveLockingLockExpiredException( + "Lock expired!", + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR, + aURL ); + break; + + default: + aException <<= + ucb::InteractiveNetworkGeneralException( + OUString(), + static_cast< cppu::OWeakObject * >( this ), + task::InteractionClassification_ERROR ); + break; + } + + return aException; +} + + +// static +bool Content::shouldAccessNetworkAfterException( const DAVException & e ) +{ + if ( ( e.getStatus() == SC_NOT_FOUND ) || + ( e.getError() == DAVException::DAV_HTTP_LOOKUP ) || + ( e.getError() == DAVException::DAV_HTTP_CONNECT ) || + ( e.getError() == DAVException::DAV_HTTP_AUTH ) || + ( e.getError() == DAVException::DAV_HTTP_AUTHPROXY ) ) + return false; + + return true; +} + + +void Content::cancelCommandExecution( + const DAVException & e, + const uno::Reference< ucb::XCommandEnvironment > & xEnv, + bool bWrite /* = false */ ) +{ + ucbhelper::cancelCommandExecution( MapDAVException( e, bWrite ), xEnv ); + // Unreachable +} + + +OUString +Content::getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + // First, try to obtain value of response header "Content-Location". + if ( m_xCachedProps.get() ) + { + OUString aLocation; + m_xCachedProps->getValue( "Content-Location" ) >>= aLocation; + if ( aLocation.getLength() ) + { + try + { + // Do not use m_xIdentifier->getContentIdentifier() because it + // for example does not reflect redirects applied to requests + // done using the original URI but m_xResAccess' URI does. + return rtl::Uri::convertRelToAbs( rResAccess->getURL(), + aLocation ); + } + catch ( rtl::MalformedUriException const & ) + { + } + } + } + + return rResAccess->getURL(); +} + + +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed ) +{ + { + osl::MutexGuard g(m_aMutex); + if (m_eResourceType != UNKNOWN) { + return m_eResourceType; + } + } + + ResourceType eResourceType = UNKNOWN; + + try + { + // Try to fetch some frequently used property value, e.g. those + // used when loading documents... along with identifying whether + // this is a DAV resource. + std::vector< DAVResource > resources; + std::vector< OUString > aPropNames; + uno::Sequence< beans::Property > aProperties( 5 ); + aProperties[ 0 ].Name = "IsFolder"; + aProperties[ 1 ].Name = "IsDocument"; + aProperties[ 2 ].Name = "IsReadOnly"; + aProperties[ 3 ].Name = "MediaType"; + aProperties[ 4 ].Name = DAVProperties::SUPPORTEDLOCK; + + ContentProperties::UCBNamesToDAVNames( + aProperties, aPropNames ); + + rResAccess->PROPFIND( + DAVZERO, aPropNames, resources, xEnv ); + + // TODO - is this really only one? + if ( resources.size() == 1 ) + { + osl::MutexGuard g(m_aMutex); + m_xCachedProps.reset( + new CachableContentProperties( ContentProperties( resources[ 0 ] ) ) ); + m_xCachedProps->containsAllNames( + aProperties, m_aFailedPropNames ); + } + + eResourceType = DAV; + } + catch ( DAVException const & e ) + { + rResAccess->resetUri(); + + if ( e.getStatus() == SC_METHOD_NOT_ALLOWED ) + { + // Status SC_METHOD_NOT_ALLOWED is a safe indicator that the + // resource is NON_DAV + eResourceType = NON_DAV; + } + else if (networkAccessAllowed != nullptr) + { + *networkAccessAllowed = *networkAccessAllowed + && shouldAccessNetworkAfterException(e); + } + + // cancel command execution is case that no user authentication data has been provided. + if ( e.getError() == DAVException::DAV_HTTP_NOAUTH ) + { + cancelCommandExecution( e, uno::Reference< ucb::XCommandEnvironment >() ); + } + } + + osl::MutexGuard g(m_aMutex); + if (m_eResourceType == UNKNOWN) { + m_eResourceType = eResourceType; + } else { + SAL_WARN_IF( + eResourceType != m_eResourceType, "ucb.ucp.webdav", + "different resource types for <" << rResAccess->getURL() << ">: " + << +eResourceType << " vs. " << +m_eResourceType); + } + return m_eResourceType; +} + + +Content::ResourceType Content::getResourceType( + const uno::Reference< ucb::XCommandEnvironment >& xEnv ) +{ + std::unique_ptr< DAVResourceAccess > xResAccess; + { + osl::MutexGuard aGuard( m_aMutex ); + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + } + const Content::ResourceType & ret = getResourceType( xEnv, xResAccess ); + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + m_xResAccess.reset( new DAVResourceAccess( *xResAccess ) ); + } + return ret; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavcontent.hxx b/ucb/source/ucp/webdav/webdavcontent.hxx new file mode 100644 index 000000000..ad4e93229 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontent.hxx @@ -0,0 +1,269 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVCONTENT_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVCONTENT_HXX + +#include <memory> +#include <list> +#include <rtl/ref.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContentCreator.hpp> +#include <ucbhelper/contenthelper.hxx> +#include "DAVResourceAccess.hxx" +#include "PropertyMap.hxx" + +namespace com::sun::star::beans { + struct Property; + struct PropertyValue; +} + +namespace com::sun::star::io { + class XInputStream; +} + +namespace com::sun::star::sdbc { + class XRow; +} + +namespace com::sun::star::ucb { + struct OpenCommandArgument2; + struct PropertyCommandArgument; + struct PostCommandArgument2; + struct TransferInfo; +} + +namespace http_dav_ucp +{ + + +// UNO service name for the content. +#define WEBDAV_CONTENT_SERVICE_NAME "com.sun.star.ucb.WebDAVContent" + + +class ContentProvider; +class ContentProperties; +class CachableContentProperties; + +class Content : public ::ucbhelper::ContentImplHelper, + public css::ucb::XContentCreator +{ + enum ResourceType + { + UNKNOWN, + NON_DAV, + DAV + }; + + std::unique_ptr< DAVResourceAccess > m_xResAccess; + std::unique_ptr< CachableContentProperties > m_xCachedProps; // locally cached props + OUString m_aEscapedTitle; + ResourceType m_eResourceType; + ContentProvider* m_pProvider; // No need for a ref, base class holds object + bool m_bTransient; + bool m_bLocked; + bool m_bCollection; + bool m_bDidGetOrHead; + std::vector< OUString > m_aFailedPropNames; + +private: + virtual css::uno::Sequence< css::beans::Property > + getProperties( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual css::uno::Sequence< css::ucb::CommandInfo > + getCommands( const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ) override; + virtual OUString getParentURL() override; + + /// @throws css::uno::Exception + bool isFolder( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Sequence< css::beans::Property >& rProperties, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + css::uno::Sequence< css::uno::Any > + setPropertyValues( const css::uno::Sequence< css::beans::PropertyValue >& rValues, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + typedef rtl::Reference< Content > ContentRef; + typedef std::vector< ContentRef > ContentRefList; + void queryChildren( ContentRefList& rChildren); + + bool + exchangeIdentity( const css::uno::Reference< css::ucb::XContentIdentifier >& xNewId ); + + OUString + getBaseURI( const std::unique_ptr< DAVResourceAccess > & rResAccess ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv ); + + /// @throws css::uno::Exception + ResourceType + getResourceType( const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + const std::unique_ptr< DAVResourceAccess > & rResAccess, + bool * networkAccessAllowed = nullptr ); + + // Command "open" + /// @throws css::uno::Exception + css::uno::Any open( + const css::ucb::OpenCommandArgument2 & rArg, + const css::uno::Reference< + css::ucb::XCommandEnvironment > & xEnv ); + + // Command "post" + /// @throws css::uno::Exception + void post( const css::ucb::PostCommandArgument2 & rArg, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv ); + + // Command "insert" + /// @throws css::uno::Exception + void insert( const css::uno::Reference< css::io::XInputStream > & xInputStream, + bool bReplaceExisting, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "transfer" + /// @throws css::uno::Exception + void transfer( const css::ucb::TransferInfo & rArgs, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "delete" + /// @throws css::uno::Exception + void destroy( bool bDeletePhysical ); + + // Command "lock" + /// @throws css::uno::Exception + void lock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // Command "unlock" + /// @throws css::uno::Exception + void unlock( const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + css::uno::Any MapDAVException( const DAVException & e, + bool bWrite ); + /// @throws css::uno::Exception + void cancelCommandExecution( + const DAVException & e, + const css::uno::Reference< css::ucb::XCommandEnvironment > & xEnv, + bool bWrite = false ); + + static bool shouldAccessNetworkAfterException( const DAVException & e ); + + bool supportsExclusiveWriteLock( + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + // XPropertyContainer replacement + /// @throws css::beans::PropertyExistException + /// @throws css::beans::IllegalTypeException + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void addProperty( const css::ucb::PropertyCommandArgument &aCmdArg, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); + + /// @throws css::beans::PropertyExistException + /// @throws css::beans::NotRemoveableException + /// @throws css::uno::RuntimeException + void removeProperty( const OUString& Name, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ); +public: + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory ); + /// @throws css::ucb::ContentCreationException + Content( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + ContentProvider* pProvider, + const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier, + rtl::Reference< DAVSessionFactory > const & rSessionFactory, + bool isCollection ); + virtual ~Content() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL + getImplementationName() override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XContent + virtual OUString SAL_CALL + getContentType() override; + + // XCommandProcessor + virtual css::uno::Any SAL_CALL + execute( const css::ucb::Command& aCommand, + sal_Int32 CommandId, + const css::uno::Reference< css::ucb::XCommandEnvironment >& Environment ) override; + virtual void SAL_CALL + abort( sal_Int32 CommandId ) override; + + // XPropertyContainer + virtual void SAL_CALL + addProperty( const OUString& Name, + sal_Int16 Attributes, + const css::uno::Any& DefaultValue ) override; + + virtual void SAL_CALL + removeProperty( const OUString& Name ) override; + + + // Additional interfaces + + + // XContentCreator + virtual css::uno::Sequence< css::ucb::ContentInfo > SAL_CALL + queryCreatableContentsInfo() override; + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + createNewContent( const css::ucb::ContentInfo& Info ) override; + + + // Non-interface methods. + + + DAVResourceAccess & getResourceAccess() { return *m_xResAccess; } + + // Called from resultset data supplier. + static css::uno::Reference< css::sdbc::XRow > + getPropertyValues( const css::uno::Reference< css::uno::XComponentContext >& rContext, + const css::uno::Sequence< css::beans::Property >& rProperties, + const ContentProperties& rData, + const rtl::Reference< ::ucbhelper::ContentProviderImplHelper >& rProvider, + const OUString& rContentId ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavcontentcaps.cxx b/ucb/source/ucp/webdav/webdavcontentcaps.cxx new file mode 100644 index 000000000..14050e2a1 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavcontentcaps.cxx @@ -0,0 +1,602 @@ +/* -*- 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 <set> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ucb/CommandInfo.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.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/uno/Sequence.hxx> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <com/sun/star/ucb/LockEntry.hpp> +#include "webdavcontent.hxx" +#include "webdavprovider.hxx" +#include "DAVProperties.hxx" +#include "ContentProperties.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// ContentProvider implementation. + + +bool ContentProvider::getProperty( + const OUString & rPropName, beans::Property & rProp, bool bStrict ) +{ + if ( !m_pProps ) + { + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_pProps ) + { + m_pProps = std::make_unique<PropertyMap>(); + + + // Fill map of known properties... + + + // Mandatory UCB properties. + m_pProps->insert( + beans::Property( + "ContentType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsDocument", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "IsFolder", + -1, + cppu::UnoType<bool>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Title", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + // Optional UCB properties. + + m_pProps->insert( + beans::Property( + "DateCreated", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "DateModified", + -1, + cppu::UnoType<util::DateTime>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "MediaType", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "Size", + -1, + cppu::UnoType<sal_Int64>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "BaseURI", + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + "CreatableContentsInfo", + -1, + cppu::UnoType<uno::Sequence< ucb::ContentInfo >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + // Standard DAV properties. + + m_pProps->insert( + beans::Property( + DAVProperties::CREATIONDATE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::DISPLAYNAME, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLANGUAGE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTLENGTH, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETCONTENTTYPE , + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETETAG, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::GETLASTMODIFIED, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::LOCKDISCOVERY, + -1, + cppu::UnoType<uno::Sequence< ucb::Lock >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::RESOURCETYPE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::SUPPORTEDLOCK, + -1, + cppu::UnoType<uno::Sequence< ucb::LockEntry >>::get(), + beans::PropertyAttribute::BOUND + | beans::PropertyAttribute::READONLY ) ); + + m_pProps->insert( + beans::Property( + DAVProperties::EXECUTABLE, + -1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ) ); + } + } + + + // Lookup property. + + + beans::Property aProp; + aProp.Name = rPropName; + const PropertyMap::const_iterator it = m_pProps->find( aProp ); + if ( it != m_pProps->end() ) + { + rProp = *it; + } + else + { + if ( bStrict ) + return false; + + // All unknown props are treated as: + rProp = beans::Property( + rPropName, + - 1, + cppu::UnoType<OUString>::get(), + beans::PropertyAttribute::BOUND ); + } + + return true; +} + + +// Content implementation. + + +// virtual +uno::Sequence< beans::Property > Content::getProperties( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + bool bTransient; + std::unique_ptr< DAVResourceAccess > xResAccess; + std::unique_ptr< ContentProperties > xCachedProps; + rtl::Reference< ContentProvider > xProvider; + + { + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + bTransient = m_bTransient; + xResAccess.reset( new DAVResourceAccess( *m_xResAccess ) ); + if ( m_xCachedProps.get() ) + xCachedProps.reset( + new ContentProperties( *m_xCachedProps ) ); + xProvider.set( m_pProvider ); + } + + std::set< OUString > aPropSet; + + // No server access for just created (not yet committed) objects. + // Only a minimal set of properties supported at this stage. + if ( !bTransient ) + { + // Obtain all properties supported for this resource from server. + try + { + std::vector< DAVResourceInfo > props; + xResAccess->PROPFIND( DAVZERO, props, xEnv ); + + // Note: vector always contains exactly one resource info, because + // we used a depth of DAVZERO for PROPFIND. + aPropSet.insert( (*props.begin()).properties.begin(), + (*props.begin()).properties.end() ); + } + catch ( DAVException const & ) + { + } + } + + // Add DAV properties, map DAV properties to UCB properties. + bool bHasCreationDate = false; // creationdate <-> DateCreated + bool bHasGetLastModified = false; // getlastmodified <-> DateModified + bool bHasGetContentType = false; // getcontenttype <-> MediaType + bool bHasGetContentLength = false; // getcontentlength <-> Size + + bool bHasContentType = false; + bool bHasIsDocument = false; + bool bHasIsFolder = false; + bool bHasTitle = false; + bool bHasBaseURI = false; + bool bHasDateCreated = false; + bool bHasDateModified = false; + bool bHasMediaType = false; + bool bHasSize = false; + bool bHasCreatableInfos = false; + + { + for ( const auto& rProp : aPropSet ) + { + if ( !bHasCreationDate && + ( rProp == DAVProperties::CREATIONDATE ) ) + { + bHasCreationDate = true; + } + else if ( !bHasGetLastModified && + ( rProp == DAVProperties::GETLASTMODIFIED ) ) + { + bHasGetLastModified = true; + } + else if ( !bHasGetContentType && + ( rProp == DAVProperties::GETCONTENTTYPE ) ) + { + bHasGetContentType = true; + } + else if ( !bHasGetContentLength && + ( rProp == DAVProperties::GETCONTENTLENGTH ) ) + { + bHasGetContentLength = true; + } + else if ( !bHasContentType && rProp == "ContentType" ) + { + bHasContentType = true; + } + else if ( !bHasIsDocument && rProp == "IsDocument" ) + { + bHasIsDocument = true; + } + else if ( !bHasIsFolder && rProp == "IsFolder" ) + { + bHasIsFolder = true; + } + else if ( !bHasTitle && rProp == "Title" ) + { + bHasTitle = true; + } + else if ( !bHasBaseURI && rProp == "BaseURI" ) + { + bHasBaseURI = true; + } + else if ( !bHasDateCreated && rProp == "DateCreated" ) + { + bHasDateCreated = true; + } + else if ( !bHasDateModified && rProp == "DateModified" ) + { + bHasDateModified = true; + } + else if ( !bHasMediaType && rProp == "MediaType" ) + { + bHasMediaType = true; + } + else if ( !bHasSize && rProp == "Size" ) + { + bHasSize = true; + } + else if ( !bHasCreatableInfos && rProp == "CreatableContentsInfo" ) + { + bHasCreatableInfos = true; + } + } + } + + // Add mandatory properties. + if ( !bHasContentType ) + aPropSet.insert( + OUString( "ContentType" ) ); + + if ( !bHasIsDocument ) + aPropSet.insert( + OUString( "IsDocument" ) ); + + if ( !bHasIsFolder ) + aPropSet.insert( + OUString( "IsFolder" ) ); + + if ( !bHasTitle ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "Title" ) ); + } + + // Add optional properties. + + if ( !bHasBaseURI ) + { + // Always present since it can be calculated from content's URI. + aPropSet.insert( + OUString( "BaseURI" ) ); + } + + if ( !bHasDateCreated && bHasCreationDate ) + aPropSet.insert( + OUString( "DateCreated" ) ); + + if ( !bHasDateModified && bHasGetLastModified ) + aPropSet.insert( + OUString( "DateModified" ) ); + + if ( !bHasMediaType && bHasGetContentType ) + aPropSet.insert( + OUString( "MediaType" ) ); + + if ( !bHasSize && bHasGetContentLength ) + aPropSet.insert( + OUString( "Size" ) ); + + if ( !bHasCreatableInfos ) + aPropSet.insert( + OUString( + "CreatableContentsInfo" ) ); + + // Add cached properties, if present and still missing. + if ( xCachedProps.get() ) + { + const std::unique_ptr< PropertyValueMap > & xProps + = xCachedProps->getProperties(); + + for ( const auto& rEntry : *xProps ) + aPropSet.insert( rEntry.first ); + } + + // std::set -> uno::Sequence + sal_Int32 nCount = aPropSet.size(); + uno::Sequence< beans::Property > aProperties( nCount ); + + beans::Property aProp; + sal_Int32 n = 0; + + for ( const auto& rProp : aPropSet ) + { + xProvider->getProperty( rProp, aProp ); + aProperties[ n++ ] = aProp; + } + + return aProperties; +} + + +// virtual +uno::Sequence< ucb::CommandInfo > Content::getCommands( + const uno::Reference< ucb::XCommandEnvironment > & xEnv ) +{ + osl::Guard< osl::Mutex > aGuard( m_aMutex ); + + uno::Sequence< ucb::CommandInfo > aCmdInfo( 10 ); + + + // Mandatory commands + + + aCmdInfo[ 0 ] = + ucb::CommandInfo( + "getCommandInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 1 ] = + ucb::CommandInfo( + "getPropertySetInfo", + -1, + cppu::UnoType<void>::get() ); + aCmdInfo[ 2 ] = + ucb::CommandInfo( + "getPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::Property >>::get()); + aCmdInfo[ 3 ] = + ucb::CommandInfo( + "setPropertyValues", + -1, + cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get()); + + + // Optional standard commands + + + aCmdInfo[ 4 ] = + ucb::CommandInfo( + "delete", + -1, + cppu::UnoType<bool>::get() ); + aCmdInfo[ 5 ] = + ucb::CommandInfo( + "insert", + -1, + cppu::UnoType<ucb::InsertCommandArgument>::get() ); + aCmdInfo[ 6 ] = + ucb::CommandInfo( + "open", + -1, + cppu::UnoType<ucb::OpenCommandArgument2>::get() ); + + + // New commands + + + aCmdInfo[ 7 ] = + ucb::CommandInfo( + "post", + -1, + cppu::UnoType<ucb::PostCommandArgument2>::get() ); + aCmdInfo[ 8 ] = + ucb::CommandInfo( + "addProperty", + -1, + cppu::UnoType<ucb::PropertyCommandArgument>::get() ); + aCmdInfo[ 9 ] = + ucb::CommandInfo( + "removeProperty", + -1, + cppu::UnoType<OUString>::get() ); + + bool bFolder = false; + + try + { + bFolder = isFolder( xEnv ); + } + catch ( uno::Exception const & ) + { + return aCmdInfo; + } + + bool bSupportsLocking = supportsExclusiveWriteLock( xEnv ); + + sal_Int32 nPos = aCmdInfo.getLength(); + sal_Int32 nMoreCmds = ( bFolder ? 2 : 0 ) + ( bSupportsLocking ? 2 : 0 ); + if ( nMoreCmds ) + aCmdInfo.realloc( nPos + nMoreCmds ); + else + return aCmdInfo; + + if ( bFolder ) + { + + // Optional standard commands + + + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "transfer", + -1, + cppu::UnoType<ucb::TransferInfo>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "createNewContent", + -1, + cppu::UnoType<ucb::ContentInfo>::get() ); + nPos++; + } + else + { + // no document-only commands at the moment. + } + + if ( bSupportsLocking ) + { + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "lock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + aCmdInfo[ nPos ] = + ucb::CommandInfo( + "unlock", + -1, + cppu::UnoType<void>::get() ); + nPos++; + } + return aCmdInfo; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavdatasupplier.cxx b/ucb/source/ucp/webdav/webdavdatasupplier.cxx new file mode 100644 index 000000000..6001b8665 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavdatasupplier.cxx @@ -0,0 +1,466 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> + +#include <memory> +#include <utility> + +#include <com/sun/star/ucb/OpenMode.hpp> +#include <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/providerhelper.hxx> +#include "webdavdatasupplier.hxx" +#include "webdavcontent.hxx" +#include "ContentProperties.hxx" +#include "DAVProperties.hxx" +#include "SerfUri.hxx" +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> +#include <com/sun/star/ucb/ResultSetException.hpp> + +using namespace com::sun::star; +using namespace http_dav_ucp; + +namespace http_dav_ucp +{ + + +// struct ResultListEntry. + +namespace { + +struct ResultListEntry +{ + OUString aId; + uno::Reference< ucb::XContentIdentifier > xId; + uno::Reference< ucb::XContent > xContent; + uno::Reference< sdbc::XRow > xRow; + std::unique_ptr<ContentProperties> pData; + + explicit ResultListEntry( std::unique_ptr<ContentProperties> && pEntry ) : pData( std::move(pEntry) ) {} +}; + +} + +// ResultList. + + +typedef std::vector< ResultListEntry* > ResultList; + + +// struct DataSupplier_Impl. + + +struct DataSupplier_Impl +{ + osl::Mutex m_aMutex; + ResultList m_aResults; + rtl::Reference< Content > m_xContent; + uno::Reference< uno::XComponentContext > m_xContext; + sal_Int32 m_nOpenMode; + bool m_bCountFinal; + bool m_bThrowException; + + DataSupplier_Impl( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) + : m_xContent( rContent ), m_xContext( rxContext ), m_nOpenMode( nOpenMode ), + m_bCountFinal( false ), m_bThrowException( false ) {} + ~DataSupplier_Impl(); +}; + + +DataSupplier_Impl::~DataSupplier_Impl() +{ + for ( auto& rResultPtr : m_aResults ) + { + delete rResultPtr; + } +} + +} + + +// DataSupplier Implementation. + + +DataSupplier::DataSupplier( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode ) +: m_pImpl(std::make_unique<DataSupplier_Impl>(rxContext, rContent, nOpenMode)) +{ +} + + +// virtual +DataSupplier::~DataSupplier() +{} + + +// virtual +OUString DataSupplier::queryContentIdentifierString( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + OUString aId = m_pImpl->m_aResults[ nIndex ]->aId; + if ( aId.getLength() ) + { + // Already cached. + return aId; + } + } + + if ( getResult( nIndex ) ) + { + OUString aId = m_pImpl->m_xContent->getResourceAccess().getURL(); + + const ContentProperties& props + = *( m_pImpl->m_aResults[ nIndex ]->pData ); + + if ( ( aId.lastIndexOf( '/' ) + 1 ) != aId.getLength() ) + aId += "/"; + + aId += props.getEscapedTitle(); + + if ( props.isTrailingSlash() ) + aId += "/"; + + m_pImpl->m_aResults[ nIndex ]->aId = aId; + return aId; + } + return OUString(); +} + + +// virtual +uno::Reference< ucb::XContentIdentifier > +DataSupplier::queryContentIdentifier( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = m_pImpl->m_aResults[ nIndex ]->xId; + if ( xId.is() ) + { + // Already cached. + return xId; + } + } + + OUString aId = queryContentIdentifierString( nIndex ); + if ( aId.getLength() ) + { + uno::Reference< ucb::XContentIdentifier > xId + = new ::ucbhelper::ContentIdentifier( aId ); + m_pImpl->m_aResults[ nIndex ]->xId = xId; + return xId; + } + return uno::Reference< ucb::XContentIdentifier >(); +} + + +// virtual +uno::Reference< ucb::XContent > +DataSupplier::queryContent( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->m_aResults[ nIndex ]->xContent; + if ( xContent.is() ) + { + // Already cached. + return xContent; + } + } + + uno::Reference< ucb::XContentIdentifier > xId + = queryContentIdentifier( nIndex ); + if ( xId.is() ) + { + try + { + uno::Reference< ucb::XContent > xContent + = m_pImpl->m_xContent->getProvider()->queryContent( xId ); + m_pImpl->m_aResults[ nIndex ]->xContent = xContent; + return xContent; + + } + catch ( ucb::IllegalIdentifierException& ) + { + } + } + return uno::Reference< ucb::XContent >(); +} + + +// virtual +bool DataSupplier::getResult( sal_uInt32 nIndex ) +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + + // Obtain values... + if ( getData() ) + { + if ( m_pImpl->m_aResults.size() > nIndex ) + { + // Result already present. + return true; + } + } + + return false; +} + + +// virtual +sal_uInt32 DataSupplier::totalCount() +{ + // Obtain values... + getData(); + + return m_pImpl->m_aResults.size(); +} + + +// virtual +sal_uInt32 DataSupplier::currentCount() +{ + return m_pImpl->m_aResults.size(); +} + + +// virtual +bool DataSupplier::isCountFinal() +{ + return m_pImpl->m_bCountFinal; +} + + +// virtual +uno::Reference< sdbc::XRow > DataSupplier::queryPropertyValues( + sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + { + uno::Reference< sdbc::XRow > xRow = m_pImpl->m_aResults[ nIndex ]->xRow; + if ( xRow.is() ) + { + // Already cached. + return xRow; + } + } + + if ( getResult( nIndex ) ) + { + uno::Reference< sdbc::XRow > xRow + = Content::getPropertyValues( + m_pImpl->m_xContext, + getResultSet()->getProperties(), + *(m_pImpl->m_aResults[ nIndex ]->pData), + rtl::Reference< ::ucbhelper::ContentProviderImplHelper >( + m_pImpl->m_xContent->getProvider().get() ), + queryContentIdentifierString( nIndex ) ); + m_pImpl->m_aResults[ nIndex ]->xRow = xRow; + return xRow; + } + + return uno::Reference< sdbc::XRow >(); +} + + +// virtual +void DataSupplier::releasePropertyValues( sal_uInt32 nIndex ) +{ + osl::Guard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( nIndex < m_pImpl->m_aResults.size() ) + m_pImpl->m_aResults[ nIndex ]->xRow.clear(); +} + + +// virtual +void DataSupplier::close() +{ +} + + +// virtual +void DataSupplier::validate() +{ + if ( m_pImpl->m_bThrowException ) + throw ucb::ResultSetException(); +} + +bool DataSupplier::getData() +{ + osl::ClearableGuard< osl::Mutex > aGuard( m_pImpl->m_aMutex ); + + if ( !m_pImpl->m_bCountFinal ) + { + std::vector< OUString > propertyNames; + ContentProperties::UCBNamesToDAVNames( + getResultSet()->getProperties(), propertyNames ); + + // Append "resourcetype", if not already present. It's value is + // needed to get a valid ContentProperties::pIsFolder value, which + // is needed for OpenMode handling. + + bool isNoResourceType = std::none_of(propertyNames.begin(), propertyNames.end(), + [](const OUString& rPropName) { return rPropName.equals(DAVProperties::RESOURCETYPE); }); + + if ( isNoResourceType ) + propertyNames.push_back( DAVProperties::RESOURCETYPE ); + + std::vector< DAVResource > resources; + try + { + // propfind depth 1, get property values for parent AND for each + // child + m_pImpl->m_xContent->getResourceAccess() + .PROPFIND( DAVONE, + propertyNames, + resources, + getResultSet()->getEnvironment() ); + } + catch ( DAVException & ) + { + SAL_WARN( "ucb.ucp.webdav", "PROPFIND : DAVException" ); + m_pImpl->m_bThrowException = true; + } + + if ( !m_pImpl->m_bThrowException ) + { + try + { + SerfUri aURI( + m_pImpl->m_xContent->getResourceAccess().getURL() ); + OUString aPath = aURI.GetPath(); + + if ( aPath.endsWith("/") ) + aPath = aPath.copy( 0, aPath.getLength() - 1 ); + + aPath = SerfUri::unescape( aPath ); + bool bFoundParent = false; + + for ( size_t n = 0; n < resources.size(); ++n ) + { + const DAVResource & rRes = resources[ n ]; + + // Filter parent, which is contained somewhere(!) in + // the vector. + if ( !bFoundParent ) + { + try + { + SerfUri aCurrURI( rRes.uri ); + OUString aCurrPath = aCurrURI.GetPath(); + if ( aCurrPath.endsWith("/") ) + aCurrPath + = aCurrPath.copy( + 0, + aCurrPath.getLength() - 1 ); + + aCurrPath = SerfUri::unescape( aCurrPath ); + if ( aPath == aCurrPath ) + { + bFoundParent = true; + continue; + } + } + catch ( DAVException const & ) + { + // do nothing, ignore error. continue. + } + } + + std::unique_ptr<ContentProperties> pContentProperties + = std::make_unique<ContentProperties>( rRes ); + + // Check resource against open mode. + switch ( m_pImpl->m_nOpenMode ) + { + case ucb::OpenMode::FOLDERS: + { + bool bFolder = false; + + const uno::Any & rValue + = pContentProperties->getValue( "IsFolder" ); + rValue >>= bFolder; + + if ( !bFolder ) + continue; + + break; + } + + case ucb::OpenMode::DOCUMENTS: + { + bool bDocument = false; + + const uno::Any & rValue + = pContentProperties->getValue( "IsDocument" ); + rValue >>= bDocument; + + if ( !bDocument ) + continue; + + break; + } + + case ucb::OpenMode::ALL: + default: + break; + } + + m_pImpl->m_aResults.push_back( + new ResultListEntry( std::move(pContentProperties) ) ); + } + } + catch ( DAVException const & ) + { + } + } + + m_pImpl->m_bCountFinal = true; + + // Callback possible, because listeners may be informed! + aGuard.clear(); + getResultSet()->rowCountFinal(); + } + return !m_pImpl->m_bThrowException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavdatasupplier.hxx b/ucb/source/ucp/webdav/webdavdatasupplier.hxx new file mode 100644 index 000000000..ecd22000e --- /dev/null +++ b/ucb/source/ucp/webdav/webdavdatasupplier.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVDATASUPPLIER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVDATASUPPLIER_HXX + +#include <sal/config.h> + +#include <memory> +#include <rtl/ref.hxx> +#include <ucbhelper/resultset.hxx> + +namespace http_dav_ucp { + +struct DataSupplier_Impl; +class Content; +struct DAVResource; +class ContentProperties; + +class DataSupplier : public ucbhelper::ResultSetDataSupplier +{ + std::unique_ptr<DataSupplier_Impl> m_pImpl; + +private: + bool getData(); + +public: + DataSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rContent, + sal_Int32 nOpenMode); + + virtual ~DataSupplier() override; + + virtual OUString queryContentIdentifierString( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContentIdentifier > + queryContentIdentifier( sal_uInt32 nIndex ) override; + virtual css::uno::Reference< css::ucb::XContent > + queryContent( sal_uInt32 nIndex ) override; + + virtual bool getResult( sal_uInt32 nIndex ) override; + + virtual sal_uInt32 totalCount() override; + virtual sal_uInt32 currentCount() override; + virtual bool isCountFinal() override; + + virtual css::uno::Reference< css::sdbc::XRow > + queryPropertyValues( sal_uInt32 nIndex ) override; + virtual void releasePropertyValues( sal_uInt32 nIndex ) override; + + virtual void close() override; + + virtual void validate() override; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavprovider.cxx b/ucb/source/ucp/webdav/webdavprovider.cxx new file mode 100644 index 000000000..6c1d77b94 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavprovider.cxx @@ -0,0 +1,176 @@ +/* -*- 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 <ucbhelper/contentidentifier.hxx> +#include <ucbhelper/getcomponentcontext.hxx> +#include <ucbhelper/macros.hxx> +#include "webdavprovider.hxx" +#include "webdavcontent.hxx" + +#include <cppuhelper/queryinterface.hxx> +#include <osl/mutex.hxx> +#include <com/sun/star/ucb/IllegalIdentifierException.hpp> + +#include <tools/urlobj.hxx> + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// ContentProvider Implementation. + + +ContentProvider::ContentProvider( + const uno::Reference< uno::XComponentContext >& rContext ) +: ::ucbhelper::ContentProviderImplHelper( rContext ), + m_xDAVSessionFactory( new DAVSessionFactory ) +{ +} + + +// virtual +ContentProvider::~ContentProvider() +{} + + +// XInterface methods. +void SAL_CALL ContentProvider::acquire() + throw() +{ + OWeakObject::acquire(); +} + +void SAL_CALL ContentProvider::release() + throw() +{ + OWeakObject::release(); +} + +css::uno::Any SAL_CALL ContentProvider::queryInterface( const css::uno::Type & rType ) +{ + css::uno::Any aRet = cppu::queryInterface( rType, + static_cast< lang::XTypeProvider* >(this), + static_cast< lang::XServiceInfo* >(this), + static_cast< ucb::XContentProvider* >(this) + ); + return aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType ); +} + +// XTypeProvider methods. + + +XTYPEPROVIDER_IMPL_3( ContentProvider, + lang::XTypeProvider, + lang::XServiceInfo, + ucb::XContentProvider ); + + +// XServiceInfo methods. + +XSERVICEINFO_COMMOM_IMPL( ContentProvider, + "com.sun.star.comp.WebDAVContentProvider" ) +/// @throws css::uno::Exception +static css::uno::Reference< css::uno::XInterface > +ContentProvider_CreateInstance( const css::uno::Reference< css::lang::XMultiServiceFactory> & rSMgr ) +{ + css::lang::XServiceInfo* pX = + static_cast<css::lang::XServiceInfo*>(new ContentProvider( ucbhelper::getComponentContext(rSMgr) )); + return css::uno::Reference< css::uno::XInterface >::query( pX ); +} + +css::uno::Sequence< OUString > +ContentProvider::getSupportedServiceNames_Static() +{ + css::uno::Sequence< OUString > aSNS { WEBDAV_CONTENT_PROVIDER_SERVICE_NAME }; + return aSNS; +} + +// Service factory implementation. + + +ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider ); + + +// XContentProvider methods. + + +// virtual +uno::Reference< ucb::XContent > SAL_CALL +ContentProvider::queryContent( + const uno::Reference< + ucb::XContentIdentifier >& Identifier ) +{ + // Check URL scheme... + INetURLObject aURL(Identifier->getContentIdentifier()); + + if (aURL.isSchemeEqualTo(INetProtocol::NotValid)) + throw ucb::IllegalIdentifierException(); + + if (!aURL.isAnyKnownWebDAVScheme()) + throw ucb::IllegalIdentifierException(); + + uno::Reference< ucb::XContentIdentifier > xCanonicId; + + if (aURL.isSchemeEqualTo(INetProtocol::VndSunStarWebdav) || + aURL.isSchemeEqualTo(DAV_URL_SCHEME) || + aURL.isSchemeEqualTo(WEBDAV_URL_SCHEME)) + { + aURL.changeScheme(INetProtocol::Http); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else if (aURL.isSchemeEqualTo(VNDSUNSTARWEBDAVS_URL_SCHEME) || + aURL.isSchemeEqualTo(DAVS_URL_SCHEME) || + aURL.isSchemeEqualTo(WEBDAVS_URL_SCHEME)) + { + aURL.changeScheme(INetProtocol::Https); + xCanonicId = new ::ucbhelper::ContentIdentifier( aURL.getExternalURL() ); + } + else + { + xCanonicId = Identifier; + } + + osl::MutexGuard aGuard( m_aMutex ); + + // Check, if a content with given id already exists... + uno::Reference< ucb::XContent > xContent + = queryExistingContent( xCanonicId ).get(); + if ( xContent.is() ) + return xContent; + + // Create a new content. + + try + { + xContent = new ::http_dav_ucp::Content( + m_xContext, this, xCanonicId, m_xDAVSessionFactory ); + registerNewContent( xContent ); + } + catch ( ucb::ContentCreationException const & ) + { + throw ucb::IllegalIdentifierException(); + } + + if ( !xContent->getIdentifier().is() ) + throw ucb::IllegalIdentifierException(); + + return xContent; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavprovider.hxx b/ucb/source/ucp/webdav/webdavprovider.hxx new file mode 100644 index 000000000..e399178a9 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavprovider.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVPROVIDER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVPROVIDER_HXX + +#include <sal/config.h> + +#include <memory> + +#include <rtl/ref.hxx> +#include <com/sun/star/beans/Property.hpp> +#include "DAVSessionFactory.hxx" +#include <ucbhelper/providerhelper.hxx> +#include "PropertyMap.hxx" + +namespace com::sun::star::lang { +class XSingleServiceFactory; +} + +namespace http_dav_ucp { + + +// UNO service name for the provider. This name will be used by the UCB to +// create instances of the provider. +#define WEBDAV_CONTENT_PROVIDER_SERVICE_NAME "com.sun.star.ucb.WebDAVContentProvider" + +// URL scheme. This is the scheme the provider will be able to create +// contents for. The UCB will select the provider ( i.e. in order to create +// contents ) according to this scheme. +#define VNDSUNSTARWEBDAV_URL_SCHEME "vnd.sun.star.webdav" +#define VNDSUNSTARWEBDAVS_URL_SCHEME u"vnd.sun.star.webdavs" +#define HTTP_URL_SCHEME "http" +#define HTTPS_URL_SCHEME "https" +#define DAV_URL_SCHEME u"dav" +#define DAVS_URL_SCHEME u"davs" +#define WEBDAV_URL_SCHEME u"webdav" +#define WEBDAVS_URL_SCHEME u"webdavs" + +#define HTTP_CONTENT_TYPE "application/" HTTP_URL_SCHEME "-content" + +#define WEBDAV_CONTENT_TYPE HTTP_CONTENT_TYPE +#define WEBDAV_COLLECTION_TYPE "application/" VNDSUNSTARWEBDAV_URL_SCHEME "-collection" + + +class ContentProvider : public ::ucbhelper::ContentProviderImplHelper +{ + rtl::Reference< DAVSessionFactory > m_xDAVSessionFactory; + std::unique_ptr<PropertyMap> m_pProps; + +public: + explicit ContentProvider( const css::uno::Reference< css::uno::XComponentContext >& rContext ); + virtual ~ContentProvider() override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() + throw() override; + virtual void SAL_CALL release() + throw() override; + + // XTypeProvider + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override; + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + static OUString getImplementationName_Static(); + static css::uno::Sequence< OUString > getSupportedServiceNames_Static(); + + static css::uno::Reference< css::lang::XSingleServiceFactory > + createServiceFactory( const css::uno::Reference< + css::lang::XMultiServiceFactory >& rxServiceMgr ); + + // XContentProvider + virtual css::uno::Reference< css::ucb::XContent > SAL_CALL + queryContent( const css::uno::Reference< css::ucb::XContentIdentifier >& Identifier ) override; + + + // Non-interface methods. + + bool getProperty( const OUString & rPropName, + css::beans::Property & rProp, + bool bStrict = false ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresponseparser.cxx b/ucb/source/ucp/webdav/webdavresponseparser.cxx new file mode 100644 index 000000000..b03a392d5 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresponseparser.cxx @@ -0,0 +1,897 @@ +/* -*- 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 "webdavresponseparser.hxx" +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/ucb/LockEntry.hpp> +#include <com/sun/star/ucb/LockScope.hpp> +#include <com/sun/star/ucb/LockType.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include <map> +#include <unordered_map> +#include <sal/log.hxx> + +using namespace com::sun::star; + + +// WebDAVNamespace enum and StringToEnum converter +namespace +{ + enum WebDAVNamespace + { + WebDAVNamespace_unknown = 0, + WebDAVNamespace_DAV, + WebDAVNamespace_ucb_openoffice_org_dav_props, + + WebDAVNamespace_last + }; + + WebDAVNamespace StrToWebDAVNamespace(const OUString& rStr) + { + if(rStr == "DAV:") + { + return WebDAVNamespace_DAV; + } + else if(rStr == "http://ucb.openoffice.org/dav/props/") + { + return WebDAVNamespace_ucb_openoffice_org_dav_props; + } + + return WebDAVNamespace_unknown; + } +} // end of anonymous namespace + +// WebDAVName enum and StringToEnum converter using unordered_map +namespace +{ + enum WebDAVName + { + WebDAVName_unknown = 0, + WebDAVName_activelock, + WebDAVName_multistatus, + WebDAVName_response, + WebDAVName_href, + WebDAVName_propstat, + WebDAVName_prop, + WebDAVName_resourcetype, + WebDAVName_collection, + WebDAVName_getcontenttype, + WebDAVName_supportedlock, + WebDAVName_lockentry, + WebDAVName_lockscope, + WebDAVName_locktoken, + WebDAVName_exclusive, + WebDAVName_locktype, + WebDAVName_owner, + WebDAVName_timeout, + WebDAVName_write, + WebDAVName_shared, + WebDAVName_status, + WebDAVName_getlastmodified, + WebDAVName_creationdate, + WebDAVName_getcontentlength, + + WebDAVName_last + }; + + WebDAVName StrToWebDAVName(const OUString& rStr) + { + typedef std::unordered_map< OUString, WebDAVName > WebDAVNameMapper; + typedef std::pair< OUString, WebDAVName > WebDAVNameValueType; + static WebDAVNameMapper aWebDAVNameMapperList; + + if(aWebDAVNameMapperList.empty()) + { + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("activelock"), WebDAVName_activelock)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("multistatus"), WebDAVName_multistatus)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("response"), WebDAVName_response)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("href"), WebDAVName_href)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("propstat"), WebDAVName_propstat)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("prop"), WebDAVName_prop)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("resourcetype"), WebDAVName_resourcetype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("collection"), WebDAVName_collection)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontenttype"), WebDAVName_getcontenttype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("supportedlock"), WebDAVName_supportedlock)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockentry"), WebDAVName_lockentry)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("lockscope"), WebDAVName_lockscope)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktoken"), WebDAVName_locktoken)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("exclusive"), WebDAVName_exclusive)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("locktype"), WebDAVName_locktype)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("owner"), WebDAVName_owner)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("timeout"), WebDAVName_timeout)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("write"), WebDAVName_write)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("shared"), WebDAVName_shared)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("status"), WebDAVName_status)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getlastmodified"), WebDAVName_getlastmodified)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("creationdate"), WebDAVName_creationdate)); + aWebDAVNameMapperList.insert(WebDAVNameValueType(OUString("getcontentlength"), WebDAVName_getcontentlength)); + } + + const WebDAVNameMapper::const_iterator aResult(aWebDAVNameMapperList.find(rStr)); + + if(aResult == aWebDAVNameMapperList.end()) + { + return WebDAVName_unknown; + } + else + { + return aResult->second; + } + } +} // end of anonymous namespace + + +// WebDAVContext, holding information for each start/endElement pair + +namespace +{ + typedef std::map< OUString, OUString > NamespaceMap; + + class WebDAVContext + { + private: + WebDAVContext* mpParent; + NamespaceMap maNamespaceMap; + OUString maWhiteSpace; + + OUString maNamespace; + OUString maName; + + WebDAVNamespace maWebDAVNamespace; + WebDAVName maWebDAVName; + + // local helpers + void parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs); + OUString mapNamespaceToken(const OUString& rToken) const; + void splitName(const OUString& rSource); + + public: + WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs); + + WebDAVContext* getParent() const { return mpParent; } + OUString& getWhiteSpace() { return maWhiteSpace; } + void setWhiteSpace(const OUString& rNew) { maWhiteSpace = rNew; } + + const OUString& getNamespace() const { return maNamespace; } + const OUString& getName() const { return maName; } + const WebDAVNamespace& getWebDAVNamespace() const { return maWebDAVNamespace; } + const WebDAVName& getWebDAVName() const { return maWebDAVName; } + }; + + void WebDAVContext::parseForNamespaceTokens(const uno::Reference< xml::sax::XAttributeList >& xAttribs) + { + const sal_Int16 nAttributes(xAttribs->getLength()); + + for(sal_Int16 a(0); a < nAttributes; a++) + { + const OUString aName(xAttribs->getNameByIndex(a)); + const sal_Int32 nLen(aName.getLength()); + + if(nLen) + { + if(aName.startsWith("xmlns")) + { + const sal_Int32 nIndex(aName.indexOf(':', 0)); + + if(-1 != nIndex && nIndex + 1 < nLen) + { + const OUString aToken(aName.copy(nIndex + 1)); + + maNamespaceMap.emplace(aToken, xAttribs->getValueByIndex(a)); + } + } + } + } + } + + OUString WebDAVContext::mapNamespaceToken(const OUString& rToken) const + { + NamespaceMap::const_iterator iter = maNamespaceMap.find(rToken); + + if(maNamespaceMap.end() == iter) + { + if(getParent()) + { + return getParent()->mapNamespaceToken(rToken); + } + else + { + return rToken; + } + } + else + { + return (*iter).second; + } + } + + void WebDAVContext::splitName(const OUString& rSource) + { + const sal_Int32 nLen(rSource.getLength()); + maNamespace.clear(); + maName = rSource; + + if(nLen) + { + const sal_Int32 nIndex(rSource.indexOf(':', 0)); + + if(nIndex > 0 && ((nIndex + 1) < nLen)) + { + maNamespace = mapNamespaceToken(rSource.copy(0, nIndex)); + maName = rSource.copy(nIndex + 1); + } + } + } + + WebDAVContext::WebDAVContext(WebDAVContext* pParent, const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs) + : mpParent(pParent), + maNamespaceMap(), + maWhiteSpace(), + maNamespace(), + maName(), + maWebDAVNamespace(WebDAVNamespace_unknown), + maWebDAVName(WebDAVName_unknown) + { + const sal_Int16 nAttributes(xAttribs->getLength()); + + if(nAttributes) + { + // parse evtl. namespace entries + parseForNamespaceTokens(xAttribs); + } + + // split name to namespace and name + splitName(aName); + + // evaluate enums for namespace and name + maWebDAVNamespace = StrToWebDAVNamespace(maNamespace); + maWebDAVName = StrToWebDAVName(maName); + } +} // end of anonymous namespace + + +// the Xml parser itself + +namespace +{ + enum WebDAVResponseParserMode + { + WebDAVResponseParserMode_PropFind = 0, + WebDAVResponseParserMode_PropName, + WebDAVResponseParserMode_Lock + }; + + class WebDAVResponseParser : public cppu::WeakImplHelper< css::xml::sax::XDocumentHandler > + { + private: + std::vector< ucb::Lock > maResult_Lock; + std::vector< http_dav_ucp::DAVResource > maResult_PropFind; + std::vector< http_dav_ucp::DAVResourceInfo > maResult_PropName; + + WebDAVContext* mpContext; + OUString maHref; + OUString maStatus; + std::vector< http_dav_ucp::DAVPropertyValue > maResponseProperties; + std::vector< http_dav_ucp::DAVPropertyValue > maPropStatProperties; + std::vector< OUString > maResponseNames; + std::vector< OUString > maPropStatNames; + uno::Sequence< ucb::LockEntry > maLockEntries; + ucb::LockScope maLockScope; + ucb::LockType maLockType; + ucb::Lock maLock; + WebDAVResponseParserMode meWebDAVResponseParserMode; + + bool mbResourceTypeCollection : 1; + bool mbLockScopeSet : 1; + bool mbLockTypeSet : 1; + + // local helpers + bool whitespaceIsAvailable() const + { + return mpContext && mpContext->getWhiteSpace().getLength(); + } + bool hasParent(WebDAVName aWebDAVName) const + { + return mpContext && mpContext->getParent() && aWebDAVName == mpContext->getParent()->getWebDAVName(); + } + bool propertyIsReady() const + { + return hasParent(WebDAVName_prop) && whitespaceIsAvailable(); + } + bool isCollectingProperties() const + { + return WebDAVResponseParserMode_PropFind == meWebDAVResponseParserMode; + } + bool isCollectingPropNames() const + { + return WebDAVResponseParserMode_PropName == meWebDAVResponseParserMode; + } + bool collectThisPropertyAsName() const + { + return isCollectingPropNames() && hasParent(WebDAVName_prop); + } + void pop_context() + { + if(mpContext) + { + WebDAVContext* pTemp = mpContext; + mpContext = mpContext->getParent(); + delete pTemp; + } + else + { + SAL_WARN( "ucb.ucp.webdav", "Parser context pop without context (!)"); + } + } + + public: + explicit WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode); + virtual ~WebDAVResponseParser() override; + + // Methods XDocumentHandler + virtual void SAL_CALL startDocument( ) override; + virtual void SAL_CALL endDocument( ) override; + virtual void SAL_CALL startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) override; + virtual void SAL_CALL endElement( const OUString& aName ) override; + virtual void SAL_CALL characters( const OUString& aChars ) override; + virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override; + virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override; + virtual void SAL_CALL setDocumentLocator( const uno::Reference< xml::sax::XLocator >& xLocator ) override; + + const std::vector< ucb::Lock >& getResult_Lock() const { return maResult_Lock; } + const std::vector< http_dav_ucp::DAVResource >& getResult_PropFind() const { return maResult_PropFind; } + const std::vector< http_dav_ucp::DAVResourceInfo >& getResult_PropName() const { return maResult_PropName; } + }; + + WebDAVResponseParser::WebDAVResponseParser(WebDAVResponseParserMode eWebDAVResponseParserMode) + : maResult_PropFind(), + maResult_PropName(), + mpContext(nullptr), + maHref(), + maStatus(), + maResponseProperties(), + maPropStatProperties(), + maResponseNames(), + maPropStatNames(), + maLockEntries(), + maLockScope(ucb::LockScope_EXCLUSIVE), + maLockType(ucb::LockType_WRITE), + meWebDAVResponseParserMode(eWebDAVResponseParserMode), + mbResourceTypeCollection(false), + mbLockScopeSet(false), + mbLockTypeSet(false) + { + } + + WebDAVResponseParser::~WebDAVResponseParser() + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser destructed with existing content (!)"); + while(mpContext) + { + pop_context(); + } + } + + void SAL_CALL WebDAVResponseParser::startDocument( ) + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser start with existing content (!)"); + } + + void SAL_CALL WebDAVResponseParser::endDocument( ) + { + SAL_WARN_IF(mpContext, "ucb.ucp.webdav", "Parser end with existing content (!)"); + } + + void SAL_CALL WebDAVResponseParser::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) + { + const sal_Int32 nLen(aName.getLength()); + + if(nLen) + { + // create new context (push) + mpContext = new WebDAVContext(mpContext, aName, xAttribs); + + if(collectThisPropertyAsName()) + { + // When collecting property names and parent is prop there is no need + // to handle the content of this property deeper (evtl. preparations) + } + else + { + switch(mpContext->getWebDAVNamespace()) + { + default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled + { + break; + } + case WebDAVNamespace_DAV: + { + switch(mpContext->getWebDAVName()) + { + default: // WebDAVName_unknown, WebDAVName_last or unhandled + { + break; + } + case WebDAVName_propstat: + { + // propstat start + if(isCollectingProperties()) + { + // reset maPropStatProperties + maPropStatProperties.clear(); + } + else + { + // when collecting properties reset maPropStatNames + maPropStatNames.clear(); + } + break; + } + case WebDAVName_response: + { + // response start, reset Href and status and maResponseProperties + maHref.clear(); + maStatus.clear(); + + if(isCollectingProperties()) + { + // reset maResponseProperties + maResponseProperties.clear(); + } + else + { + // reset maResponseNames when collecting properties + maResponseNames.clear(); + } + break; + } + case WebDAVName_resourcetype: + { + // resourcetype start, reset collection + mbResourceTypeCollection = false; + break; + } + case WebDAVName_supportedlock: + { + // supportedlock start, reset maLockEntries + maLockEntries.realloc(0); + break; + } + case WebDAVName_lockentry: + { + // lockentry start, reset maLockEntries + mbLockScopeSet = false; + mbLockTypeSet = false; + break; + } + case WebDAVName_activelock: + { + maLock = ucb::Lock(); + break; + } + } + break; + } + case WebDAVNamespace_ucb_openoffice_org_dav_props: + { + break; + } + } + } + } + } + + void SAL_CALL WebDAVResponseParser::endElement( const OUString& aName ) + { + const sal_Int32 nLen(aName.getLength()); + SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser EndElement without content (!)"); + + if(mpContext && nLen) + { + if(collectThisPropertyAsName()) + { + // When collecting property names and parent is prop, just append the prop name + // to the collection, no need to parse deeper + maPropStatNames.push_back(mpContext->getNamespace() + mpContext->getName()); + } + else + { + switch(mpContext->getWebDAVNamespace()) + { + default: // WebDAVNamespace_unknown, WebDAVNamespace_last or unhandled + { + break; + } + case WebDAVNamespace_DAV: + { + switch(mpContext->getWebDAVName()) + { + default: // WebDAVName_unknown, WebDAVName_last or unhandled + { + break; + } + case WebDAVName_href: + { + // href end, save it if we have whitespace + if(whitespaceIsAvailable()) + { + maHref = mpContext->getWhiteSpace(); + } + break; + } + case WebDAVName_status: + { + // status end, save it if we have whitespace + if(whitespaceIsAvailable()) + { + maStatus = mpContext->getWhiteSpace(); + } + break; + } + case WebDAVName_getlastmodified: + { + // getlastmodified end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getlastmodified"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_creationdate: + { + // creationdate end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:creationdate"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_collection: + { + // collection end, check and set + if(hasParent(WebDAVName_resourcetype)) + { + mbResourceTypeCollection = true; + } + break; + } + case WebDAVName_resourcetype: + { + // resourcetype end, check for collection + if(hasParent(WebDAVName_prop)) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:resourcetype"; + aDAVPropertyValue.Value <<= (mbResourceTypeCollection ? OUString("collection") : OUString()); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_getcontentlength: + { + // getcontentlength end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getcontentlength"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_getcontenttype: + { + // getcontenttype end, safe if content is correct + if(propertyIsReady()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:getcontenttype"; + aDAVPropertyValue.Value <<= mpContext->getWhiteSpace(); + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_supportedlock: + { + // supportedlock end + if(hasParent(WebDAVName_prop) && maLockEntries.hasElements()) + { + http_dav_ucp::DAVPropertyValue aDAVPropertyValue; + + aDAVPropertyValue.Name = "DAV:supportedlock"; + aDAVPropertyValue.Value <<= maLockEntries; + maPropStatProperties.push_back(aDAVPropertyValue); + } + break; + } + case WebDAVName_lockentry: + { + // lockentry end + if(hasParent(WebDAVName_supportedlock) && (mbLockScopeSet && mbLockTypeSet)) + { + const sal_Int32 nLength(maLockEntries.getLength()); + ucb::LockEntry aEntry; + + aEntry.Scope = maLockScope; + aEntry.Type = maLockType; + maLockEntries.realloc(nLength + 1); + maLockEntries[nLength] = aEntry; + } + break; + } + case WebDAVName_owner: + { + maLock.Owner <<= mpContext->getWhiteSpace(); + break; + } + case WebDAVName_timeout: + { + const OUString sTimeout(mpContext->getWhiteSpace()); + if (sTimeout == "Infinite") + maLock.Timeout = -1; + else if (sTimeout.startsWith("Second-")) + maLock.Timeout = sTimeout.copy(7).toInt64(); + break; + } + case WebDAVName_locktoken: + { + const OUString sLockToken(maHref); + SAL_WARN_IF(!sLockToken.startsWith("opaquelocktoken:"), "ucb.ucp.webdav", + "Parser error: wrong 'locktoken' value."); + const sal_Int32 nLength(maLock.LockTokens.getLength()); + maLock.LockTokens.realloc(nLength+1); + maLock.LockTokens[nLength] = sLockToken; + break; + } + case WebDAVName_exclusive: + { + // exclusive lockscope end + if(hasParent(WebDAVName_lockscope)) + { + maLockScope = ucb::LockScope_EXCLUSIVE; + mbLockScopeSet = true; + } + break; + } + case WebDAVName_shared: + { + // shared lockscope end + if(hasParent(WebDAVName_lockscope)) + { + maLockScope = ucb::LockScope_SHARED; + mbLockScopeSet = true; + } + break; + } + case WebDAVName_write: + { + // write locktype end + if(hasParent(WebDAVName_locktype)) + { + maLockType = ucb::LockType_WRITE; + mbLockTypeSet = true; + } + break; + } + case WebDAVName_activelock: + { + maLock.Type = maLockType; + maLock.Scope = maLockScope; + maResult_Lock.push_back(maLock); + } + [[fallthrough]]; // I hope intentional? + case WebDAVName_propstat: + { + // propstat end, check status + if(maStatus.getLength()) + { + if(maStatus == "HTTP/1.1 200 OK") + { + if(isCollectingProperties()) + { + if(!maPropStatProperties.empty()) + { + // append to maResponseProperties if okay + maResponseProperties.insert(maResponseProperties.end(), maPropStatProperties.begin(), maPropStatProperties.end()); + } + } + else + { + if(!maPropStatNames.empty()) + { + // when collecting properties append to + maResponseNames.insert(maResponseNames.end(), maPropStatNames.begin(), maPropStatNames.end()); + } + } + } + } + break; + } + case WebDAVName_response: + { + // response end + if(maHref.getLength()) + { + if(isCollectingProperties()) + { + // create DAVResource when we have content + if(!maResponseProperties.empty()) + { + http_dav_ucp::DAVResource aDAVResource; + + aDAVResource.uri = maHref; + aDAVResource.properties = maResponseProperties; + maResult_PropFind.push_back(aDAVResource); + } + } + else + { + // when collecting properties add them to result when there are some + if(!maResponseNames.empty()) + { + http_dav_ucp::DAVResourceInfo aDAVResourceInfo(maHref); + + aDAVResourceInfo.properties = maResponseNames; + maResult_PropName.push_back(aDAVResourceInfo); + } + } + } + break; + } + } + break; + } + case WebDAVNamespace_ucb_openoffice_org_dav_props: + { + break; + } + } + } + + // destroy last context (pop) + pop_context(); + } + } + + void SAL_CALL WebDAVResponseParser::characters( const OUString& aChars ) + { + // collect whitespace over evtl. several calls in mpContext + SAL_WARN_IF(!mpContext, "ucb.ucp.webdav", "Parser characters without content (!)"); + const sal_Int32 nLen(aChars.getLength()); + + if(mpContext && nLen) + { + // remove leading/trailing blanks and CRLF + const OUString aTrimmedChars(aChars.trim()); + + if(aTrimmedChars.getLength()) + { + OUString aNew(mpContext->getWhiteSpace()); + + if(aNew.getLength()) + { + // add one char when appending (see html1.1 spec) + aNew += " "; + } + + aNew += aTrimmedChars; + mpContext->setWhiteSpace(aNew); + } + } + } + + void SAL_CALL WebDAVResponseParser::ignorableWhitespace( const OUString& /*aWhitespaces*/ ) + { + } + + void SAL_CALL WebDAVResponseParser::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ ) + { + } + + void SAL_CALL WebDAVResponseParser::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& /*xLocator*/ ) + { + } +} // end of anonymous namespace + + +// wrapper for various calls to the parser + +namespace +{ + template<typename T> + void parseWebDAVResponse( + const uno::Reference< io::XInputStream >& xInputStream, + std::vector< T >& rResult, + WebDAVResponseParserMode eWebDAVResponseParserMode, + std::vector<T> const & (WebDAVResponseParser::* fn)() const) + { + if(xInputStream.is()) + { + try + { + // prepare ParserInputSrouce + xml::sax::InputSource myInputSource; + myInputSource.aInputStream = xInputStream; + + // get parser + uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create( + comphelper::getProcessComponentContext() ); + + // create parser; connect parser and filter + WebDAVResponseParser* pWebDAVResponseParser = new WebDAVResponseParser(eWebDAVResponseParserMode); + uno::Reference< xml::sax::XDocumentHandler > xWebDAVHdl(pWebDAVResponseParser); + xParser->setDocumentHandler(xWebDAVHdl); + + // finally, parse the stream + xParser->parseStream(myInputSource); + + // get result + rResult = (pWebDAVResponseParser->*fn)(); + } + catch(uno::Exception&) + { + SAL_WARN("ucb.ucp.webdav", "WebDAV Parse error (!)"); + } + } + } +} // end of anonymous namespace + + +// helper to parse a XML WebDAV response + +namespace http_dav_ucp +{ + std::vector< ucb::Lock > parseWebDAVLockResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< ucb::Lock > aResult; + parseWebDAVResponse< ucb::Lock >(xInputStream, aResult, WebDAVResponseParserMode_Lock, &WebDAVResponseParser::getResult_Lock); + return aResult; + } + + std::vector< DAVResource > parseWebDAVPropFindResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< DAVResource > aResult; + parseWebDAVResponse< DAVResource >(xInputStream, aResult, WebDAVResponseParserMode_PropFind, &WebDAVResponseParser::getResult_PropFind); + return aResult; + } + + std::vector< DAVResourceInfo > parseWebDAVPropNameResponse(const uno::Reference< io::XInputStream >& xInputStream) + { + std::vector< DAVResourceInfo > aResult; + parseWebDAVResponse< DAVResourceInfo >(xInputStream, aResult, WebDAVResponseParserMode_PropName, &WebDAVResponseParser::getResult_PropName); + return aResult; + } +} // namespace http_dav_ucp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresponseparser.hxx b/ucb/source/ucp/webdav/webdavresponseparser.hxx new file mode 100644 index 000000000..546d44dfb --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresponseparser.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/ucb/Lock.hpp> +#include "DAVResource.hxx" +#include <vector> + + +namespace http_dav_ucp +{ + std::vector< css::ucb::Lock > parseWebDAVLockResponse(const css::uno::Reference< css::io::XInputStream >& xInputStream); + std::vector< DAVResource > parseWebDAVPropFindResponse(const css::uno::Reference< css::io::XInputStream >& xInputStream); + std::vector< DAVResourceInfo > parseWebDAVPropNameResponse(const css::uno::Reference< css::io::XInputStream >& xInputStream); +} // namespace http_dav_ucp + + +#endif // INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESPONSEPARSER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresultset.cxx b/ucb/source/ucp/webdav/webdavresultset.cxx new file mode 100644 index 000000000..e67dd1558 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresultset.cxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +/************************************************************************** + TODO + ************************************************************************** + + - This implementation is not a dynamic result set!!! It only implements + the necessary interfaces, but never recognizes/notifies changes!!! + + *************************************************************************/ +#include "webdavresultset.hxx" + +using namespace com::sun::star; +using namespace http_dav_ucp; + + +// DynamicResultSet Implementation. + + +DynamicResultSet::DynamicResultSet( + const uno::Reference< uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const ucb::OpenCommandArgument2& rCommand, + const uno::Reference< ucb::XCommandEnvironment >& rxEnv ) +: ResultSetImplHelper( rxContext, rCommand ), + m_xContent( rxContent ), + m_xEnv( rxEnv ) +{ +} + + +// Non-interface methods. + + +void DynamicResultSet::initStatic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); +} + + +void DynamicResultSet::initDynamic() +{ + m_xResultSet1 + = new ::ucbhelper::ResultSet( m_xContext, + m_aCommand.Properties, + new DataSupplier( m_xContext, + m_xContent, + m_aCommand.Mode ), + m_xEnv ); + m_xResultSet2 = m_xResultSet1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavresultset.hxx b/ucb/source/ucp/webdav/webdavresultset.hxx new file mode 100644 index 000000000..cb1b7c0f5 --- /dev/null +++ b/ucb/source/ucp/webdav/webdavresultset.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ + + +#ifndef INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESULTSET_HXX +#define INCLUDED_UCB_SOURCE_UCP_WEBDAV_WEBDAVRESULTSET_HXX + +#include <rtl/ref.hxx> +#include <ucbhelper/resultsethelper.hxx> +#include "webdavcontent.hxx" +#include "webdavdatasupplier.hxx" + +namespace http_dav_ucp { + +class DynamicResultSet : public ::ucbhelper::ResultSetImplHelper +{ + rtl::Reference< Content > m_xContent; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xEnv; + +private: + virtual void initStatic() override; + virtual void initDynamic() override; + +public: + DynamicResultSet( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const rtl::Reference< Content >& rxContent, + const css::ucb::OpenCommandArgument2& rCommand, + const css::uno::Reference< css::ucb::XCommandEnvironment >& rxEnv ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/webdav/webdavservices.cxx b/ucb/source/ucp/webdav/webdavservices.cxx new file mode 100644 index 000000000..419c9740d --- /dev/null +++ b/ucb/source/ucp/webdav/webdavservices.cxx @@ -0,0 +1,56 @@ +/* -*- 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 <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include "webdavprovider.hxx" + +using namespace com::sun::star; + +extern "C" SAL_DLLPUBLIC_EXPORT void * ucpdav1_component_getFactory( + const char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ ) +{ + void * pRet = nullptr; + + uno::Reference< lang::XMultiServiceFactory > xSMgr( + static_cast< lang::XMultiServiceFactory * >( pServiceManager ) ); + uno::Reference< lang::XSingleServiceFactory > xFactory; + + + // WebDAV Content Provider. + + + if ( ::http_dav_ucp::ContentProvider::getImplementationName_Static(). + equalsAscii( pImplName ) ) + { + xFactory = ::http_dav_ucp::ContentProvider::createServiceFactory( xSMgr ); + } + + + if ( xFactory.is() ) + { + xFactory->acquire(); + pRet = xFactory.get(); + } + + return pRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |